@compa11y/react 0.1.0 → 0.1.2

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/index.js CHANGED
@@ -5,14 +5,14 @@ export { ToastProvider, ToastViewport, useToast, useToastHelpers } from './chunk
5
5
  export { ComboboxCompound as Combobox, Combobox as ComboboxBase, ComboboxInput, ComboboxListbox, ComboboxOption } from './chunk-2S4C6FGA.js';
6
6
  import { useAnnouncer } from './chunk-OND5B7UG.js';
7
7
  export { useAnnounceLoading, useAnnounceOnChange, useAnnouncer } from './chunk-OND5B7UG.js';
8
- import { useKeyboard } from './chunk-JXYOE7SH.js';
8
+ import { useTypeAhead, useKeyboard } from './chunk-JXYOE7SH.js';
9
9
  export { useGridKeyboard, useKeyPressed, useKeyboard, useMenuKeyboard, useTabsKeyboard, useTypeAhead } from './chunk-JXYOE7SH.js';
10
10
  import { useId } from './chunk-WURPAE3R.js';
11
11
  export { useId, useIdScope, useIds } from './chunk-WURPAE3R.js';
12
- import { forwardRef, useState, useEffect, useCallback, useRef } from 'react';
12
+ import { createContext, forwardRef, useCallback, useRef, useState, useEffect, useLayoutEffect, useMemo, useContext, Children } from 'react';
13
13
  import { createComponentWarnings, initFocusVisible, isFocusVisible, focusWithVisibleRing } from '@compa11y/core';
14
14
  export { announce, announceAssertive, announceError, announcePolite, announceProgress, announceStatus, aria, buildAriaProps, checks, createComponentWarnings, hasAccessibleName, isAndroid, isBrowser, isIOS, isMac, mergeAriaIds, prefersDarkMode, prefersHighContrast, prefersReducedMotion, setWarningHandler, warn } from '@compa11y/core';
15
- import { jsxs, jsx } from 'react/jsx-runtime';
15
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
16
16
 
17
17
  function useFocusVisible() {
18
18
  const [isFocusVisible$1, setIsFocusVisible] = useState(false);
@@ -231,191 +231,2801 @@ function useRovingTabindexMap(ids, options = {}) {
231
231
  }
232
232
  };
233
233
  }
234
- var warnings = createComponentWarnings("Switch");
235
- var Switch = forwardRef(
236
- function Switch2({
234
+ var warnings = createComponentWarnings("Select");
235
+ var SelectContext = createContext(null);
236
+ function useSelectContext() {
237
+ const context = useContext(SelectContext);
238
+ if (!context) {
239
+ throw new Error(
240
+ "Select compound components must be used within a Select component"
241
+ );
242
+ }
243
+ return context;
244
+ }
245
+ function findNextEnabledIndex(options, currentIndex, direction) {
246
+ const length = options.length;
247
+ let index = currentIndex + direction;
248
+ if (index < 0) index = length - 1;
249
+ if (index >= length) index = 0;
250
+ const startIndex = index;
251
+ while (options[index]?.disabled) {
252
+ index += direction;
253
+ if (index < 0) index = length - 1;
254
+ if (index >= length) index = 0;
255
+ if (index === startIndex) return -1;
256
+ }
257
+ return index;
258
+ }
259
+ function findFirstEnabledIndex(options) {
260
+ return options.findIndex((o) => !o.disabled);
261
+ }
262
+ function findLastEnabledIndex(options) {
263
+ for (let i = options.length - 1; i >= 0; i--) {
264
+ if (!options[i]?.disabled) return i;
265
+ }
266
+ return -1;
267
+ }
268
+ function Select({
269
+ options,
270
+ value: controlledValue,
271
+ defaultValue,
272
+ onValueChange,
273
+ disabled = false,
274
+ placeholder = "Select an option...",
275
+ "aria-label": ariaLabel,
276
+ "aria-labelledby": ariaLabelledBy,
277
+ children
278
+ }) {
279
+ const [uncontrolledValue, setUncontrolledValue] = useState(
280
+ defaultValue ?? null
281
+ );
282
+ const [isOpen, setIsOpen] = useState(false);
283
+ const [highlightedIndex, setHighlightedIndex] = useState(-1);
284
+ const triggerRef = useRef(null);
285
+ const selectedValue = controlledValue !== void 0 ? controlledValue : uncontrolledValue;
286
+ const triggerId = useId("select-trigger");
287
+ const listboxId = useId("select-listbox");
288
+ const baseOptionId = useId("select-option");
289
+ const setSelectedValue = useCallback(
290
+ (value) => {
291
+ if (controlledValue === void 0) {
292
+ setUncontrolledValue(value);
293
+ }
294
+ onValueChange?.(value);
295
+ },
296
+ [controlledValue, onValueChange]
297
+ );
298
+ const getOptionId = useCallback(
299
+ (index) => `${baseOptionId}-${index}`,
300
+ [baseOptionId]
301
+ );
302
+ const onSelect = useCallback(
303
+ (option) => {
304
+ setSelectedValue(option.value);
305
+ setIsOpen(false);
306
+ setHighlightedIndex(-1);
307
+ triggerRef.current?.focus();
308
+ },
309
+ [setSelectedValue]
310
+ );
311
+ useEffect(() => {
312
+ if (!ariaLabel && !ariaLabelledBy) {
313
+ warnings.warning(
314
+ "Select has no accessible label.",
315
+ "Add aria-label or aria-labelledby prop."
316
+ );
317
+ }
318
+ }, [ariaLabel, ariaLabelledBy]);
319
+ const contextValue = {
320
+ selectedValue,
321
+ setSelectedValue,
322
+ isOpen,
323
+ setIsOpen,
324
+ highlightedIndex,
325
+ setHighlightedIndex,
326
+ options,
327
+ triggerId,
328
+ listboxId,
329
+ getOptionId,
330
+ onSelect,
331
+ triggerRef,
332
+ placeholder,
333
+ disabled
334
+ };
335
+ return /* @__PURE__ */ jsx(SelectContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { "data-compa11y-select": true, "data-disabled": disabled || void 0, children }) });
336
+ }
337
+ var SelectTrigger = forwardRef(
338
+ function SelectTrigger2({ onKeyDown, onClick, onBlur, ...props }, forwardedRef) {
339
+ const {
340
+ selectedValue,
341
+ isOpen,
342
+ setIsOpen,
343
+ highlightedIndex,
344
+ setHighlightedIndex,
345
+ options,
346
+ triggerId,
347
+ listboxId,
348
+ getOptionId,
349
+ onSelect,
350
+ triggerRef,
351
+ placeholder,
352
+ disabled
353
+ } = useSelectContext();
354
+ const { announce: announce2 } = useAnnouncer();
355
+ const typeAheadProps = useTypeAhead(
356
+ options.map((o) => o.label),
357
+ (match) => {
358
+ const index = options.findIndex(
359
+ (o) => o.label === match && !o.disabled
360
+ );
361
+ if (index >= 0) {
362
+ if (!isOpen) {
363
+ setIsOpen(true);
364
+ }
365
+ setHighlightedIndex(index);
366
+ }
367
+ },
368
+ { disabled }
369
+ );
370
+ const openAndHighlight = useCallback(
371
+ (preferLast = false) => {
372
+ setIsOpen(true);
373
+ const selectedIndex = options.findIndex(
374
+ (o) => o.value === selectedValue
375
+ );
376
+ if (selectedIndex >= 0) {
377
+ setHighlightedIndex(selectedIndex);
378
+ } else {
379
+ setHighlightedIndex(
380
+ preferLast ? findLastEnabledIndex(options) : findFirstEnabledIndex(options)
381
+ );
382
+ }
383
+ },
384
+ [options, selectedValue, setIsOpen, setHighlightedIndex]
385
+ );
386
+ const keyboardProps = useKeyboard(
387
+ {
388
+ ArrowDown: () => {
389
+ if (!isOpen) {
390
+ openAndHighlight();
391
+ } else {
392
+ const next = findNextEnabledIndex(options, highlightedIndex, 1);
393
+ if (next >= 0) setHighlightedIndex(next);
394
+ }
395
+ },
396
+ ArrowUp: () => {
397
+ if (!isOpen) {
398
+ openAndHighlight(true);
399
+ } else {
400
+ const prev = findNextEnabledIndex(options, highlightedIndex, -1);
401
+ if (prev >= 0) setHighlightedIndex(prev);
402
+ }
403
+ },
404
+ Enter: () => {
405
+ if (isOpen && highlightedIndex >= 0) {
406
+ const option = options[highlightedIndex];
407
+ if (option && !option.disabled) {
408
+ onSelect(option);
409
+ announce2(`${option.label} selected`);
410
+ }
411
+ } else if (!isOpen) {
412
+ openAndHighlight();
413
+ }
414
+ },
415
+ " ": () => {
416
+ if (isOpen && highlightedIndex >= 0) {
417
+ const option = options[highlightedIndex];
418
+ if (option && !option.disabled) {
419
+ onSelect(option);
420
+ announce2(`${option.label} selected`);
421
+ }
422
+ } else if (!isOpen) {
423
+ openAndHighlight();
424
+ }
425
+ },
426
+ Escape: () => {
427
+ if (isOpen) {
428
+ setIsOpen(false);
429
+ setHighlightedIndex(-1);
430
+ }
431
+ },
432
+ Home: () => {
433
+ if (isOpen) {
434
+ setHighlightedIndex(findFirstEnabledIndex(options));
435
+ }
436
+ },
437
+ End: () => {
438
+ if (isOpen) {
439
+ setHighlightedIndex(findLastEnabledIndex(options));
440
+ }
441
+ },
442
+ Tab: () => {
443
+ if (isOpen) {
444
+ setIsOpen(false);
445
+ setHighlightedIndex(-1);
446
+ }
447
+ return false;
448
+ }
449
+ },
450
+ { preventDefault: true, stopPropagation: false }
451
+ );
452
+ const handleKeyDown = (event) => {
453
+ onKeyDown?.(event);
454
+ if (!event.defaultPrevented) {
455
+ keyboardProps.onKeyDown(event);
456
+ }
457
+ if (!event.defaultPrevented) {
458
+ typeAheadProps.onKeyDown(event);
459
+ }
460
+ };
461
+ const handleClick = (event) => {
462
+ onClick?.(event);
463
+ if (!event.defaultPrevented && !disabled) {
464
+ if (isOpen) {
465
+ setIsOpen(false);
466
+ setHighlightedIndex(-1);
467
+ } else {
468
+ openAndHighlight();
469
+ }
470
+ }
471
+ };
472
+ const handleBlur = (event) => {
473
+ onBlur?.(event);
474
+ setTimeout(() => {
475
+ setIsOpen(false);
476
+ setHighlightedIndex(-1);
477
+ }, 150);
478
+ };
479
+ const activeDescendant = isOpen && highlightedIndex >= 0 ? getOptionId(highlightedIndex) : void 0;
480
+ const selectedOption = options.find((o) => o.value === selectedValue);
481
+ const displayText = selectedOption?.label ?? placeholder;
482
+ const setRefs = useCallback(
483
+ (node) => {
484
+ triggerRef.current = node;
485
+ if (typeof forwardedRef === "function") {
486
+ forwardedRef(node);
487
+ } else if (forwardedRef) {
488
+ forwardedRef.current = node;
489
+ }
490
+ },
491
+ [forwardedRef, triggerRef]
492
+ );
493
+ return /* @__PURE__ */ jsxs(
494
+ "button",
495
+ {
496
+ ref: setRefs,
497
+ id: triggerId,
498
+ type: "button",
499
+ role: "combobox",
500
+ "aria-expanded": isOpen,
501
+ "aria-controls": listboxId,
502
+ "aria-haspopup": "listbox",
503
+ "aria-activedescendant": activeDescendant,
504
+ disabled,
505
+ onKeyDown: handleKeyDown,
506
+ onClick: handleClick,
507
+ onBlur: handleBlur,
508
+ "data-compa11y-select-trigger": true,
509
+ "data-placeholder": !selectedOption || void 0,
510
+ ...props,
511
+ children: [
512
+ /* @__PURE__ */ jsx("span", { "data-compa11y-select-value": true, children: displayText }),
513
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-compa11y-select-chevron": true, children: "\u25BC" })
514
+ ]
515
+ }
516
+ );
517
+ }
518
+ );
519
+ var SelectListbox = forwardRef(
520
+ function SelectListbox2({ children, style, ...props }, forwardedRef) {
521
+ const { isOpen, options, listboxId, triggerId } = useSelectContext();
522
+ const { announce: announce2 } = useAnnouncer();
523
+ const internalRef = useRef(null);
524
+ const [position, setPosition] = useState("bottom");
525
+ useEffect(() => {
526
+ if (isOpen) {
527
+ announce2(
528
+ `${options.length} option${options.length === 1 ? "" : "s"} available`
529
+ );
530
+ }
531
+ }, [isOpen, options.length, announce2]);
532
+ useLayoutEffect(() => {
533
+ if (isOpen && internalRef.current) {
534
+ const listbox = internalRef.current;
535
+ const rect = listbox.getBoundingClientRect();
536
+ const viewportHeight = window.innerHeight;
537
+ const spaceBelow = viewportHeight - rect.top;
538
+ const spaceAbove = rect.top;
539
+ const listboxHeight = Math.min(rect.height, 200);
540
+ if (spaceBelow < listboxHeight + 50 && spaceAbove > spaceBelow) {
541
+ setPosition("top");
542
+ } else {
543
+ setPosition("bottom");
544
+ }
545
+ }
546
+ }, [isOpen]);
547
+ const handleMouseDown = (event) => {
548
+ event.preventDefault();
549
+ };
550
+ const setRefs = useCallback(
551
+ (node) => {
552
+ internalRef.current = node;
553
+ if (typeof forwardedRef === "function") {
554
+ forwardedRef(node);
555
+ } else if (forwardedRef) {
556
+ forwardedRef.current = node;
557
+ }
558
+ },
559
+ [forwardedRef]
560
+ );
561
+ if (!isOpen) {
562
+ return null;
563
+ }
564
+ const positionStyle = position === "top" ? { bottom: "100%", top: "auto", marginBottom: "4px", marginTop: 0 } : {};
565
+ return /* @__PURE__ */ jsx(
566
+ "ul",
567
+ {
568
+ ref: setRefs,
569
+ id: listboxId,
570
+ role: "listbox",
571
+ "aria-labelledby": triggerId,
572
+ style: { ...style, ...positionStyle },
573
+ onMouseDown: handleMouseDown,
574
+ "data-compa11y-select-listbox": true,
575
+ "data-position": position,
576
+ ...props,
577
+ children: children ?? options.map((option, index) => /* @__PURE__ */ jsx(
578
+ SelectOptionItem,
579
+ {
580
+ option,
581
+ index
582
+ },
583
+ option.value
584
+ ))
585
+ }
586
+ );
587
+ }
588
+ );
589
+ var SelectOptionItem = forwardRef(
590
+ function SelectOptionItem2({ option, index, onClick, onMouseEnter, ...props }, forwardedRef) {
591
+ const {
592
+ selectedValue,
593
+ highlightedIndex,
594
+ setHighlightedIndex,
595
+ getOptionId,
596
+ onSelect
597
+ } = useSelectContext();
598
+ const { announce: announce2 } = useAnnouncer();
599
+ const internalRef = useRef(null);
600
+ const isSelected = selectedValue === option.value;
601
+ const isHighlighted = highlightedIndex === index;
602
+ const optionId = getOptionId(index);
603
+ useEffect(() => {
604
+ if (isHighlighted && internalRef.current) {
605
+ internalRef.current.scrollIntoView({
606
+ block: "nearest",
607
+ behavior: "smooth"
608
+ });
609
+ }
610
+ }, [isHighlighted]);
611
+ const handleClick = (event) => {
612
+ onClick?.(event);
613
+ if (!event.defaultPrevented && !option.disabled) {
614
+ onSelect(option);
615
+ announce2(`${option.label} selected`);
616
+ }
617
+ };
618
+ const handleMouseEnter = (event) => {
619
+ onMouseEnter?.(event);
620
+ if (!option.disabled) {
621
+ setHighlightedIndex(index);
622
+ }
623
+ };
624
+ const setRefs = useCallback(
625
+ (node) => {
626
+ internalRef.current = node;
627
+ if (typeof forwardedRef === "function") {
628
+ forwardedRef(node);
629
+ } else if (forwardedRef) {
630
+ forwardedRef.current = node;
631
+ }
632
+ },
633
+ [forwardedRef]
634
+ );
635
+ return /* @__PURE__ */ jsxs(
636
+ "li",
637
+ {
638
+ ref: setRefs,
639
+ id: optionId,
640
+ role: "option",
641
+ "aria-selected": isSelected,
642
+ "aria-disabled": option.disabled,
643
+ "data-highlighted": isHighlighted,
644
+ "data-selected": isSelected,
645
+ "data-disabled": option.disabled,
646
+ onClick: handleClick,
647
+ onMouseEnter: handleMouseEnter,
648
+ "data-compa11y-select-option": true,
649
+ ...props,
650
+ children: [
651
+ /* @__PURE__ */ jsx("span", { "data-compa11y-select-option-text": true, children: option.label }),
652
+ isSelected && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-compa11y-select-check": true, children: "\u2713" })
653
+ ]
654
+ }
655
+ );
656
+ }
657
+ );
658
+ var SelectCompound = Object.assign(Select, {
659
+ Trigger: SelectTrigger,
660
+ Listbox: SelectListbox,
661
+ Option: SelectOptionItem
662
+ });
663
+ var warnings2 = createComponentWarnings("Checkbox");
664
+ var CheckmarkIcon = () => /* @__PURE__ */ jsx(
665
+ "svg",
666
+ {
667
+ width: "12",
668
+ height: "12",
669
+ viewBox: "0 0 12 12",
670
+ fill: "none",
671
+ "aria-hidden": "true",
672
+ style: { display: "block" },
673
+ children: /* @__PURE__ */ jsx(
674
+ "path",
675
+ {
676
+ d: "M2.5 6L5 8.5L9.5 3.5",
677
+ stroke: "currentColor",
678
+ strokeWidth: "2",
679
+ strokeLinecap: "round",
680
+ strokeLinejoin: "round"
681
+ }
682
+ )
683
+ }
684
+ );
685
+ var IndeterminateIcon = () => /* @__PURE__ */ jsx(
686
+ "svg",
687
+ {
688
+ width: "12",
689
+ height: "12",
690
+ viewBox: "0 0 12 12",
691
+ fill: "none",
692
+ "aria-hidden": "true",
693
+ style: { display: "block" },
694
+ children: /* @__PURE__ */ jsx(
695
+ "path",
696
+ {
697
+ d: "M3 6H9",
698
+ stroke: "currentColor",
699
+ strokeWidth: "2",
700
+ strokeLinecap: "round"
701
+ }
702
+ )
703
+ }
704
+ );
705
+ var CheckboxGroupContext = createContext(
706
+ null
707
+ );
708
+ function useCheckboxGroupContext() {
709
+ return useContext(CheckboxGroupContext);
710
+ }
711
+ var CheckboxGroup = forwardRef(
712
+ function CheckboxGroup2({
713
+ value: controlledValue,
714
+ defaultValue = [],
715
+ onValueChange,
716
+ disabled = false,
717
+ legend,
718
+ error,
719
+ orientation = "vertical",
720
+ name: providedName,
721
+ unstyled = false,
722
+ className = "",
723
+ children,
724
+ "aria-label": ariaLabel,
725
+ "aria-labelledby": ariaLabelledBy
726
+ }, ref) {
727
+ const generatedName = useId("checkbox-group");
728
+ const name = providedName || generatedName;
729
+ const errorId = useId("checkbox-group-error");
730
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
731
+ const isControlled = controlledValue !== void 0;
732
+ const value = isControlled ? controlledValue : uncontrolledValue;
733
+ useEffect(() => {
734
+ if (!legend && !ariaLabel && !ariaLabelledBy) {
735
+ warnings2.warning(
736
+ "CheckboxGroup has no accessible label. Screen readers need this to identify the group.",
737
+ 'Add legend="...", aria-label="...", or aria-labelledby="..."'
738
+ );
739
+ }
740
+ }, [legend, ariaLabel, ariaLabelledBy]);
741
+ const handleCheckboxChange = useCallback(
742
+ (checkboxValue, checked) => {
743
+ const currentValue = isControlled ? controlledValue : uncontrolledValue;
744
+ const newValue = checked ? [...currentValue, checkboxValue] : currentValue.filter((v) => v !== checkboxValue);
745
+ if (!isControlled) {
746
+ setUncontrolledValue(newValue);
747
+ }
748
+ onValueChange?.(newValue);
749
+ },
750
+ [isControlled, controlledValue, uncontrolledValue, onValueChange]
751
+ );
752
+ const contextValue = {
753
+ name,
754
+ value,
755
+ disabled,
756
+ unstyled,
757
+ onCheckboxChange: handleCheckboxChange
758
+ };
759
+ const hasError = Boolean(error);
760
+ const fieldsetStyle = {
761
+ border: "none",
762
+ margin: 0,
763
+ padding: 0,
764
+ minWidth: 0
765
+ };
766
+ const legendStyle = unstyled ? { padding: 0, marginBottom: "0.5rem" } : {
767
+ padding: 0,
768
+ marginBottom: "0.5rem",
769
+ fontWeight: 600
770
+ };
771
+ const itemsStyle = {
772
+ display: "flex",
773
+ flexDirection: orientation === "horizontal" ? "row" : "column",
774
+ flexWrap: orientation === "horizontal" ? "wrap" : void 0,
775
+ gap: "var(--compa11y-checkbox-group-gap, 0.75rem)"
776
+ };
777
+ const errorStyle = unstyled ? { marginTop: "0.25rem" } : {
778
+ color: "var(--compa11y-checkbox-group-error-color, #ef4444)",
779
+ fontSize: "0.8125rem",
780
+ marginTop: "0.25rem"
781
+ };
782
+ return /* @__PURE__ */ jsx(CheckboxGroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
783
+ "fieldset",
784
+ {
785
+ ref,
786
+ className,
787
+ "aria-label": ariaLabel,
788
+ "aria-labelledby": ariaLabelledBy,
789
+ "aria-describedby": hasError ? errorId : void 0,
790
+ disabled,
791
+ "data-compa11y-checkbox-group": "",
792
+ "data-orientation": orientation,
793
+ "data-disabled": disabled ? "true" : void 0,
794
+ style: fieldsetStyle,
795
+ children: [
796
+ legend && /* @__PURE__ */ jsx(
797
+ "legend",
798
+ {
799
+ "data-compa11y-checkbox-group-legend": "",
800
+ style: legendStyle,
801
+ children: legend
802
+ }
803
+ ),
804
+ /* @__PURE__ */ jsx("div", { style: itemsStyle, children }),
805
+ hasError && /* @__PURE__ */ jsx(
806
+ "div",
807
+ {
808
+ id: errorId,
809
+ role: "alert",
810
+ "data-compa11y-checkbox-group-error": "",
811
+ style: errorStyle,
812
+ children: error
813
+ }
814
+ )
815
+ ]
816
+ }
817
+ ) });
818
+ }
819
+ );
820
+ CheckboxGroup.displayName = "CheckboxGroup";
821
+ var Checkbox = forwardRef(
822
+ function Checkbox2({
237
823
  checked: controlledChecked,
238
824
  defaultChecked = false,
239
825
  onCheckedChange,
826
+ indeterminate = false,
827
+ label,
828
+ hint,
829
+ error,
830
+ disabled: localDisabled = false,
831
+ value,
832
+ required = false,
833
+ unstyled: localUnstyled,
834
+ className = "",
835
+ size = "md",
836
+ name: localName,
837
+ "aria-label": ariaLabel,
838
+ "aria-labelledby": ariaLabelledBy,
839
+ "aria-describedby": externalDescribedBy,
840
+ onClick,
841
+ onFocus,
842
+ onBlur,
843
+ ...props
844
+ }, ref) {
845
+ const groupContext = useCheckboxGroupContext();
846
+ const id = useId("checkbox");
847
+ const fieldId = `${id}-input`;
848
+ const labelId = `${id}-label`;
849
+ const hintId = `${id}-hint`;
850
+ const errorId = `${id}-error`;
851
+ const { announce: announce2 } = useAnnouncer();
852
+ const { isFocusVisible, focusProps } = useFocusVisible();
853
+ const isInGroup = groupContext !== null;
854
+ const disabled = isInGroup ? groupContext.disabled || localDisabled : localDisabled;
855
+ const unstyled = localUnstyled ?? (isInGroup ? groupContext.unstyled : false);
856
+ const name = isInGroup ? groupContext.name : localName;
857
+ const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
858
+ const checked = isInGroup ? groupContext.value.includes(value || "") : controlledChecked !== void 0 ? controlledChecked : uncontrolledChecked;
859
+ const inputRef = useRef(null);
860
+ const mergedRef = useCallback(
861
+ (node) => {
862
+ inputRef.current = node;
863
+ if (typeof ref === "function") {
864
+ ref(node);
865
+ } else if (ref) {
866
+ ref.current = node;
867
+ }
868
+ },
869
+ [ref]
870
+ );
871
+ useEffect(() => {
872
+ if (inputRef.current) {
873
+ inputRef.current.indeterminate = indeterminate;
874
+ }
875
+ }, [indeterminate]);
876
+ useEffect(() => {
877
+ if (!label && !ariaLabel && !ariaLabelledBy) {
878
+ warnings2.warning(
879
+ "Checkbox has no accessible label. Screen readers need this to identify the checkbox.",
880
+ 'Add label="...", aria-label="...", or aria-labelledby="..."'
881
+ );
882
+ }
883
+ }, [label, ariaLabel, ariaLabelledBy]);
884
+ const handleChange = useCallback(
885
+ (event) => {
886
+ const newChecked = event.target.checked;
887
+ if (isInGroup) {
888
+ groupContext.onCheckboxChange(value || "", newChecked);
889
+ } else {
890
+ if (controlledChecked === void 0) {
891
+ setUncontrolledChecked(newChecked);
892
+ }
893
+ onCheckedChange?.(newChecked);
894
+ }
895
+ const labelText = typeof label === "string" ? label : ariaLabel || "Checkbox";
896
+ announce2(`${labelText} ${newChecked ? "checked" : "unchecked"}`);
897
+ },
898
+ [
899
+ isInGroup,
900
+ groupContext,
901
+ value,
902
+ controlledChecked,
903
+ onCheckedChange,
904
+ label,
905
+ ariaLabel,
906
+ announce2
907
+ ]
908
+ );
909
+ const handleFocus = useCallback(
910
+ (event) => {
911
+ focusProps.onFocus();
912
+ onFocus?.(event);
913
+ },
914
+ [focusProps, onFocus]
915
+ );
916
+ const handleBlur = useCallback(
917
+ (event) => {
918
+ focusProps.onBlur();
919
+ onBlur?.(event);
920
+ },
921
+ [focusProps, onBlur]
922
+ );
923
+ const hasLabel = Boolean(label);
924
+ const hasHint = Boolean(hint);
925
+ const hasError = Boolean(error);
926
+ const describedByParts = [];
927
+ if (externalDescribedBy) describedByParts.push(externalDescribedBy);
928
+ if (hasHint) describedByParts.push(hintId);
929
+ if (hasError) describedByParts.push(errorId);
930
+ const describedBy = describedByParts.length ? describedByParts.join(" ") : void 0;
931
+ const sizes = {
932
+ sm: { box: 16 },
933
+ md: { box: 20 },
934
+ lg: { box: 24 }
935
+ };
936
+ const sizeConfig = sizes[size];
937
+ const wrapperStyle = {
938
+ display: "inline-flex",
939
+ alignItems: "flex-start",
940
+ gap: "0.5rem",
941
+ cursor: disabled ? "not-allowed" : "pointer",
942
+ userSelect: "none",
943
+ ...disabled && !unstyled ? { opacity: 0.5 } : {}
944
+ };
945
+ const controlStyle = {
946
+ position: "relative",
947
+ display: "inline-flex",
948
+ alignItems: "center",
949
+ justifyContent: "center",
950
+ flexShrink: 0
951
+ };
952
+ const hiddenInputStyle = {
953
+ position: "absolute",
954
+ opacity: 0,
955
+ width: "100%",
956
+ height: "100%",
957
+ margin: 0,
958
+ cursor: "inherit",
959
+ zIndex: 1
960
+ };
961
+ const indicatorStyle = unstyled ? {} : {
962
+ width: sizeConfig.box,
963
+ height: sizeConfig.box,
964
+ minWidth: 24,
965
+ minHeight: 24,
966
+ border: checked || indeterminate ? "var(--compa11y-checkbox-checked-border, 2px solid #0066cc)" : "var(--compa11y-checkbox-border, 2px solid #666)",
967
+ borderRadius: "var(--compa11y-checkbox-radius, 3px)",
968
+ background: checked || indeterminate ? "var(--compa11y-checkbox-checked-bg, #0066cc)" : "var(--compa11y-checkbox-bg, white)",
969
+ display: "flex",
970
+ alignItems: "center",
971
+ justifyContent: "center",
972
+ flexShrink: 0,
973
+ transition: "all 0.15s ease",
974
+ pointerEvents: "none",
975
+ color: "var(--compa11y-checkbox-check-color, white)",
976
+ ...isFocusVisible ? {
977
+ outline: "2px solid var(--compa11y-focus-color, #0066cc)",
978
+ outlineOffset: "2px"
979
+ } : {}
980
+ };
981
+ const contentStyle = {
982
+ display: "flex",
983
+ flexDirection: "column",
984
+ gap: "0.125rem",
985
+ paddingTop: "0.125rem"
986
+ };
987
+ const labelStyle = unstyled ? { cursor: "inherit" } : {
988
+ cursor: "inherit",
989
+ color: "var(--compa11y-checkbox-label-color, inherit)"
990
+ };
991
+ const hintStyle = unstyled ? {} : {
992
+ color: "var(--compa11y-checkbox-hint-color, #666)",
993
+ fontSize: "0.8125rem"
994
+ };
995
+ const errorStyles = unstyled ? {} : {
996
+ color: "var(--compa11y-checkbox-error-color, #ef4444)",
997
+ fontSize: "0.8125rem"
998
+ };
999
+ return /* @__PURE__ */ jsxs(
1000
+ "div",
1001
+ {
1002
+ className,
1003
+ "data-compa11y-checkbox": "",
1004
+ "data-checked": checked ? "true" : "false",
1005
+ "data-indeterminate": indeterminate ? "true" : void 0,
1006
+ "data-disabled": disabled ? "true" : void 0,
1007
+ "data-size": size,
1008
+ style: wrapperStyle,
1009
+ children: [
1010
+ /* @__PURE__ */ jsxs("div", { style: controlStyle, children: [
1011
+ /* @__PURE__ */ jsx(
1012
+ "input",
1013
+ {
1014
+ ref: mergedRef,
1015
+ type: "checkbox",
1016
+ id: fieldId,
1017
+ name,
1018
+ value,
1019
+ checked,
1020
+ onChange: handleChange,
1021
+ onClick,
1022
+ onFocus: handleFocus,
1023
+ onBlur: handleBlur,
1024
+ disabled,
1025
+ required,
1026
+ "aria-required": required || void 0,
1027
+ "aria-invalid": hasError || void 0,
1028
+ "aria-label": !hasLabel && !ariaLabelledBy ? ariaLabel : void 0,
1029
+ "aria-labelledby": ariaLabelledBy || (hasLabel ? labelId : void 0),
1030
+ "aria-describedby": describedBy,
1031
+ "aria-checked": indeterminate ? "mixed" : checked,
1032
+ tabIndex: disabled ? -1 : 0,
1033
+ style: hiddenInputStyle,
1034
+ ...props
1035
+ }
1036
+ ),
1037
+ !unstyled && /* @__PURE__ */ jsxs(
1038
+ "div",
1039
+ {
1040
+ "data-compa11y-checkbox-indicator": "",
1041
+ "aria-hidden": "true",
1042
+ style: indicatorStyle,
1043
+ children: [
1044
+ checked && !indeterminate && /* @__PURE__ */ jsx(CheckmarkIcon, {}),
1045
+ indeterminate && /* @__PURE__ */ jsx(IndeterminateIcon, {})
1046
+ ]
1047
+ }
1048
+ )
1049
+ ] }),
1050
+ (hasLabel || hasHint || hasError) && /* @__PURE__ */ jsxs("div", { style: contentStyle, children: [
1051
+ hasLabel && /* @__PURE__ */ jsxs(
1052
+ "label",
1053
+ {
1054
+ htmlFor: fieldId,
1055
+ id: labelId,
1056
+ "data-compa11y-checkbox-label": "",
1057
+ style: labelStyle,
1058
+ children: [
1059
+ label,
1060
+ required && /* @__PURE__ */ jsx(
1061
+ "span",
1062
+ {
1063
+ "aria-hidden": "true",
1064
+ style: {
1065
+ color: "var(--compa11y-checkbox-required-color, #ef4444)",
1066
+ marginLeft: "0.125rem"
1067
+ },
1068
+ children: "*"
1069
+ }
1070
+ )
1071
+ ]
1072
+ }
1073
+ ),
1074
+ hasHint && /* @__PURE__ */ jsx(
1075
+ "div",
1076
+ {
1077
+ id: hintId,
1078
+ "data-compa11y-checkbox-hint": "",
1079
+ style: hintStyle,
1080
+ children: hint
1081
+ }
1082
+ ),
1083
+ hasError && /* @__PURE__ */ jsx(
1084
+ "div",
1085
+ {
1086
+ id: errorId,
1087
+ role: "alert",
1088
+ "data-compa11y-checkbox-error": "",
1089
+ style: errorStyles,
1090
+ children: error
1091
+ }
1092
+ )
1093
+ ] })
1094
+ ]
1095
+ }
1096
+ );
1097
+ }
1098
+ );
1099
+ Checkbox.displayName = "Checkbox";
1100
+ var CheckboxIndicator = forwardRef(function CheckboxIndicator2({ children, className }, ref) {
1101
+ return /* @__PURE__ */ jsx(
1102
+ "div",
1103
+ {
1104
+ ref,
1105
+ className,
1106
+ "data-compa11y-checkbox-indicator": "",
1107
+ "aria-hidden": "true",
1108
+ children
1109
+ }
1110
+ );
1111
+ });
1112
+ CheckboxIndicator.displayName = "CheckboxIndicator";
1113
+ var CheckboxCompound = Object.assign(Checkbox, {
1114
+ Group: CheckboxGroup,
1115
+ Indicator: CheckboxIndicator
1116
+ });
1117
+ var warnings3 = createComponentWarnings("RadioGroup");
1118
+ var RadioGroupContext = createContext(null);
1119
+ function useRadioGroupContext() {
1120
+ const context = useContext(RadioGroupContext);
1121
+ if (!context) {
1122
+ throw new Error(
1123
+ "[Compa11y RadioGroup]: Radio must be used within a RadioGroup."
1124
+ );
1125
+ }
1126
+ return context;
1127
+ }
1128
+ var RadioGroup = forwardRef(
1129
+ function RadioGroup2({
1130
+ value: controlledValue,
1131
+ defaultValue,
1132
+ onValueChange,
1133
+ disabled = false,
1134
+ discoverable = true,
1135
+ required = false,
1136
+ orientation = "vertical",
1137
+ name: providedName,
1138
+ legend,
1139
+ hint,
1140
+ error,
1141
+ unstyled = false,
1142
+ className = "",
1143
+ children,
1144
+ "aria-label": ariaLabel,
1145
+ "aria-labelledby": ariaLabelledBy
1146
+ }, ref) {
1147
+ const generatedName = useId("radiogroup");
1148
+ const name = providedName || generatedName;
1149
+ const hintId = useId("radiogroup-hint");
1150
+ const errorId = useId("radiogroup-error");
1151
+ const [uncontrolledValue, setUncontrolledValue] = useState(
1152
+ defaultValue ?? null
1153
+ );
1154
+ const isControlled = controlledValue !== void 0;
1155
+ const value = isControlled ? controlledValue : uncontrolledValue;
1156
+ const radiosRef = useRef(/* @__PURE__ */ new Map());
1157
+ useEffect(() => {
1158
+ if (!legend && !ariaLabel && !ariaLabelledBy) {
1159
+ warnings3.warning(
1160
+ "RadioGroup has no accessible label. Screen readers need this to identify the group.",
1161
+ 'Add legend="...", aria-label="...", or aria-labelledby="..."'
1162
+ );
1163
+ }
1164
+ }, [legend, ariaLabel, ariaLabelledBy]);
1165
+ const handleValueChange = useCallback(
1166
+ (newValue) => {
1167
+ if (disabled) return;
1168
+ if (!isControlled) {
1169
+ setUncontrolledValue(newValue);
1170
+ }
1171
+ onValueChange?.(newValue);
1172
+ },
1173
+ [disabled, isControlled, onValueChange]
1174
+ );
1175
+ const registerRadio = useCallback(
1176
+ (radioValue, radioDisabled) => {
1177
+ radiosRef.current.set(radioValue, radioDisabled);
1178
+ },
1179
+ []
1180
+ );
1181
+ const unregisterRadio = useCallback((radioValue) => {
1182
+ radiosRef.current.delete(radioValue);
1183
+ }, []);
1184
+ const updateRadioDisabled = useCallback(
1185
+ (radioValue, radioDisabled) => {
1186
+ radiosRef.current.set(radioValue, radioDisabled);
1187
+ },
1188
+ []
1189
+ );
1190
+ const getEnabledRadioValues = useCallback(() => {
1191
+ const values = [];
1192
+ radiosRef.current.forEach((isDisabled, radioValue) => {
1193
+ if (!isDisabled && !disabled) {
1194
+ values.push(radioValue);
1195
+ }
1196
+ });
1197
+ return values;
1198
+ }, [disabled]);
1199
+ const internalRef = useRef(null);
1200
+ const groupRef = ref || internalRef;
1201
+ const navigateWithRef = useCallback(
1202
+ (direction) => {
1203
+ const enabledValues = getEnabledRadioValues();
1204
+ if (enabledValues.length === 0) return;
1205
+ const currentIndex = value ? enabledValues.indexOf(value) : -1;
1206
+ let nextIndex;
1207
+ switch (direction) {
1208
+ case "next":
1209
+ nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % enabledValues.length;
1210
+ break;
1211
+ case "prev":
1212
+ nextIndex = currentIndex < 0 ? enabledValues.length - 1 : (currentIndex - 1 + enabledValues.length) % enabledValues.length;
1213
+ break;
1214
+ case "first":
1215
+ nextIndex = 0;
1216
+ break;
1217
+ case "last":
1218
+ nextIndex = enabledValues.length - 1;
1219
+ break;
1220
+ }
1221
+ const nextValue = enabledValues[nextIndex];
1222
+ if (nextValue !== void 0) {
1223
+ handleValueChange(nextValue);
1224
+ const groupEl = typeof groupRef === "object" && groupRef ? groupRef.current : null;
1225
+ if (groupEl) {
1226
+ const input = groupEl.querySelector(
1227
+ `input[type="radio"][value="${CSS.escape(nextValue)}"]`
1228
+ );
1229
+ input?.focus();
1230
+ }
1231
+ }
1232
+ },
1233
+ [getEnabledRadioValues, value, handleValueChange, groupRef]
1234
+ );
1235
+ const keyboardProps = useKeyboard(
1236
+ {
1237
+ ArrowDown: () => navigateWithRef("next"),
1238
+ ArrowRight: () => navigateWithRef("next"),
1239
+ ArrowUp: () => navigateWithRef("prev"),
1240
+ ArrowLeft: () => navigateWithRef("prev"),
1241
+ Home: () => navigateWithRef("first"),
1242
+ End: () => navigateWithRef("last")
1243
+ },
1244
+ { preventDefault: true, stopPropagation: true, disabled }
1245
+ );
1246
+ const contextValue = {
1247
+ name,
1248
+ value: value ?? null,
1249
+ disabled,
1250
+ discoverable,
1251
+ required,
1252
+ unstyled,
1253
+ orientation,
1254
+ onValueChange: handleValueChange,
1255
+ registerRadio,
1256
+ unregisterRadio,
1257
+ updateRadioDisabled
1258
+ };
1259
+ const describedByParts = [];
1260
+ if (hint) describedByParts.push(hintId);
1261
+ if (error) describedByParts.push(errorId);
1262
+ const ariaDescribedBy = describedByParts.length > 0 ? describedByParts.join(" ") : void 0;
1263
+ const groupContent = /* @__PURE__ */ jsxs(Fragment, { children: [
1264
+ /* @__PURE__ */ jsx(
1265
+ "div",
1266
+ {
1267
+ style: unstyled ? {
1268
+ display: "flex",
1269
+ flexDirection: orientation === "horizontal" ? "row" : "column"
1270
+ } : {
1271
+ display: "flex",
1272
+ flexDirection: orientation === "horizontal" ? "row" : "column",
1273
+ gap: "var(--compa11y-radio-gap, 0.75rem)"
1274
+ },
1275
+ children
1276
+ }
1277
+ ),
1278
+ hint && /* @__PURE__ */ jsx(
1279
+ "div",
1280
+ {
1281
+ id: hintId,
1282
+ style: unstyled ? {} : {
1283
+ color: "var(--compa11y-radio-group-hint-color, #666)",
1284
+ fontSize: "0.8125rem",
1285
+ marginTop: "0.25rem"
1286
+ },
1287
+ children: hint
1288
+ }
1289
+ ),
1290
+ error && /* @__PURE__ */ jsx(
1291
+ "div",
1292
+ {
1293
+ id: errorId,
1294
+ role: "alert",
1295
+ style: unstyled ? {} : {
1296
+ color: "var(--compa11y-radio-group-error-color, #ef4444)",
1297
+ fontSize: "0.8125rem",
1298
+ marginTop: "0.25rem"
1299
+ },
1300
+ children: error
1301
+ }
1302
+ )
1303
+ ] });
1304
+ if (legend) {
1305
+ return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
1306
+ "fieldset",
1307
+ {
1308
+ ref: groupRef,
1309
+ role: "radiogroup",
1310
+ "aria-label": ariaLabel,
1311
+ "aria-labelledby": ariaLabelledBy,
1312
+ "aria-orientation": orientation,
1313
+ "aria-required": required || void 0,
1314
+ "aria-disabled": disabled || void 0,
1315
+ "aria-invalid": error ? true : void 0,
1316
+ "aria-describedby": ariaDescribedBy,
1317
+ className: `compa11y-radiogroup ${className}`.trim(),
1318
+ "data-compa11y-radiogroup": "",
1319
+ "data-orientation": orientation,
1320
+ "data-disabled": disabled ? "true" : void 0,
1321
+ "data-error": error ? "true" : void 0,
1322
+ onKeyDown: keyboardProps.onKeyDown,
1323
+ style: unstyled ? { border: "none", margin: 0, padding: 0, minWidth: 0 } : {
1324
+ border: "none",
1325
+ margin: 0,
1326
+ padding: 0,
1327
+ minWidth: 0
1328
+ },
1329
+ children: [
1330
+ /* @__PURE__ */ jsxs(
1331
+ "legend",
1332
+ {
1333
+ style: unstyled ? {} : {
1334
+ padding: 0,
1335
+ marginBottom: "var(--compa11y-radio-group-legend-gap, 0.5rem)",
1336
+ fontWeight: "var(--compa11y-radio-group-legend-weight, 600)",
1337
+ color: "var(--compa11y-radio-group-legend-color, inherit)",
1338
+ fontSize: "var(--compa11y-radio-group-legend-size, 1rem)"
1339
+ },
1340
+ children: [
1341
+ legend,
1342
+ required && !unstyled && /* @__PURE__ */ jsxs(
1343
+ "span",
1344
+ {
1345
+ "aria-hidden": "true",
1346
+ style: {
1347
+ color: "var(--compa11y-radio-group-required-color, #ef4444)",
1348
+ marginLeft: "0.125rem"
1349
+ },
1350
+ children: [
1351
+ " ",
1352
+ "*"
1353
+ ]
1354
+ }
1355
+ )
1356
+ ]
1357
+ }
1358
+ ),
1359
+ groupContent
1360
+ ]
1361
+ }
1362
+ ) });
1363
+ }
1364
+ return /* @__PURE__ */ jsx(RadioGroupContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
1365
+ "div",
1366
+ {
1367
+ ref: groupRef,
1368
+ role: "radiogroup",
1369
+ "aria-label": ariaLabel,
1370
+ "aria-labelledby": ariaLabelledBy,
1371
+ "aria-orientation": orientation,
1372
+ "aria-required": required || void 0,
1373
+ "aria-disabled": disabled || void 0,
1374
+ "aria-invalid": error ? true : void 0,
1375
+ "aria-describedby": ariaDescribedBy,
1376
+ className: `compa11y-radiogroup ${className}`.trim(),
1377
+ "data-compa11y-radiogroup": "",
1378
+ "data-orientation": orientation,
1379
+ "data-disabled": disabled ? "true" : void 0,
1380
+ "data-error": error ? "true" : void 0,
1381
+ onKeyDown: keyboardProps.onKeyDown,
1382
+ children: groupContent
1383
+ }
1384
+ ) });
1385
+ }
1386
+ );
1387
+ RadioGroup.displayName = "RadioGroup";
1388
+ var Radio = forwardRef(
1389
+ function Radio2({
1390
+ value,
1391
+ disabled: localDisabled = false,
1392
+ discoverable: localDiscoverable,
240
1393
  label,
1394
+ hint,
1395
+ unstyled: localUnstyled,
1396
+ className = "",
1397
+ children,
1398
+ "aria-label": ariaLabel
1399
+ }, ref) {
1400
+ const context = useRadioGroupContext();
1401
+ const generatedId = useId("radio");
1402
+ const hintId = useId("radio-hint");
1403
+ const disabled = context.disabled || localDisabled;
1404
+ const discoverable = localDiscoverable ?? context.discoverable;
1405
+ const unstyled = localUnstyled ?? context.unstyled;
1406
+ const checked = context.value === value;
1407
+ const { announce: announce2 } = useAnnouncer();
1408
+ const { isFocusVisible, focusProps } = useFocusVisible();
1409
+ const inputRef = useRef(null);
1410
+ const mergedRef = useCallback(
1411
+ (node) => {
1412
+ inputRef.current = node;
1413
+ if (typeof ref === "function") {
1414
+ ref(node);
1415
+ } else if (ref) {
1416
+ ref.current = node;
1417
+ }
1418
+ },
1419
+ [ref]
1420
+ );
1421
+ useEffect(() => {
1422
+ context.registerRadio(value, disabled);
1423
+ return () => context.unregisterRadio(value);
1424
+ }, [value]);
1425
+ useEffect(() => {
1426
+ context.updateRadioDisabled(value, disabled);
1427
+ }, [disabled, value]);
1428
+ const handleChange = useCallback(
1429
+ (event) => {
1430
+ if (disabled) {
1431
+ event.preventDefault();
1432
+ return;
1433
+ }
1434
+ context.onValueChange(value);
1435
+ const labelText = label || (typeof children === "string" ? children : null) || value;
1436
+ announce2(`${labelText} selected`, { politeness: "polite" });
1437
+ },
1438
+ [disabled, context, value, label, children, announce2]
1439
+ );
1440
+ const handleClick = useCallback(
1441
+ (event) => {
1442
+ if (disabled) {
1443
+ event.preventDefault();
1444
+ }
1445
+ },
1446
+ [disabled]
1447
+ );
1448
+ const handleFocus = useCallback(
1449
+ (_event) => {
1450
+ focusProps.onFocus();
1451
+ if (!checked && !disabled) {
1452
+ context.onValueChange(value);
1453
+ }
1454
+ },
1455
+ [focusProps, checked, disabled, context, value]
1456
+ );
1457
+ const handleBlur = useCallback(() => {
1458
+ focusProps.onBlur();
1459
+ }, [focusProps]);
1460
+ const hasLabel = Boolean(children || label);
1461
+ const labelContent = children || label;
1462
+ const labelId = `${generatedId}-label`;
1463
+ const hasHint = Boolean(hint);
1464
+ const isFirstEnabled = (() => {
1465
+ if (context.value !== null) return false;
1466
+ const entries = Array.from(
1467
+ inputRef.current?.closest('[role="radiogroup"]')?.querySelectorAll('input[type="radio"]') ?? []
1468
+ );
1469
+ for (const entry of entries) {
1470
+ const input = entry;
1471
+ if (!input.disabled && input.getAttribute("aria-disabled") !== "true") {
1472
+ return input.value === value;
1473
+ }
1474
+ }
1475
+ return false;
1476
+ })();
1477
+ const tabIndex = (() => {
1478
+ if (disabled && !discoverable) return -1;
1479
+ if (checked) return 0;
1480
+ if (context.value === null && isFirstEnabled) return 0;
1481
+ return -1;
1482
+ })();
1483
+ const useNativeDisabled = disabled && !discoverable;
1484
+ const ariaDescribedBy = hasHint ? hintId : void 0;
1485
+ return /* @__PURE__ */ jsxs(
1486
+ "label",
1487
+ {
1488
+ htmlFor: generatedId,
1489
+ className: `compa11y-radio-wrapper ${className}`.trim(),
1490
+ "data-compa11y-radio": "",
1491
+ "data-value": value,
1492
+ "data-checked": checked ? "true" : "false",
1493
+ "data-disabled": disabled ? "true" : void 0,
1494
+ style: unstyled ? {
1495
+ display: "inline-flex",
1496
+ alignItems: "flex-start",
1497
+ cursor: disabled ? "not-allowed" : "pointer",
1498
+ gap: "0.5rem"
1499
+ } : {
1500
+ display: "inline-flex",
1501
+ alignItems: "flex-start",
1502
+ gap: "var(--compa11y-radio-gap, 0.5rem)",
1503
+ cursor: disabled ? "not-allowed" : "pointer",
1504
+ userSelect: "none",
1505
+ opacity: disabled ? 0.5 : 1
1506
+ },
1507
+ children: [
1508
+ /* @__PURE__ */ jsxs(
1509
+ "div",
1510
+ {
1511
+ style: {
1512
+ position: "relative",
1513
+ display: "inline-flex",
1514
+ alignItems: "center",
1515
+ justifyContent: "center",
1516
+ flexShrink: 0
1517
+ },
1518
+ children: [
1519
+ /* @__PURE__ */ jsx(
1520
+ "input",
1521
+ {
1522
+ ref: mergedRef,
1523
+ type: "radio",
1524
+ id: generatedId,
1525
+ name: context.name,
1526
+ value,
1527
+ checked,
1528
+ onChange: handleChange,
1529
+ onClick: handleClick,
1530
+ onFocus: handleFocus,
1531
+ onBlur: handleBlur,
1532
+ disabled: useNativeDisabled,
1533
+ tabIndex,
1534
+ "aria-disabled": disabled ? "true" : void 0,
1535
+ "aria-label": !hasLabel ? ariaLabel : void 0,
1536
+ "aria-labelledby": hasLabel ? labelId : void 0,
1537
+ "aria-describedby": ariaDescribedBy,
1538
+ required: context.required,
1539
+ className: "compa11y-radio-input",
1540
+ style: {
1541
+ position: "absolute",
1542
+ opacity: 0,
1543
+ width: "var(--compa11y-radio-size, 1.25rem)",
1544
+ height: "var(--compa11y-radio-size, 1.25rem)",
1545
+ margin: 0,
1546
+ cursor: disabled ? "not-allowed" : "pointer"
1547
+ }
1548
+ }
1549
+ ),
1550
+ !unstyled && /* @__PURE__ */ jsx(
1551
+ "div",
1552
+ {
1553
+ className: "compa11y-radio-circle",
1554
+ style: {
1555
+ width: "var(--compa11y-radio-size, 1.25rem)",
1556
+ height: "var(--compa11y-radio-size, 1.25rem)",
1557
+ minWidth: "24px",
1558
+ minHeight: "24px",
1559
+ border: checked ? "var(--compa11y-radio-checked-border, 2px solid #0066cc)" : "var(--compa11y-radio-border, 2px solid #666)",
1560
+ borderRadius: "50%",
1561
+ background: checked ? "var(--compa11y-radio-checked-bg, #0066cc)" : "var(--compa11y-radio-bg, white)",
1562
+ display: "flex",
1563
+ alignItems: "center",
1564
+ justifyContent: "center",
1565
+ flexShrink: 0,
1566
+ transition: "all 0.15s ease",
1567
+ pointerEvents: "none",
1568
+ ...isFocusVisible ? {
1569
+ outline: "2px solid var(--compa11y-focus-color, #0066cc)",
1570
+ outlineOffset: "2px"
1571
+ } : {}
1572
+ },
1573
+ children: /* @__PURE__ */ jsx(
1574
+ "div",
1575
+ {
1576
+ "aria-hidden": "true",
1577
+ style: {
1578
+ width: "var(--compa11y-radio-dot-size, 0.5rem)",
1579
+ height: "var(--compa11y-radio-dot-size, 0.5rem)",
1580
+ borderRadius: "50%",
1581
+ background: "var(--compa11y-radio-dot-color, white)",
1582
+ opacity: checked ? 1 : 0,
1583
+ transform: checked ? "scale(1)" : "scale(0)",
1584
+ transition: "all 0.15s ease"
1585
+ }
1586
+ }
1587
+ )
1588
+ }
1589
+ )
1590
+ ]
1591
+ }
1592
+ ),
1593
+ (hasLabel || hasHint) && /* @__PURE__ */ jsxs(
1594
+ "div",
1595
+ {
1596
+ style: unstyled ? {} : {
1597
+ display: "flex",
1598
+ flexDirection: "column",
1599
+ gap: "0.125rem",
1600
+ paddingTop: "0.125rem"
1601
+ },
1602
+ children: [
1603
+ hasLabel && /* @__PURE__ */ jsx(
1604
+ "span",
1605
+ {
1606
+ id: labelId,
1607
+ className: "compa11y-radio-label",
1608
+ style: unstyled ? {} : {
1609
+ color: "var(--compa11y-radio-label-color, inherit)",
1610
+ fontSize: "var(--compa11y-radio-label-size, 1rem)"
1611
+ },
1612
+ children: labelContent
1613
+ }
1614
+ ),
1615
+ hasHint && /* @__PURE__ */ jsx(
1616
+ "span",
1617
+ {
1618
+ id: hintId,
1619
+ className: "compa11y-radio-hint",
1620
+ style: unstyled ? {} : {
1621
+ color: "var(--compa11y-radio-hint-color, #666)",
1622
+ fontSize: "var(--compa11y-radio-hint-size, 0.8125rem)"
1623
+ },
1624
+ children: hint
1625
+ }
1626
+ )
1627
+ ]
1628
+ }
1629
+ )
1630
+ ]
1631
+ }
1632
+ );
1633
+ }
1634
+ );
1635
+ Radio.displayName = "Radio";
1636
+ var RadioGroupCompound = Object.assign(RadioGroup, { Radio });
1637
+ var warnings4 = createComponentWarnings("Switch");
1638
+ var Switch = forwardRef(
1639
+ function Switch2({
1640
+ checked: controlledChecked,
1641
+ defaultChecked = false,
1642
+ onCheckedChange,
1643
+ label,
1644
+ disabled = false,
1645
+ unstyled = false,
1646
+ className,
1647
+ size = "md",
1648
+ "aria-label": ariaLabel,
1649
+ "aria-labelledby": ariaLabelledby,
1650
+ onClick,
1651
+ ...props
1652
+ }, ref) {
1653
+ const id = useId("switch");
1654
+ const labelId = `${id}-label`;
1655
+ const { announce: announce2 } = useAnnouncer();
1656
+ const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
1657
+ const isControlled = controlledChecked !== void 0;
1658
+ const checked = isControlled ? controlledChecked : uncontrolledChecked;
1659
+ useEffect(() => {
1660
+ if (!label && !ariaLabel && !ariaLabelledby) {
1661
+ warnings4.warning(
1662
+ "Switch has no accessible label. Screen readers need this to identify the switch.",
1663
+ 'Add label="Description", aria-label="...", or aria-labelledby="..."'
1664
+ );
1665
+ }
1666
+ }, [label, ariaLabel, ariaLabelledby]);
1667
+ const toggleSwitch = useCallback(() => {
1668
+ if (disabled) return;
1669
+ const newChecked = !checked;
1670
+ if (!isControlled) {
1671
+ setUncontrolledChecked(newChecked);
1672
+ }
1673
+ onCheckedChange?.(newChecked);
1674
+ const labelText = label || ariaLabel || "Switch";
1675
+ announce2(`${labelText} ${newChecked ? "on" : "off"}`);
1676
+ }, [
1677
+ checked,
1678
+ disabled,
1679
+ isControlled,
1680
+ onCheckedChange,
1681
+ label,
1682
+ ariaLabel,
1683
+ announce2
1684
+ ]);
1685
+ const handleClick = useCallback(
1686
+ (event) => {
1687
+ onClick?.(event);
1688
+ if (!event.defaultPrevented) {
1689
+ toggleSwitch();
1690
+ }
1691
+ },
1692
+ [onClick, toggleSwitch]
1693
+ );
1694
+ const keyboardProps = useKeyboard(
1695
+ {
1696
+ " ": () => {
1697
+ toggleSwitch();
1698
+ },
1699
+ Enter: () => {
1700
+ toggleSwitch();
1701
+ }
1702
+ },
1703
+ { preventDefault: true }
1704
+ );
1705
+ const handleKeyDown = (event) => {
1706
+ props.onKeyDown?.(event);
1707
+ if (!event.defaultPrevented) {
1708
+ keyboardProps.onKeyDown(event);
1709
+ }
1710
+ };
1711
+ const computedAriaLabel = ariaLabel;
1712
+ const computedAriaLabelledby = ariaLabelledby || (label ? labelId : void 0);
1713
+ const wrapperStructuralStyles = {
1714
+ display: "inline-flex",
1715
+ alignItems: "center"
1716
+ };
1717
+ const sizes = {
1718
+ sm: { width: 32, height: 18, thumb: 14, translate: 14 },
1719
+ md: { width: 44, height: 24, thumb: 20, translate: 20 },
1720
+ lg: { width: 56, height: 30, thumb: 26, translate: 26 }
1721
+ };
1722
+ const sizeConfig = sizes[size];
1723
+ const trackStructuralStyles = {
1724
+ position: "relative",
1725
+ display: "inline-flex",
1726
+ alignItems: "center",
1727
+ flexShrink: 0,
1728
+ width: sizeConfig.width,
1729
+ height: sizeConfig.height,
1730
+ border: "none",
1731
+ padding: 2,
1732
+ cursor: disabled ? "not-allowed" : "pointer"
1733
+ };
1734
+ const trackVisualStyles = unstyled ? {} : {
1735
+ backgroundColor: checked ? "#0066cc" : "#d1d5db",
1736
+ borderRadius: sizeConfig.height / 2,
1737
+ transition: "background-color 0.2s ease",
1738
+ opacity: disabled ? 0.5 : 1
1739
+ };
1740
+ const thumbStructuralStyles = {
1741
+ position: "absolute",
1742
+ left: 2,
1743
+ width: sizeConfig.thumb,
1744
+ height: sizeConfig.thumb,
1745
+ pointerEvents: "none",
1746
+ transform: checked ? `translateX(${sizeConfig.translate}px)` : "translateX(0)"
1747
+ };
1748
+ const thumbVisualStyles = unstyled ? {} : {
1749
+ backgroundColor: "white",
1750
+ borderRadius: "50%",
1751
+ boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)",
1752
+ transition: "transform 0.2s ease"
1753
+ };
1754
+ const labelStyles = unstyled ? {
1755
+ marginLeft: 8,
1756
+ userSelect: "none",
1757
+ cursor: disabled ? "not-allowed" : "pointer"
1758
+ } : {
1759
+ marginLeft: 8,
1760
+ userSelect: "none",
1761
+ cursor: disabled ? "not-allowed" : "pointer",
1762
+ opacity: disabled ? 0.5 : 1
1763
+ };
1764
+ return /* @__PURE__ */ jsxs(
1765
+ "div",
1766
+ {
1767
+ style: wrapperStructuralStyles,
1768
+ "data-compa11y-switch-wrapper": true,
1769
+ "data-size": size,
1770
+ children: [
1771
+ /* @__PURE__ */ jsx(
1772
+ "button",
1773
+ {
1774
+ ref,
1775
+ type: "button",
1776
+ role: "switch",
1777
+ "aria-checked": checked,
1778
+ "aria-label": computedAriaLabel,
1779
+ "aria-labelledby": computedAriaLabelledby,
1780
+ disabled,
1781
+ onClick: handleClick,
1782
+ onKeyDown: handleKeyDown,
1783
+ className,
1784
+ style: { ...trackStructuralStyles, ...trackVisualStyles },
1785
+ tabIndex: disabled ? -1 : 0,
1786
+ "data-compa11y-switch": true,
1787
+ "data-checked": checked,
1788
+ "data-disabled": disabled || void 0,
1789
+ "data-size": size,
1790
+ ...props,
1791
+ onFocus: (e) => {
1792
+ if (!unstyled) {
1793
+ e.currentTarget.style.outline = "2px solid #0066cc";
1794
+ e.currentTarget.style.outlineOffset = "2px";
1795
+ }
1796
+ props.onFocus?.(e);
1797
+ },
1798
+ onBlur: (e) => {
1799
+ if (!unstyled) {
1800
+ e.currentTarget.style.outline = "none";
1801
+ }
1802
+ props.onBlur?.(e);
1803
+ },
1804
+ children: /* @__PURE__ */ jsx(
1805
+ "span",
1806
+ {
1807
+ style: { ...thumbStructuralStyles, ...thumbVisualStyles },
1808
+ "data-compa11y-switch-thumb": true,
1809
+ "aria-hidden": "true"
1810
+ }
1811
+ )
1812
+ }
1813
+ ),
1814
+ label && /* @__PURE__ */ jsx(
1815
+ "label",
1816
+ {
1817
+ id: labelId,
1818
+ onClick: disabled ? void 0 : () => toggleSwitch(),
1819
+ style: labelStyles,
1820
+ "data-compa11y-switch-label": true,
1821
+ children: label
1822
+ }
1823
+ )
1824
+ ]
1825
+ }
1826
+ );
1827
+ }
1828
+ );
1829
+ Switch.displayName = "Switch";
1830
+ var warnings5 = createComponentWarnings("Listbox");
1831
+ var ListboxContext = createContext(null);
1832
+ function useListboxContext() {
1833
+ const context = useContext(ListboxContext);
1834
+ if (!context) {
1835
+ throw new Error(
1836
+ "[Compa11y Listbox]: Option/Group must be used within a Listbox."
1837
+ );
1838
+ }
1839
+ return context;
1840
+ }
1841
+ var ListboxGroupContext = createContext({
1842
+ groupDisabled: false
1843
+ });
1844
+ var Listbox = forwardRef(
1845
+ function Listbox2({
1846
+ value: controlledValue,
1847
+ defaultValue,
1848
+ onValueChange,
1849
+ multiple = false,
1850
+ disabled = false,
1851
+ discoverable = true,
1852
+ orientation = "vertical",
1853
+ unstyled = false,
1854
+ className = "",
1855
+ children,
1856
+ "aria-label": ariaLabel,
1857
+ "aria-labelledby": ariaLabelledBy
1858
+ }, ref) {
1859
+ const listboxId = useId("listbox");
1860
+ const { announce: announce2 } = useAnnouncer();
1861
+ const [uncontrolledValue, setUncontrolledValue] = useState(() => defaultValue ?? (multiple ? [] : ""));
1862
+ const isControlled = controlledValue !== void 0;
1863
+ const currentValue = isControlled ? controlledValue : uncontrolledValue;
1864
+ const selectedValues = useMemo(() => {
1865
+ if (multiple) {
1866
+ return new Set(
1867
+ Array.isArray(currentValue) ? currentValue : []
1868
+ );
1869
+ }
1870
+ return new Set(currentValue ? [String(currentValue)] : []);
1871
+ }, [currentValue, multiple]);
1872
+ const [focusedValue, setFocusedValue] = useState(null);
1873
+ const optionsRef = useRef(/* @__PURE__ */ new Map());
1874
+ const [optionsVersion, setOptionsVersion] = useState(0);
1875
+ const registerOption = useCallback(
1876
+ (value, optDisabled, label) => {
1877
+ optionsRef.current.set(value, { disabled: optDisabled, label });
1878
+ setOptionsVersion((v) => v + 1);
1879
+ },
1880
+ []
1881
+ );
1882
+ const unregisterOption = useCallback((value) => {
1883
+ optionsRef.current.delete(value);
1884
+ setOptionsVersion((v) => v + 1);
1885
+ }, []);
1886
+ const updateOptionDisabled = useCallback(
1887
+ (value, optDisabled) => {
1888
+ const existing = optionsRef.current.get(value);
1889
+ if (existing) {
1890
+ optionsRef.current.set(value, { ...existing, disabled: optDisabled });
1891
+ }
1892
+ },
1893
+ []
1894
+ );
1895
+ const enabledValues = useMemo(() => {
1896
+ const result = [];
1897
+ optionsRef.current.forEach((info, value) => {
1898
+ if (!info.disabled && !disabled) {
1899
+ result.push(value);
1900
+ }
1901
+ });
1902
+ return result;
1903
+ }, [optionsVersion, disabled]);
1904
+ useEffect(() => {
1905
+ if (!ariaLabel && !ariaLabelledBy) {
1906
+ warnings5.warning(
1907
+ "Listbox has no accessible label. Screen readers need this.",
1908
+ 'Add aria-label="..." or aria-labelledby="..."'
1909
+ );
1910
+ }
1911
+ }, [ariaLabel, ariaLabelledBy]);
1912
+ const isSelected = useCallback(
1913
+ (value) => selectedValues.has(value),
1914
+ [selectedValues]
1915
+ );
1916
+ const handleSelect = useCallback(
1917
+ (optionValue) => {
1918
+ if (disabled) return;
1919
+ const info = optionsRef.current.get(optionValue);
1920
+ if (info?.disabled) return;
1921
+ let newValue;
1922
+ const label = info?.label || optionValue;
1923
+ if (multiple) {
1924
+ const current = new Set(selectedValues);
1925
+ if (current.has(optionValue)) {
1926
+ current.delete(optionValue);
1927
+ announce2(`${label} deselected`, { politeness: "polite" });
1928
+ } else {
1929
+ current.add(optionValue);
1930
+ announce2(`${label} selected`, { politeness: "polite" });
1931
+ }
1932
+ newValue = Array.from(current);
1933
+ } else {
1934
+ newValue = optionValue;
1935
+ announce2(`${label} selected`, { politeness: "polite" });
1936
+ }
1937
+ if (!isControlled) {
1938
+ setUncontrolledValue(newValue);
1939
+ }
1940
+ onValueChange?.(newValue);
1941
+ },
1942
+ [disabled, multiple, selectedValues, isControlled, onValueChange, announce2]
1943
+ );
1944
+ const navigateOption = useCallback(
1945
+ (direction) => {
1946
+ if (enabledValues.length === 0) return;
1947
+ const currentIndex = focusedValue ? enabledValues.indexOf(focusedValue) : -1;
1948
+ let nextIndex;
1949
+ switch (direction) {
1950
+ case "next":
1951
+ nextIndex = currentIndex < 0 ? 0 : Math.min(currentIndex + 1, enabledValues.length - 1);
1952
+ break;
1953
+ case "prev":
1954
+ nextIndex = currentIndex < 0 ? enabledValues.length - 1 : Math.max(currentIndex - 1, 0);
1955
+ break;
1956
+ case "first":
1957
+ nextIndex = 0;
1958
+ break;
1959
+ case "last":
1960
+ nextIndex = enabledValues.length - 1;
1961
+ break;
1962
+ }
1963
+ const nextValue = enabledValues[nextIndex];
1964
+ if (nextValue !== void 0) {
1965
+ setFocusedValue(nextValue);
1966
+ if (!multiple) {
1967
+ const info = optionsRef.current.get(nextValue);
1968
+ const label = info?.label || nextValue;
1969
+ let newVal = nextValue;
1970
+ if (!isControlled) {
1971
+ setUncontrolledValue(newVal);
1972
+ }
1973
+ onValueChange?.(newVal);
1974
+ announce2(`${label} selected`, { politeness: "polite" });
1975
+ } else {
1976
+ const info = optionsRef.current.get(nextValue);
1977
+ const label = info?.label || nextValue;
1978
+ const sel = selectedValues.has(nextValue);
1979
+ announce2(`${label}${sel ? ", selected" : ""}`, {
1980
+ politeness: "polite"
1981
+ });
1982
+ }
1983
+ }
1984
+ },
1985
+ [
1986
+ enabledValues,
1987
+ focusedValue,
1988
+ multiple,
1989
+ isControlled,
1990
+ onValueChange,
1991
+ announce2,
1992
+ selectedValues
1993
+ ]
1994
+ );
1995
+ const toggleFocusedOption = useCallback(() => {
1996
+ if (focusedValue && multiple) {
1997
+ handleSelect(focusedValue);
1998
+ }
1999
+ }, [focusedValue, multiple, handleSelect]);
2000
+ const selectRangeToFirst = useCallback(() => {
2001
+ if (!multiple || !focusedValue) return;
2002
+ const currentIdx = enabledValues.indexOf(focusedValue);
2003
+ if (currentIdx < 0) return;
2004
+ const toSelect = enabledValues.slice(0, currentIdx + 1);
2005
+ const newSet = new Set(selectedValues);
2006
+ toSelect.forEach((v) => newSet.add(v));
2007
+ const newValue = Array.from(newSet);
2008
+ if (!isControlled) {
2009
+ setUncontrolledValue(newValue);
2010
+ }
2011
+ onValueChange?.(newValue);
2012
+ setFocusedValue(enabledValues[0] ?? null);
2013
+ announce2(`${toSelect.length} items selected`, { politeness: "polite" });
2014
+ }, [
2015
+ multiple,
2016
+ focusedValue,
2017
+ enabledValues,
2018
+ selectedValues,
2019
+ isControlled,
2020
+ onValueChange,
2021
+ announce2
2022
+ ]);
2023
+ const selectRangeToLast = useCallback(() => {
2024
+ if (!multiple || !focusedValue) return;
2025
+ const currentIdx = enabledValues.indexOf(focusedValue);
2026
+ if (currentIdx < 0) return;
2027
+ const toSelect = enabledValues.slice(currentIdx);
2028
+ const newSet = new Set(selectedValues);
2029
+ toSelect.forEach((v) => newSet.add(v));
2030
+ const newValue = Array.from(newSet);
2031
+ if (!isControlled) {
2032
+ setUncontrolledValue(newValue);
2033
+ }
2034
+ onValueChange?.(newValue);
2035
+ setFocusedValue(enabledValues[enabledValues.length - 1] ?? null);
2036
+ announce2(`${toSelect.length} items selected`, { politeness: "polite" });
2037
+ }, [
2038
+ multiple,
2039
+ focusedValue,
2040
+ enabledValues,
2041
+ selectedValues,
2042
+ isControlled,
2043
+ onValueChange,
2044
+ announce2
2045
+ ]);
2046
+ const toggleSelectAll = useCallback(() => {
2047
+ if (!multiple) return;
2048
+ const allSelected = enabledValues.every((v) => selectedValues.has(v));
2049
+ let newValue;
2050
+ if (allSelected) {
2051
+ newValue = [];
2052
+ announce2("All deselected", { politeness: "polite" });
2053
+ } else {
2054
+ newValue = [...enabledValues];
2055
+ announce2("All selected", { politeness: "polite" });
2056
+ }
2057
+ if (!isControlled) {
2058
+ setUncontrolledValue(newValue);
2059
+ }
2060
+ onValueChange?.(newValue);
2061
+ }, [
2062
+ multiple,
2063
+ enabledValues,
2064
+ selectedValues,
2065
+ isControlled,
2066
+ onValueChange,
2067
+ announce2
2068
+ ]);
2069
+ const keyboardHandlers = useMemo(() => {
2070
+ const handlers = {
2071
+ ArrowDown: () => navigateOption("next"),
2072
+ ArrowUp: () => navigateOption("prev"),
2073
+ Home: () => navigateOption("first"),
2074
+ End: () => navigateOption("last")
2075
+ };
2076
+ if (multiple) {
2077
+ handlers[" "] = () => toggleFocusedOption();
2078
+ handlers["Shift+ArrowDown"] = () => {
2079
+ navigateOption("next");
2080
+ setTimeout(() => toggleFocusedOption(), 0);
2081
+ };
2082
+ handlers["Shift+ArrowUp"] = () => {
2083
+ navigateOption("prev");
2084
+ setTimeout(() => toggleFocusedOption(), 0);
2085
+ };
2086
+ handlers["Ctrl+Shift+Home"] = () => selectRangeToFirst();
2087
+ handlers["Ctrl+Shift+End"] = () => selectRangeToLast();
2088
+ handlers["Ctrl+a"] = () => toggleSelectAll();
2089
+ handlers["Meta+a"] = () => toggleSelectAll();
2090
+ }
2091
+ return handlers;
2092
+ }, [
2093
+ multiple,
2094
+ navigateOption,
2095
+ toggleFocusedOption,
2096
+ selectRangeToFirst,
2097
+ selectRangeToLast,
2098
+ toggleSelectAll
2099
+ ]);
2100
+ const keyboardProps = useKeyboard(keyboardHandlers, {
2101
+ preventDefault: true,
2102
+ stopPropagation: true,
2103
+ disabled
2104
+ });
2105
+ const optionLabels = useMemo(() => {
2106
+ return Array.from(optionsRef.current.values()).map((info) => info.label);
2107
+ }, [optionsVersion]);
2108
+ const typeAheadProps = useTypeAhead(
2109
+ optionLabels,
2110
+ (matchedLabel) => {
2111
+ for (const [value, info] of optionsRef.current.entries()) {
2112
+ if (info.label === matchedLabel && !info.disabled) {
2113
+ setFocusedValue(value);
2114
+ if (!multiple) {
2115
+ let newVal = value;
2116
+ if (!isControlled) {
2117
+ setUncontrolledValue(newVal);
2118
+ }
2119
+ onValueChange?.(newVal);
2120
+ announce2(`${matchedLabel} selected`, { politeness: "polite" });
2121
+ }
2122
+ break;
2123
+ }
2124
+ }
2125
+ },
2126
+ { disabled }
2127
+ );
2128
+ const handleKeyDown = useCallback(
2129
+ (event) => {
2130
+ keyboardProps.onKeyDown(event);
2131
+ if (!event.defaultPrevented) {
2132
+ typeAheadProps.onKeyDown(event);
2133
+ }
2134
+ },
2135
+ [keyboardProps, typeAheadProps]
2136
+ );
2137
+ const handleFocus = useCallback(() => {
2138
+ if (focusedValue) return;
2139
+ if (!multiple && currentValue && typeof currentValue === "string") {
2140
+ setFocusedValue(currentValue);
2141
+ return;
2142
+ }
2143
+ if (multiple && Array.isArray(currentValue) && currentValue.length > 0) {
2144
+ setFocusedValue(currentValue[0] ?? null);
2145
+ return;
2146
+ }
2147
+ if (enabledValues.length > 0) {
2148
+ setFocusedValue(enabledValues[0] ?? null);
2149
+ }
2150
+ }, [focusedValue, multiple, currentValue, enabledValues]);
2151
+ const handleBlur = useCallback(() => {
2152
+ }, []);
2153
+ const listboxRef = useRef(null);
2154
+ useEffect(() => {
2155
+ if (focusedValue && listboxRef.current) {
2156
+ const el = listboxRef.current.querySelector(
2157
+ `[data-value="${CSS.escape(focusedValue)}"]`
2158
+ );
2159
+ el?.scrollIntoView({ block: "nearest" });
2160
+ }
2161
+ }, [focusedValue]);
2162
+ const contextValue = useMemo(
2163
+ () => ({
2164
+ multiple,
2165
+ disabled,
2166
+ discoverable,
2167
+ unstyled,
2168
+ orientation,
2169
+ selectedValues,
2170
+ focusedValue,
2171
+ onSelect: handleSelect,
2172
+ onFocusOption: setFocusedValue,
2173
+ registerOption,
2174
+ unregisterOption,
2175
+ updateOptionDisabled,
2176
+ isSelected,
2177
+ listboxId
2178
+ }),
2179
+ [
2180
+ multiple,
2181
+ disabled,
2182
+ discoverable,
2183
+ unstyled,
2184
+ orientation,
2185
+ selectedValues,
2186
+ focusedValue,
2187
+ handleSelect,
2188
+ registerOption,
2189
+ unregisterOption,
2190
+ updateOptionDisabled,
2191
+ isSelected,
2192
+ listboxId
2193
+ ]
2194
+ );
2195
+ const mergedRef = useCallback(
2196
+ (node) => {
2197
+ listboxRef.current = node;
2198
+ if (typeof ref === "function") {
2199
+ ref(node);
2200
+ } else if (ref) {
2201
+ ref.current = node;
2202
+ }
2203
+ },
2204
+ [ref]
2205
+ );
2206
+ const activeDescendantId = focusedValue ? `${listboxId}-option-${focusedValue}` : void 0;
2207
+ return /* @__PURE__ */ jsx(ListboxContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
2208
+ "div",
2209
+ {
2210
+ ref: mergedRef,
2211
+ role: "listbox",
2212
+ "aria-label": ariaLabel,
2213
+ "aria-labelledby": ariaLabelledBy,
2214
+ "aria-orientation": orientation,
2215
+ "aria-multiselectable": multiple || void 0,
2216
+ "aria-disabled": disabled || void 0,
2217
+ "aria-activedescendant": activeDescendantId,
2218
+ tabIndex: disabled && !discoverable ? -1 : 0,
2219
+ className: `compa11y-listbox ${className}`.trim(),
2220
+ "data-compa11y-listbox": "",
2221
+ "data-orientation": orientation,
2222
+ "data-disabled": disabled ? "true" : void 0,
2223
+ "data-multiple": multiple ? "true" : void 0,
2224
+ onKeyDown: handleKeyDown,
2225
+ onFocus: handleFocus,
2226
+ onBlur: handleBlur,
2227
+ style: unstyled ? {} : {
2228
+ width: "var(--compa11y-listbox-width, 250px)",
2229
+ maxHeight: "var(--compa11y-listbox-max-height, 300px)",
2230
+ overflowY: "auto",
2231
+ border: "var(--compa11y-listbox-border, 1px solid #e0e0e0)",
2232
+ borderRadius: "var(--compa11y-listbox-radius, 4px)",
2233
+ background: "var(--compa11y-listbox-bg, white)",
2234
+ padding: "var(--compa11y-listbox-padding, 0.25rem 0)",
2235
+ ...disabled ? {
2236
+ opacity: "var(--compa11y-listbox-disabled-opacity, 0.5)",
2237
+ cursor: "not-allowed"
2238
+ } : {}
2239
+ },
2240
+ children
2241
+ }
2242
+ ) });
2243
+ }
2244
+ );
2245
+ Listbox.displayName = "Listbox";
2246
+ var ListboxOption = forwardRef(
2247
+ function ListboxOption2({
2248
+ value,
2249
+ disabled: localDisabled = false,
2250
+ discoverable: _,
2251
+ unstyled: localUnstyled,
2252
+ className = "",
2253
+ children,
2254
+ "aria-label": ariaLabel
2255
+ }, ref) {
2256
+ const context = useListboxContext();
2257
+ const { groupDisabled } = useContext(ListboxGroupContext);
2258
+ const disabled = context.disabled || groupDisabled || localDisabled;
2259
+ const unstyled = localUnstyled ?? context.unstyled;
2260
+ const selected = context.isSelected(value);
2261
+ const focused = context.focusedValue === value;
2262
+ const optionId = `${context.listboxId}-option-${value}`;
2263
+ const labelRef = useRef("");
2264
+ const optionRef = useRef(null);
2265
+ const mergedRef = useCallback(
2266
+ (node) => {
2267
+ optionRef.current = node;
2268
+ if (typeof ref === "function") {
2269
+ ref(node);
2270
+ } else if (ref) {
2271
+ ref.current = node;
2272
+ }
2273
+ },
2274
+ [ref]
2275
+ );
2276
+ useEffect(() => {
2277
+ const label = typeof children === "string" ? children : optionRef.current?.textContent?.trim() || value;
2278
+ labelRef.current = label;
2279
+ context.registerOption(value, disabled, label);
2280
+ return () => context.unregisterOption(value);
2281
+ }, [value]);
2282
+ useEffect(() => {
2283
+ context.updateOptionDisabled(value, disabled);
2284
+ }, [disabled, value]);
2285
+ const handleClick = useCallback(
2286
+ (event) => {
2287
+ if (disabled) {
2288
+ event.preventDefault();
2289
+ return;
2290
+ }
2291
+ context.onFocusOption(value);
2292
+ context.onSelect(value);
2293
+ },
2294
+ [disabled, context, value]
2295
+ );
2296
+ return /* @__PURE__ */ jsxs(
2297
+ "div",
2298
+ {
2299
+ ref: mergedRef,
2300
+ id: optionId,
2301
+ role: "option",
2302
+ "aria-selected": selected,
2303
+ "aria-disabled": disabled || void 0,
2304
+ "aria-label": ariaLabel,
2305
+ className: `compa11y-listbox-option ${className}`.trim(),
2306
+ "data-compa11y-listbox-option": "",
2307
+ "data-value": value,
2308
+ "data-selected": selected ? "true" : "false",
2309
+ "data-focused": focused ? "true" : "false",
2310
+ "data-disabled": disabled ? "true" : void 0,
2311
+ onClick: handleClick,
2312
+ style: unstyled ? {
2313
+ cursor: disabled ? "not-allowed" : "pointer"
2314
+ } : {
2315
+ display: "flex",
2316
+ alignItems: "center",
2317
+ justifyContent: "space-between",
2318
+ padding: "var(--compa11y-listbox-option-padding, 0.5rem 0.75rem)",
2319
+ cursor: disabled ? "not-allowed" : "pointer",
2320
+ userSelect: "none",
2321
+ transition: "background 0.1s ease",
2322
+ background: focused ? selected ? "var(--compa11y-listbox-option-selected-hover-bg, #cce0ff)" : "var(--compa11y-listbox-option-hover-bg, #f5f5f5)" : selected ? "var(--compa11y-listbox-option-selected-bg, #e6f0ff)" : "transparent",
2323
+ fontWeight: selected ? 500 : "normal",
2324
+ opacity: disabled ? 0.5 : 1
2325
+ },
2326
+ children: [
2327
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 }, children }),
2328
+ selected && !unstyled && /* @__PURE__ */ jsx(
2329
+ "span",
2330
+ {
2331
+ "aria-hidden": "true",
2332
+ style: {
2333
+ fontSize: "0.875rem",
2334
+ color: "var(--compa11y-listbox-check-color, #0066cc)",
2335
+ marginLeft: "0.5rem"
2336
+ },
2337
+ children: "\u2713"
2338
+ }
2339
+ )
2340
+ ]
2341
+ }
2342
+ );
2343
+ }
2344
+ );
2345
+ ListboxOption.displayName = "ListboxOption";
2346
+ var ListboxGroup = forwardRef(
2347
+ function ListboxGroup2({
2348
+ label,
2349
+ disabled: groupDisabled = false,
2350
+ unstyled: localUnstyled,
2351
+ className = "",
2352
+ children
2353
+ }, ref) {
2354
+ const context = useListboxContext();
2355
+ const unstyled = localUnstyled ?? context.unstyled;
2356
+ const labelId = useId("listbox-group");
2357
+ const groupContextValue = useMemo(
2358
+ () => ({ groupDisabled: context.disabled || groupDisabled }),
2359
+ [context.disabled, groupDisabled]
2360
+ );
2361
+ return /* @__PURE__ */ jsx(ListboxGroupContext.Provider, { value: groupContextValue, children: /* @__PURE__ */ jsxs(
2362
+ "div",
2363
+ {
2364
+ ref,
2365
+ role: "group",
2366
+ "aria-labelledby": labelId,
2367
+ "aria-disabled": groupDisabled || void 0,
2368
+ className: `compa11y-listbox-group ${className}`.trim(),
2369
+ "data-compa11y-listbox-group": "",
2370
+ "data-disabled": groupDisabled ? "true" : void 0,
2371
+ style: unstyled ? {} : {
2372
+ opacity: groupDisabled ? 0.5 : 1
2373
+ },
2374
+ children: [
2375
+ /* @__PURE__ */ jsx(
2376
+ "div",
2377
+ {
2378
+ id: labelId,
2379
+ role: "presentation",
2380
+ style: unstyled ? {} : {
2381
+ padding: "var(--compa11y-listbox-group-label-padding, 0.5rem 0.75rem 0.25rem)",
2382
+ fontSize: "var(--compa11y-listbox-group-label-size, 0.75rem)",
2383
+ fontWeight: 600,
2384
+ color: "var(--compa11y-listbox-group-label-color, #666)",
2385
+ textTransform: "uppercase",
2386
+ letterSpacing: "0.05em"
2387
+ },
2388
+ children: label
2389
+ }
2390
+ ),
2391
+ children
2392
+ ]
2393
+ }
2394
+ ) });
2395
+ }
2396
+ );
2397
+ ListboxGroup.displayName = "ListboxGroup";
2398
+ var ListboxCompound = Object.assign(Listbox, {
2399
+ Option: ListboxOption,
2400
+ Group: ListboxGroup
2401
+ });
2402
+ var InputContext = createContext(null);
2403
+ function useInputContext() {
2404
+ const ctx = useContext(InputContext);
2405
+ if (!ctx) {
2406
+ throw new Error(
2407
+ "[Compa11y Input]: Input sub-components (Input.Label, Input.Field, etc.) must be used within <Input>."
2408
+ );
2409
+ }
2410
+ return ctx;
2411
+ }
2412
+ var InputLabel = forwardRef(
2413
+ function InputLabel2({ children, className }, ref) {
2414
+ const ctx = useInputContext();
2415
+ return /* @__PURE__ */ jsxs(
2416
+ "label",
2417
+ {
2418
+ ref,
2419
+ id: ctx.labelId,
2420
+ htmlFor: ctx.fieldId,
2421
+ "data-compa11y-input-label": "",
2422
+ className,
2423
+ style: ctx.unstyled ? {} : {
2424
+ display: "block",
2425
+ color: ctx.disabled ? "var(--compa11y-input-disabled-color, #999)" : "var(--compa11y-input-label-color, inherit)",
2426
+ fontSize: "var(--compa11y-input-label-size, 0.875rem)",
2427
+ fontWeight: "var(--compa11y-input-label-weight, 500)"
2428
+ },
2429
+ children: [
2430
+ children,
2431
+ ctx.required && /* @__PURE__ */ jsx(
2432
+ "span",
2433
+ {
2434
+ "data-compa11y-input-required": "",
2435
+ "aria-hidden": "true",
2436
+ style: ctx.unstyled ? {} : {
2437
+ color: "var(--compa11y-input-required-color, #ef4444)",
2438
+ marginLeft: "0.125rem"
2439
+ },
2440
+ children: "*"
2441
+ }
2442
+ )
2443
+ ]
2444
+ }
2445
+ );
2446
+ }
2447
+ );
2448
+ InputLabel.displayName = "InputLabel";
2449
+ var InputField = forwardRef(
2450
+ function InputField2({
2451
+ type = "text",
2452
+ onFocus: providedOnFocus,
2453
+ onBlur: providedOnBlur,
2454
+ className,
2455
+ style,
2456
+ ...rest
2457
+ }, ref) {
2458
+ const ctx = useInputContext();
2459
+ const inputRef = useRef(null);
2460
+ const mergedRef = useCallback(
2461
+ (node) => {
2462
+ inputRef.current = node;
2463
+ if (typeof ref === "function") {
2464
+ ref(node);
2465
+ } else if (ref) {
2466
+ ref.current = node;
2467
+ }
2468
+ },
2469
+ [ref]
2470
+ );
2471
+ const handleChange = useCallback(
2472
+ (event) => {
2473
+ ctx.setValue(event.target.value);
2474
+ },
2475
+ [ctx.setValue]
2476
+ );
2477
+ const handleFocus = useCallback(
2478
+ (event) => {
2479
+ ctx.focusProps.onFocus();
2480
+ providedOnFocus?.(event);
2481
+ },
2482
+ [ctx.focusProps, providedOnFocus]
2483
+ );
2484
+ const handleBlur = useCallback(
2485
+ (event) => {
2486
+ ctx.focusProps.onBlur();
2487
+ providedOnBlur?.(event);
2488
+ },
2489
+ [ctx.focusProps, providedOnBlur]
2490
+ );
2491
+ const describedByParts = [];
2492
+ describedByParts.push(ctx.hintId);
2493
+ if (ctx.hasError) {
2494
+ describedByParts.push(ctx.errorId);
2495
+ }
2496
+ return /* @__PURE__ */ jsx(
2497
+ "input",
2498
+ {
2499
+ ref: mergedRef,
2500
+ id: ctx.fieldId,
2501
+ type,
2502
+ value: ctx.value,
2503
+ onChange: handleChange,
2504
+ onFocus: handleFocus,
2505
+ onBlur: handleBlur,
2506
+ disabled: ctx.disabled,
2507
+ readOnly: ctx.readOnly,
2508
+ "aria-describedby": describedByParts.join(" ") || void 0,
2509
+ "aria-invalid": ctx.hasError ? "true" : void 0,
2510
+ "aria-required": ctx.required ? "true" : void 0,
2511
+ "data-compa11y-input-field": "",
2512
+ className,
2513
+ style: ctx.unstyled ? style : {
2514
+ width: "100%",
2515
+ padding: "var(--compa11y-input-padding, 0.5rem 0.75rem)",
2516
+ border: ctx.hasError ? "1px solid var(--compa11y-input-border-error, #ef4444)" : "var(--compa11y-input-border, 1px solid #ccc)",
2517
+ borderRadius: "var(--compa11y-input-radius, 4px)",
2518
+ fontSize: "var(--compa11y-input-font-size, 0.875rem)",
2519
+ fontFamily: "inherit",
2520
+ background: ctx.disabled ? "var(--compa11y-input-disabled-bg, #f5f5f5)" : ctx.readOnly ? "var(--compa11y-input-readonly-bg, #f9f9f9)" : "var(--compa11y-input-bg, white)",
2521
+ color: "inherit",
2522
+ cursor: ctx.disabled ? "not-allowed" : void 0,
2523
+ opacity: ctx.disabled ? "var(--compa11y-input-disabled-opacity, 0.7)" : void 0,
2524
+ ...ctx.isFocusVisible && !ctx.disabled ? {
2525
+ outline: ctx.hasError ? "2px solid var(--compa11y-input-border-error, #ef4444)" : "2px solid var(--compa11y-focus-color, #0066cc)",
2526
+ outlineOffset: "-1px",
2527
+ borderColor: ctx.hasError ? "var(--compa11y-input-border-error, #ef4444)" : "var(--compa11y-input-border-focus, #0066cc)"
2528
+ } : {},
2529
+ ...style
2530
+ },
2531
+ ...rest
2532
+ }
2533
+ );
2534
+ }
2535
+ );
2536
+ InputField.displayName = "InputField";
2537
+ var InputHint = forwardRef(
2538
+ function InputHint2({ children, className }, ref) {
2539
+ const ctx = useInputContext();
2540
+ return /* @__PURE__ */ jsx(
2541
+ "div",
2542
+ {
2543
+ ref,
2544
+ id: ctx.hintId,
2545
+ "data-compa11y-input-hint": "",
2546
+ className,
2547
+ style: ctx.unstyled ? {} : {
2548
+ color: "var(--compa11y-input-hint-color, #666)",
2549
+ fontSize: "var(--compa11y-input-hint-size, 0.8125rem)"
2550
+ },
2551
+ children
2552
+ }
2553
+ );
2554
+ }
2555
+ );
2556
+ InputHint.displayName = "InputHint";
2557
+ var InputError = forwardRef(
2558
+ function InputError2({ children, className }, ref) {
2559
+ const ctx = useInputContext();
2560
+ if (!children) return null;
2561
+ return /* @__PURE__ */ jsx(
2562
+ "div",
2563
+ {
2564
+ ref,
2565
+ id: ctx.errorId,
2566
+ role: "alert",
2567
+ "data-compa11y-input-error": "",
2568
+ className,
2569
+ style: ctx.unstyled ? {} : {
2570
+ color: "var(--compa11y-input-error-color, #ef4444)",
2571
+ fontSize: "var(--compa11y-input-error-size, 0.8125rem)"
2572
+ },
2573
+ children
2574
+ }
2575
+ );
2576
+ }
2577
+ );
2578
+ InputError.displayName = "InputError";
2579
+ var Input = forwardRef(
2580
+ function Input2({
2581
+ value: controlledValue,
2582
+ defaultValue = "",
2583
+ onValueChange,
2584
+ type = "text",
2585
+ label,
2586
+ hint,
2587
+ error,
2588
+ required = false,
2589
+ disabled = false,
2590
+ readOnly = false,
2591
+ "aria-label": ariaLabel,
2592
+ "aria-labelledby": ariaLabelledBy,
2593
+ placeholder,
2594
+ name,
2595
+ autoComplete,
2596
+ maxLength,
2597
+ minLength,
2598
+ pattern,
2599
+ inputMode,
2600
+ id: providedId,
2601
+ unstyled = false,
2602
+ className,
2603
+ onFocus: providedOnFocus,
2604
+ onBlur: providedOnBlur,
2605
+ children
2606
+ }, ref) {
2607
+ const generatedId = useId("input");
2608
+ const baseId = providedId || generatedId;
2609
+ const fieldId = `${baseId}-field`;
2610
+ const labelId = `${baseId}-label`;
2611
+ const hintId = `${baseId}-hint`;
2612
+ const errorId = `${baseId}-error`;
2613
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
2614
+ const isControlled = controlledValue !== void 0;
2615
+ const currentValue = isControlled ? controlledValue : uncontrolledValue;
2616
+ const setValue = useCallback(
2617
+ (newValue) => {
2618
+ if (!isControlled) {
2619
+ setUncontrolledValue(newValue);
2620
+ }
2621
+ onValueChange?.(newValue);
2622
+ },
2623
+ [isControlled, onValueChange]
2624
+ );
2625
+ const { isFocusVisible, focusProps } = useFocusVisible();
2626
+ useEffect(() => {
2627
+ if (process.env.NODE_ENV !== "production") {
2628
+ const isCompound2 = Children.count(children) > 0;
2629
+ if (!isCompound2 && !label && !ariaLabel && !ariaLabelledBy) {
2630
+ console.warn(
2631
+ "[Compa11y Input]: Input has no accessible label. Screen readers need this to identify the input. Use label prop, aria-label, or aria-labelledby."
2632
+ );
2633
+ }
2634
+ }
2635
+ }, [children, label, ariaLabel, ariaLabelledBy]);
2636
+ const hasError = Boolean(error);
2637
+ const isCompound = Children.count(children) > 0;
2638
+ const contextValue = {
2639
+ fieldId,
2640
+ labelId,
2641
+ hintId,
2642
+ errorId,
2643
+ value: currentValue,
2644
+ setValue,
2645
+ hasError,
2646
+ disabled,
2647
+ readOnly,
2648
+ required,
2649
+ isFocusVisible,
2650
+ focusProps,
2651
+ unstyled
2652
+ };
2653
+ const dataAttrs = {
2654
+ "data-compa11y-input": "",
2655
+ "data-error": hasError ? "true" : "false",
2656
+ "data-disabled": disabled ? "true" : "false",
2657
+ "data-required": required ? "true" : "false",
2658
+ "data-readonly": readOnly ? "true" : "false"
2659
+ };
2660
+ const wrapperStyle = unstyled ? {} : {
2661
+ display: "flex",
2662
+ flexDirection: "column",
2663
+ gap: "0.25rem"
2664
+ };
2665
+ if (isCompound) {
2666
+ return /* @__PURE__ */ jsx(InputContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { ...dataAttrs, className, style: wrapperStyle, children }) });
2667
+ }
2668
+ const inputRef = useRef(null);
2669
+ const mergedRef = useCallback(
2670
+ (node) => {
2671
+ inputRef.current = node;
2672
+ if (typeof ref === "function") {
2673
+ ref(node);
2674
+ } else if (ref) {
2675
+ ref.current = node;
2676
+ }
2677
+ },
2678
+ [ref]
2679
+ );
2680
+ const handleChange = useCallback(
2681
+ (event) => {
2682
+ setValue(event.target.value);
2683
+ },
2684
+ [setValue]
2685
+ );
2686
+ const handleFocus = useCallback(
2687
+ (event) => {
2688
+ focusProps.onFocus();
2689
+ providedOnFocus?.(event);
2690
+ },
2691
+ [focusProps, providedOnFocus]
2692
+ );
2693
+ const handleBlur = useCallback(
2694
+ (event) => {
2695
+ focusProps.onBlur();
2696
+ providedOnBlur?.(event);
2697
+ },
2698
+ [focusProps, providedOnBlur]
2699
+ );
2700
+ const describedByParts = [];
2701
+ if (hint) describedByParts.push(hintId);
2702
+ if (hasError) describedByParts.push(errorId);
2703
+ const ariaDescribedBy = describedByParts.length > 0 ? describedByParts.join(" ") : void 0;
2704
+ return /* @__PURE__ */ jsxs("div", { ...dataAttrs, className, style: wrapperStyle, children: [
2705
+ label && /* @__PURE__ */ jsxs(
2706
+ "label",
2707
+ {
2708
+ id: labelId,
2709
+ htmlFor: fieldId,
2710
+ "data-compa11y-input-label": "",
2711
+ style: unstyled ? {} : {
2712
+ display: "block",
2713
+ color: disabled ? "var(--compa11y-input-disabled-color, #999)" : "var(--compa11y-input-label-color, inherit)",
2714
+ fontSize: "var(--compa11y-input-label-size, 0.875rem)",
2715
+ fontWeight: "var(--compa11y-input-label-weight, 500)"
2716
+ },
2717
+ children: [
2718
+ label,
2719
+ required && /* @__PURE__ */ jsx(
2720
+ "span",
2721
+ {
2722
+ "data-compa11y-input-required": "",
2723
+ "aria-hidden": "true",
2724
+ style: unstyled ? {} : {
2725
+ color: "var(--compa11y-input-required-color, #ef4444)",
2726
+ marginLeft: "0.125rem"
2727
+ },
2728
+ children: "*"
2729
+ }
2730
+ )
2731
+ ]
2732
+ }
2733
+ ),
2734
+ /* @__PURE__ */ jsx(
2735
+ "input",
2736
+ {
2737
+ ref: mergedRef,
2738
+ id: fieldId,
2739
+ type,
2740
+ value: currentValue,
2741
+ onChange: handleChange,
2742
+ onFocus: handleFocus,
2743
+ onBlur: handleBlur,
2744
+ disabled,
2745
+ readOnly,
2746
+ required,
2747
+ placeholder,
2748
+ name,
2749
+ autoComplete,
2750
+ maxLength,
2751
+ minLength,
2752
+ pattern,
2753
+ inputMode,
2754
+ "aria-label": !label ? ariaLabel : void 0,
2755
+ "aria-labelledby": !label && ariaLabelledBy ? ariaLabelledBy : label ? labelId : void 0,
2756
+ "aria-describedby": ariaDescribedBy,
2757
+ "aria-invalid": hasError ? "true" : void 0,
2758
+ "aria-required": required ? "true" : void 0,
2759
+ "data-compa11y-input-field": "",
2760
+ style: unstyled ? {} : {
2761
+ width: "100%",
2762
+ padding: "var(--compa11y-input-padding, 0.5rem 0.75rem)",
2763
+ border: hasError ? "1px solid var(--compa11y-input-border-error, #ef4444)" : "var(--compa11y-input-border, 1px solid #ccc)",
2764
+ borderRadius: "var(--compa11y-input-radius, 4px)",
2765
+ fontSize: "var(--compa11y-input-font-size, 0.875rem)",
2766
+ fontFamily: "inherit",
2767
+ background: disabled ? "var(--compa11y-input-disabled-bg, #f5f5f5)" : readOnly ? "var(--compa11y-input-readonly-bg, #f9f9f9)" : "var(--compa11y-input-bg, white)",
2768
+ color: "inherit",
2769
+ cursor: disabled ? "not-allowed" : void 0,
2770
+ opacity: disabled ? "var(--compa11y-input-disabled-opacity, 0.7)" : void 0,
2771
+ ...isFocusVisible && !disabled ? {
2772
+ outline: hasError ? "2px solid var(--compa11y-input-border-error, #ef4444)" : "2px solid var(--compa11y-focus-color, #0066cc)",
2773
+ outlineOffset: "-1px",
2774
+ borderColor: hasError ? "var(--compa11y-input-border-error, #ef4444)" : "var(--compa11y-input-border-focus, #0066cc)"
2775
+ } : {}
2776
+ }
2777
+ }
2778
+ ),
2779
+ hint && /* @__PURE__ */ jsx(
2780
+ "div",
2781
+ {
2782
+ id: hintId,
2783
+ "data-compa11y-input-hint": "",
2784
+ style: unstyled ? {} : {
2785
+ color: "var(--compa11y-input-hint-color, #666)",
2786
+ fontSize: "var(--compa11y-input-hint-size, 0.8125rem)"
2787
+ },
2788
+ children: hint
2789
+ }
2790
+ ),
2791
+ hasError && /* @__PURE__ */ jsx(
2792
+ "div",
2793
+ {
2794
+ id: errorId,
2795
+ role: "alert",
2796
+ "data-compa11y-input-error": "",
2797
+ style: unstyled ? {} : {
2798
+ color: "var(--compa11y-input-error-color, #ef4444)",
2799
+ fontSize: "var(--compa11y-input-error-size, 0.8125rem)"
2800
+ },
2801
+ children: error
2802
+ }
2803
+ )
2804
+ ] });
2805
+ }
2806
+ );
2807
+ Input.displayName = "Input";
2808
+ var InputCompound = Object.assign(Input, {
2809
+ Label: InputLabel,
2810
+ Field: InputField,
2811
+ Hint: InputHint,
2812
+ Error: InputError
2813
+ });
2814
+ var VARIANT_STYLES = {
2815
+ primary: {
2816
+ background: "var(--compa11y-button-primary-bg, #0066cc)",
2817
+ color: "var(--compa11y-button-primary-color, white)",
2818
+ border: "var(--compa11y-button-primary-border, 1px solid #0066cc)"
2819
+ },
2820
+ secondary: {
2821
+ background: "var(--compa11y-button-secondary-bg, white)",
2822
+ color: "var(--compa11y-button-secondary-color, #333)",
2823
+ border: "var(--compa11y-button-secondary-border, 1px solid #ccc)"
2824
+ },
2825
+ danger: {
2826
+ background: "var(--compa11y-button-danger-bg, #ef4444)",
2827
+ color: "var(--compa11y-button-danger-color, white)",
2828
+ border: "var(--compa11y-button-danger-border, 1px solid #ef4444)"
2829
+ },
2830
+ outline: {
2831
+ background: "var(--compa11y-button-outline-bg, transparent)",
2832
+ color: "var(--compa11y-button-outline-color, #0066cc)",
2833
+ border: "var(--compa11y-button-outline-border, 1px solid #0066cc)"
2834
+ },
2835
+ ghost: {
2836
+ background: "var(--compa11y-button-ghost-bg, transparent)",
2837
+ color: "var(--compa11y-button-ghost-color, #333)",
2838
+ border: "var(--compa11y-button-ghost-border, 1px solid transparent)"
2839
+ }
2840
+ };
2841
+ var SIZE_STYLES = {
2842
+ sm: {
2843
+ padding: "var(--compa11y-button-padding-sm, 0.25rem 0.5rem)",
2844
+ fontSize: "var(--compa11y-button-font-size-sm, 0.75rem)"
2845
+ },
2846
+ md: {
2847
+ padding: "var(--compa11y-button-padding-md, 0.5rem 1rem)",
2848
+ fontSize: "var(--compa11y-button-font-size-md, 0.875rem)"
2849
+ },
2850
+ lg: {
2851
+ padding: "var(--compa11y-button-padding-lg, 0.75rem 1.5rem)",
2852
+ fontSize: "var(--compa11y-button-font-size-lg, 1rem)"
2853
+ }
2854
+ };
2855
+ var Button = forwardRef(
2856
+ function Button2({
2857
+ variant = "secondary",
2858
+ size = "md",
2859
+ type = "button",
241
2860
  disabled = false,
2861
+ discoverable = false,
2862
+ loading = false,
242
2863
  unstyled = false,
243
- className,
244
- size = "md",
245
2864
  "aria-label": ariaLabel,
246
- "aria-labelledby": ariaLabelledby,
2865
+ className,
2866
+ style,
247
2867
  onClick,
248
- ...props
2868
+ onFocus: providedOnFocus,
2869
+ onBlur: providedOnBlur,
2870
+ children,
2871
+ ...rest
249
2872
  }, ref) {
250
- const id = useId("switch");
251
- const labelId = `${id}-label`;
252
- const { announce: announce2 } = useAnnouncer();
253
- const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
254
- const isControlled = controlledChecked !== void 0;
255
- const checked = isControlled ? controlledChecked : uncontrolledChecked;
2873
+ const generatedId = useId("button");
2874
+ const buttonRef = useRef(null);
2875
+ const mergedRef = useCallback(
2876
+ (node) => {
2877
+ buttonRef.current = node;
2878
+ if (typeof ref === "function") {
2879
+ ref(node);
2880
+ } else if (ref) {
2881
+ ref.current = node;
2882
+ }
2883
+ },
2884
+ [ref]
2885
+ );
2886
+ const { isFocusVisible, focusProps } = useFocusVisible();
256
2887
  useEffect(() => {
257
- if (!label && !ariaLabel && !ariaLabelledby) {
258
- warnings.warning(
259
- "Switch has no accessible label. Screen readers need this to identify the switch.",
260
- 'Add label="Description", aria-label="...", or aria-labelledby="..."'
261
- );
262
- }
263
- }, [label, ariaLabel, ariaLabelledby]);
264
- const toggleSwitch = useCallback(() => {
265
- if (disabled) return;
266
- const newChecked = !checked;
267
- if (!isControlled) {
268
- setUncontrolledChecked(newChecked);
2888
+ if (process.env.NODE_ENV !== "production") {
2889
+ if (!children && !ariaLabel && !rest["aria-labelledby"]) {
2890
+ console.warn(
2891
+ "[Compa11y Button]: Button has no accessible label. Screen readers need this to identify the button. Add text content, aria-label, or aria-labelledby."
2892
+ );
2893
+ }
269
2894
  }
270
- onCheckedChange?.(newChecked);
271
- const labelText = label || ariaLabel || "Switch";
272
- announce2(`${labelText} ${newChecked ? "on" : "off"}`);
273
- }, [
274
- checked,
275
- disabled,
276
- isControlled,
277
- onCheckedChange,
278
- label,
279
- ariaLabel,
280
- announce2
281
- ]);
2895
+ }, [children, ariaLabel, rest["aria-labelledby"]]);
2896
+ const useNativeDisabled = disabled && !discoverable;
2897
+ const isInteractionDisabled = disabled || loading;
282
2898
  const handleClick = useCallback(
283
2899
  (event) => {
284
- onClick?.(event);
285
- if (!event.defaultPrevented) {
286
- toggleSwitch();
2900
+ if (isInteractionDisabled) {
2901
+ event.preventDefault();
2902
+ return;
287
2903
  }
2904
+ onClick?.(event);
288
2905
  },
289
- [onClick, toggleSwitch]
2906
+ [isInteractionDisabled, onClick]
290
2907
  );
291
- const keyboardProps = useKeyboard(
292
- {
293
- " ": () => {
294
- toggleSwitch();
295
- },
296
- Enter: () => {
297
- toggleSwitch();
298
- }
2908
+ const handleFocus = useCallback(
2909
+ (event) => {
2910
+ focusProps.onFocus();
2911
+ providedOnFocus?.(event);
299
2912
  },
300
- { preventDefault: true }
2913
+ [focusProps, providedOnFocus]
301
2914
  );
302
- const handleKeyDown = (event) => {
303
- props.onKeyDown?.(event);
304
- if (!event.defaultPrevented) {
305
- keyboardProps.onKeyDown(event);
306
- }
307
- };
308
- const computedAriaLabel = ariaLabel;
309
- const computedAriaLabelledby = ariaLabelledby || (label ? labelId : void 0);
310
- const wrapperStructuralStyles = {
311
- display: "inline-flex",
312
- alignItems: "center"
313
- };
314
- const sizes = {
315
- sm: { width: 32, height: 18, thumb: 14, translate: 14 },
316
- md: { width: 44, height: 24, thumb: 20, translate: 20 },
317
- lg: { width: 56, height: 30, thumb: 26, translate: 26 }
2915
+ const handleBlur = useCallback(
2916
+ (event) => {
2917
+ focusProps.onBlur();
2918
+ providedOnBlur?.(event);
2919
+ },
2920
+ [focusProps, providedOnBlur]
2921
+ );
2922
+ const dataAttrs = {
2923
+ "data-compa11y-button": "",
2924
+ "data-variant": variant,
2925
+ "data-size": size,
2926
+ "data-disabled": disabled ? "true" : "false",
2927
+ "data-loading": loading ? "true" : "false"
318
2928
  };
319
- const sizeConfig = sizes[size];
320
- const trackStructuralStyles = {
321
- position: "relative",
2929
+ const baseStyle = unstyled ? {} : {
322
2930
  display: "inline-flex",
323
2931
  alignItems: "center",
324
- flexShrink: 0,
325
- width: sizeConfig.width,
326
- height: sizeConfig.height,
327
- border: "none",
328
- padding: 2,
329
- cursor: disabled ? "not-allowed" : "pointer"
330
- };
331
- const trackVisualStyles = unstyled ? {} : {
332
- backgroundColor: checked ? "#0066cc" : "#d1d5db",
333
- borderRadius: sizeConfig.height / 2,
334
- transition: "background-color 0.2s ease",
335
- opacity: disabled ? 0.5 : 1
336
- };
337
- const thumbStructuralStyles = {
338
- position: "absolute",
339
- left: 2,
340
- width: sizeConfig.thumb,
341
- height: sizeConfig.thumb,
342
- pointerEvents: "none",
343
- transform: checked ? `translateX(${sizeConfig.translate}px)` : "translateX(0)"
344
- };
345
- const thumbVisualStyles = unstyled ? {} : {
346
- backgroundColor: "white",
347
- borderRadius: "50%",
348
- boxShadow: "0 1px 3px rgba(0, 0, 0, 0.2)",
349
- transition: "transform 0.2s ease"
350
- };
351
- const labelStyles = unstyled ? {
352
- marginLeft: 8,
353
- userSelect: "none",
354
- cursor: disabled ? "not-allowed" : "pointer"
355
- } : {
356
- marginLeft: 8,
357
- userSelect: "none",
358
- cursor: disabled ? "not-allowed" : "pointer",
359
- opacity: disabled ? 0.5 : 1
2932
+ justifyContent: "center",
2933
+ gap: "0.5rem",
2934
+ borderRadius: "var(--compa11y-button-radius, 4px)",
2935
+ fontFamily: "inherit",
2936
+ fontWeight: "var(--compa11y-button-font-weight, 500)",
2937
+ lineHeight: "1.5",
2938
+ cursor: isInteractionDisabled ? "not-allowed" : "pointer",
2939
+ opacity: disabled ? "var(--compa11y-button-disabled-opacity, 0.5)" : void 0,
2940
+ transition: "background-color 0.15s ease, border-color 0.15s ease",
2941
+ ...VARIANT_STYLES[variant],
2942
+ ...SIZE_STYLES[size],
2943
+ ...isFocusVisible && !useNativeDisabled ? {
2944
+ outline: "2px solid var(--compa11y-focus-color, #0066cc)",
2945
+ outlineOffset: "2px"
2946
+ } : {}
360
2947
  };
361
2948
  return /* @__PURE__ */ jsxs(
362
- "div",
2949
+ "button",
363
2950
  {
364
- style: wrapperStructuralStyles,
365
- "data-compa11y-switch-wrapper": true,
366
- "data-size": size,
2951
+ ref: mergedRef,
2952
+ id: generatedId,
2953
+ type,
2954
+ disabled: useNativeDisabled,
2955
+ tabIndex: disabled && !discoverable ? void 0 : 0,
2956
+ "aria-disabled": isInteractionDisabled ? "true" : void 0,
2957
+ "aria-busy": loading ? "true" : void 0,
2958
+ "aria-label": ariaLabel,
2959
+ onClick: handleClick,
2960
+ onFocus: handleFocus,
2961
+ onBlur: handleBlur,
2962
+ className,
2963
+ style: { ...baseStyle, ...style },
2964
+ ...dataAttrs,
2965
+ ...rest,
367
2966
  children: [
368
- /* @__PURE__ */ jsx(
369
- "button",
2967
+ loading && /* @__PURE__ */ jsx(
2968
+ "span",
370
2969
  {
371
- ref,
372
- type: "button",
373
- role: "switch",
374
- "aria-checked": checked,
375
- "aria-label": computedAriaLabel,
376
- "aria-labelledby": computedAriaLabelledby,
377
- disabled,
378
- onClick: handleClick,
379
- onKeyDown: handleKeyDown,
380
- className,
381
- style: { ...trackStructuralStyles, ...trackVisualStyles },
382
- tabIndex: disabled ? -1 : 0,
383
- "data-compa11y-switch": true,
384
- "data-checked": checked,
385
- "data-disabled": disabled || void 0,
386
- "data-size": size,
387
- ...props,
388
- onFocus: (e) => {
389
- if (!unstyled) {
390
- e.currentTarget.style.outline = "2px solid #0066cc";
391
- e.currentTarget.style.outlineOffset = "2px";
392
- }
393
- props.onFocus?.(e);
394
- },
395
- onBlur: (e) => {
396
- if (!unstyled) {
397
- e.currentTarget.style.outline = "none";
398
- }
399
- props.onBlur?.(e);
400
- },
401
- children: /* @__PURE__ */ jsx(
402
- "span",
403
- {
404
- style: { ...thumbStructuralStyles, ...thumbVisualStyles },
405
- "data-compa11y-switch-thumb": true,
406
- "aria-hidden": "true"
407
- }
408
- )
2970
+ "data-compa11y-button-spinner": "",
2971
+ "aria-hidden": "true",
2972
+ style: unstyled ? {} : {
2973
+ display: "inline-block",
2974
+ width: "1em",
2975
+ height: "1em",
2976
+ border: "2px solid currentColor",
2977
+ borderRightColor: "transparent",
2978
+ borderRadius: "50%",
2979
+ animation: "compa11y-spin 0.6s linear infinite"
2980
+ }
409
2981
  }
410
2982
  ),
411
- label && /* @__PURE__ */ jsx(
412
- "label",
2983
+ children
2984
+ ]
2985
+ }
2986
+ );
2987
+ }
2988
+ );
2989
+ Button.displayName = "Button";
2990
+ var TextareaContext = createContext(null);
2991
+ function useTextareaContext() {
2992
+ const ctx = useContext(TextareaContext);
2993
+ if (!ctx) {
2994
+ throw new Error(
2995
+ "[Compa11y Textarea]: Textarea sub-components (Textarea.Label, Textarea.Field, etc.) must be used within <Textarea>."
2996
+ );
2997
+ }
2998
+ return ctx;
2999
+ }
3000
+ var TextareaLabel = forwardRef(
3001
+ function TextareaLabel2({ children, className }, ref) {
3002
+ const ctx = useTextareaContext();
3003
+ return /* @__PURE__ */ jsxs(
3004
+ "label",
3005
+ {
3006
+ ref,
3007
+ id: ctx.labelId,
3008
+ htmlFor: ctx.fieldId,
3009
+ "data-compa11y-textarea-label": "",
3010
+ className,
3011
+ style: ctx.unstyled ? {} : {
3012
+ display: "block",
3013
+ color: ctx.disabled ? "var(--compa11y-textarea-disabled-color, #999)" : "var(--compa11y-textarea-label-color, inherit)",
3014
+ fontSize: "var(--compa11y-textarea-label-size, 0.875rem)",
3015
+ fontWeight: "var(--compa11y-textarea-label-weight, 500)"
3016
+ },
3017
+ children: [
3018
+ children,
3019
+ ctx.required && /* @__PURE__ */ jsx(
3020
+ "span",
413
3021
  {
414
- id: labelId,
415
- onClick: disabled ? void 0 : () => toggleSwitch(),
416
- style: labelStyles,
417
- "data-compa11y-switch-label": true,
418
- children: label
3022
+ "data-compa11y-textarea-required": "",
3023
+ "aria-hidden": "true",
3024
+ style: ctx.unstyled ? {} : {
3025
+ color: "var(--compa11y-textarea-required-color, #ef4444)",
3026
+ marginLeft: "0.125rem"
3027
+ },
3028
+ children: "*"
419
3029
  }
420
3030
  )
421
3031
  ]
@@ -423,8 +3033,376 @@ var Switch = forwardRef(
423
3033
  );
424
3034
  }
425
3035
  );
426
- Switch.displayName = "Switch";
3036
+ TextareaLabel.displayName = "TextareaLabel";
3037
+ var TextareaField = forwardRef(
3038
+ function TextareaField2({
3039
+ onFocus: providedOnFocus,
3040
+ onBlur: providedOnBlur,
3041
+ className,
3042
+ style,
3043
+ rows: rowsOverride,
3044
+ ...rest
3045
+ }, ref) {
3046
+ const ctx = useTextareaContext();
3047
+ const textareaRef = useRef(null);
3048
+ const mergedRef = useCallback(
3049
+ (node) => {
3050
+ textareaRef.current = node;
3051
+ if (typeof ref === "function") {
3052
+ ref(node);
3053
+ } else if (ref) {
3054
+ ref.current = node;
3055
+ }
3056
+ },
3057
+ [ref]
3058
+ );
3059
+ const handleChange = useCallback(
3060
+ (event) => {
3061
+ ctx.setValue(event.target.value);
3062
+ },
3063
+ [ctx.setValue]
3064
+ );
3065
+ const handleFocus = useCallback(
3066
+ (event) => {
3067
+ ctx.focusProps.onFocus();
3068
+ providedOnFocus?.(event);
3069
+ },
3070
+ [ctx.focusProps, providedOnFocus]
3071
+ );
3072
+ const handleBlur = useCallback(
3073
+ (event) => {
3074
+ ctx.focusProps.onBlur();
3075
+ providedOnBlur?.(event);
3076
+ },
3077
+ [ctx.focusProps, providedOnBlur]
3078
+ );
3079
+ const describedByParts = [];
3080
+ describedByParts.push(ctx.hintId);
3081
+ if (ctx.hasError) {
3082
+ describedByParts.push(ctx.errorId);
3083
+ }
3084
+ return /* @__PURE__ */ jsx(
3085
+ "textarea",
3086
+ {
3087
+ ref: mergedRef,
3088
+ id: ctx.fieldId,
3089
+ rows: rowsOverride ?? ctx.rows,
3090
+ value: ctx.value,
3091
+ onChange: handleChange,
3092
+ onFocus: handleFocus,
3093
+ onBlur: handleBlur,
3094
+ disabled: ctx.disabled,
3095
+ readOnly: ctx.readOnly,
3096
+ "aria-describedby": describedByParts.join(" ") || void 0,
3097
+ "aria-invalid": ctx.hasError ? "true" : void 0,
3098
+ "aria-required": ctx.required ? "true" : void 0,
3099
+ "data-compa11y-textarea-field": "",
3100
+ className,
3101
+ style: ctx.unstyled ? style : {
3102
+ width: "100%",
3103
+ padding: "var(--compa11y-textarea-padding, 0.5rem 0.75rem)",
3104
+ border: ctx.hasError ? "1px solid var(--compa11y-textarea-border-error, #ef4444)" : "var(--compa11y-textarea-border, 1px solid #ccc)",
3105
+ borderRadius: "var(--compa11y-textarea-radius, 4px)",
3106
+ fontSize: "var(--compa11y-textarea-font-size, 0.875rem)",
3107
+ fontFamily: "inherit",
3108
+ lineHeight: "1.5",
3109
+ resize: ctx.resize,
3110
+ background: ctx.disabled ? "var(--compa11y-textarea-disabled-bg, #f5f5f5)" : ctx.readOnly ? "var(--compa11y-textarea-readonly-bg, #f9f9f9)" : "var(--compa11y-textarea-bg, white)",
3111
+ color: "inherit",
3112
+ cursor: ctx.disabled ? "not-allowed" : void 0,
3113
+ opacity: ctx.disabled ? "var(--compa11y-textarea-disabled-opacity, 0.7)" : void 0,
3114
+ ...ctx.isFocusVisible && !ctx.disabled ? {
3115
+ outline: ctx.hasError ? "2px solid var(--compa11y-textarea-border-error, #ef4444)" : "2px solid var(--compa11y-focus-color, #0066cc)",
3116
+ outlineOffset: "-1px",
3117
+ borderColor: ctx.hasError ? "var(--compa11y-textarea-border-error, #ef4444)" : "var(--compa11y-textarea-border-focus, #0066cc)"
3118
+ } : {},
3119
+ ...style
3120
+ },
3121
+ ...rest
3122
+ }
3123
+ );
3124
+ }
3125
+ );
3126
+ TextareaField.displayName = "TextareaField";
3127
+ var TextareaHint = forwardRef(
3128
+ function TextareaHint2({ children, className }, ref) {
3129
+ const ctx = useTextareaContext();
3130
+ return /* @__PURE__ */ jsx(
3131
+ "div",
3132
+ {
3133
+ ref,
3134
+ id: ctx.hintId,
3135
+ "data-compa11y-textarea-hint": "",
3136
+ className,
3137
+ style: ctx.unstyled ? {} : {
3138
+ color: "var(--compa11y-textarea-hint-color, #666)",
3139
+ fontSize: "var(--compa11y-textarea-hint-size, 0.8125rem)"
3140
+ },
3141
+ children
3142
+ }
3143
+ );
3144
+ }
3145
+ );
3146
+ TextareaHint.displayName = "TextareaHint";
3147
+ var TextareaError = forwardRef(
3148
+ function TextareaError2({ children, className }, ref) {
3149
+ const ctx = useTextareaContext();
3150
+ if (!children) return null;
3151
+ return /* @__PURE__ */ jsx(
3152
+ "div",
3153
+ {
3154
+ ref,
3155
+ id: ctx.errorId,
3156
+ role: "alert",
3157
+ "data-compa11y-textarea-error": "",
3158
+ className,
3159
+ style: ctx.unstyled ? {} : {
3160
+ color: "var(--compa11y-textarea-error-color, #ef4444)",
3161
+ fontSize: "var(--compa11y-textarea-error-size, 0.8125rem)"
3162
+ },
3163
+ children
3164
+ }
3165
+ );
3166
+ }
3167
+ );
3168
+ TextareaError.displayName = "TextareaError";
3169
+ var Textarea = forwardRef(
3170
+ function Textarea2({
3171
+ value: controlledValue,
3172
+ defaultValue = "",
3173
+ onValueChange,
3174
+ rows = 3,
3175
+ resize = "vertical",
3176
+ label,
3177
+ hint,
3178
+ error,
3179
+ required = false,
3180
+ disabled = false,
3181
+ readOnly = false,
3182
+ "aria-label": ariaLabel,
3183
+ "aria-labelledby": ariaLabelledBy,
3184
+ placeholder,
3185
+ name,
3186
+ autoComplete,
3187
+ maxLength,
3188
+ minLength,
3189
+ id: providedId,
3190
+ unstyled = false,
3191
+ className,
3192
+ onFocus: providedOnFocus,
3193
+ onBlur: providedOnBlur,
3194
+ children
3195
+ }, ref) {
3196
+ const generatedId = useId("textarea");
3197
+ const baseId = providedId || generatedId;
3198
+ const fieldId = `${baseId}-field`;
3199
+ const labelId = `${baseId}-label`;
3200
+ const hintId = `${baseId}-hint`;
3201
+ const errorId = `${baseId}-error`;
3202
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
3203
+ const isControlled = controlledValue !== void 0;
3204
+ const currentValue = isControlled ? controlledValue : uncontrolledValue;
3205
+ const setValue = useCallback(
3206
+ (newValue) => {
3207
+ if (!isControlled) {
3208
+ setUncontrolledValue(newValue);
3209
+ }
3210
+ onValueChange?.(newValue);
3211
+ },
3212
+ [isControlled, onValueChange]
3213
+ );
3214
+ const { isFocusVisible, focusProps } = useFocusVisible();
3215
+ useEffect(() => {
3216
+ if (process.env.NODE_ENV !== "production") {
3217
+ const isCompound2 = Children.count(children) > 0;
3218
+ if (!isCompound2 && !label && !ariaLabel && !ariaLabelledBy) {
3219
+ console.warn(
3220
+ "[Compa11y Textarea]: Textarea has no accessible label. Screen readers need this to identify the textarea. Use label prop, aria-label, or aria-labelledby."
3221
+ );
3222
+ }
3223
+ }
3224
+ }, [children, label, ariaLabel, ariaLabelledBy]);
3225
+ const hasError = Boolean(error);
3226
+ const isCompound = Children.count(children) > 0;
3227
+ const contextValue = {
3228
+ fieldId,
3229
+ labelId,
3230
+ hintId,
3231
+ errorId,
3232
+ value: currentValue,
3233
+ setValue,
3234
+ hasError,
3235
+ disabled,
3236
+ readOnly,
3237
+ required,
3238
+ rows,
3239
+ resize,
3240
+ isFocusVisible,
3241
+ focusProps,
3242
+ unstyled
3243
+ };
3244
+ const dataAttrs = {
3245
+ "data-compa11y-textarea": "",
3246
+ "data-error": hasError ? "true" : "false",
3247
+ "data-disabled": disabled ? "true" : "false",
3248
+ "data-required": required ? "true" : "false",
3249
+ "data-readonly": readOnly ? "true" : "false"
3250
+ };
3251
+ const wrapperStyle = unstyled ? {} : {
3252
+ display: "flex",
3253
+ flexDirection: "column",
3254
+ gap: "0.25rem"
3255
+ };
3256
+ if (isCompound) {
3257
+ return /* @__PURE__ */ jsx(TextareaContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx("div", { ...dataAttrs, className, style: wrapperStyle, children }) });
3258
+ }
3259
+ const textareaRef = useRef(null);
3260
+ const mergedRef = useCallback(
3261
+ (node) => {
3262
+ textareaRef.current = node;
3263
+ if (typeof ref === "function") {
3264
+ ref(node);
3265
+ } else if (ref) {
3266
+ ref.current = node;
3267
+ }
3268
+ },
3269
+ [ref]
3270
+ );
3271
+ const handleChange = useCallback(
3272
+ (event) => {
3273
+ setValue(event.target.value);
3274
+ },
3275
+ [setValue]
3276
+ );
3277
+ const handleFocus = useCallback(
3278
+ (event) => {
3279
+ focusProps.onFocus();
3280
+ providedOnFocus?.(event);
3281
+ },
3282
+ [focusProps, providedOnFocus]
3283
+ );
3284
+ const handleBlur = useCallback(
3285
+ (event) => {
3286
+ focusProps.onBlur();
3287
+ providedOnBlur?.(event);
3288
+ },
3289
+ [focusProps, providedOnBlur]
3290
+ );
3291
+ const describedByParts = [];
3292
+ if (hint) describedByParts.push(hintId);
3293
+ if (hasError) describedByParts.push(errorId);
3294
+ const ariaDescribedBy = describedByParts.length > 0 ? describedByParts.join(" ") : void 0;
3295
+ return /* @__PURE__ */ jsxs("div", { ...dataAttrs, className, style: wrapperStyle, children: [
3296
+ label && /* @__PURE__ */ jsxs(
3297
+ "label",
3298
+ {
3299
+ id: labelId,
3300
+ htmlFor: fieldId,
3301
+ "data-compa11y-textarea-label": "",
3302
+ style: unstyled ? {} : {
3303
+ display: "block",
3304
+ color: disabled ? "var(--compa11y-textarea-disabled-color, #999)" : "var(--compa11y-textarea-label-color, inherit)",
3305
+ fontSize: "var(--compa11y-textarea-label-size, 0.875rem)",
3306
+ fontWeight: "var(--compa11y-textarea-label-weight, 500)"
3307
+ },
3308
+ children: [
3309
+ label,
3310
+ required && /* @__PURE__ */ jsx(
3311
+ "span",
3312
+ {
3313
+ "data-compa11y-textarea-required": "",
3314
+ "aria-hidden": "true",
3315
+ style: unstyled ? {} : {
3316
+ color: "var(--compa11y-textarea-required-color, #ef4444)",
3317
+ marginLeft: "0.125rem"
3318
+ },
3319
+ children: "*"
3320
+ }
3321
+ )
3322
+ ]
3323
+ }
3324
+ ),
3325
+ /* @__PURE__ */ jsx(
3326
+ "textarea",
3327
+ {
3328
+ ref: mergedRef,
3329
+ id: fieldId,
3330
+ rows,
3331
+ value: currentValue,
3332
+ onChange: handleChange,
3333
+ onFocus: handleFocus,
3334
+ onBlur: handleBlur,
3335
+ disabled,
3336
+ readOnly,
3337
+ required,
3338
+ placeholder,
3339
+ name,
3340
+ autoComplete,
3341
+ maxLength,
3342
+ minLength,
3343
+ "aria-label": !label ? ariaLabel : void 0,
3344
+ "aria-labelledby": !label && ariaLabelledBy ? ariaLabelledBy : label ? labelId : void 0,
3345
+ "aria-describedby": ariaDescribedBy,
3346
+ "aria-invalid": hasError ? "true" : void 0,
3347
+ "aria-required": required ? "true" : void 0,
3348
+ "data-compa11y-textarea-field": "",
3349
+ style: unstyled ? {} : {
3350
+ width: "100%",
3351
+ padding: "var(--compa11y-textarea-padding, 0.5rem 0.75rem)",
3352
+ border: hasError ? "1px solid var(--compa11y-textarea-border-error, #ef4444)" : "var(--compa11y-textarea-border, 1px solid #ccc)",
3353
+ borderRadius: "var(--compa11y-textarea-radius, 4px)",
3354
+ fontSize: "var(--compa11y-textarea-font-size, 0.875rem)",
3355
+ fontFamily: "inherit",
3356
+ lineHeight: "1.5",
3357
+ resize,
3358
+ background: disabled ? "var(--compa11y-textarea-disabled-bg, #f5f5f5)" : readOnly ? "var(--compa11y-textarea-readonly-bg, #f9f9f9)" : "var(--compa11y-textarea-bg, white)",
3359
+ color: "inherit",
3360
+ cursor: disabled ? "not-allowed" : void 0,
3361
+ opacity: disabled ? "var(--compa11y-textarea-disabled-opacity, 0.7)" : void 0,
3362
+ ...isFocusVisible && !disabled ? {
3363
+ outline: hasError ? "2px solid var(--compa11y-textarea-border-error, #ef4444)" : "2px solid var(--compa11y-focus-color, #0066cc)",
3364
+ outlineOffset: "-1px",
3365
+ borderColor: hasError ? "var(--compa11y-textarea-border-error, #ef4444)" : "var(--compa11y-textarea-border-focus, #0066cc)"
3366
+ } : {}
3367
+ }
3368
+ }
3369
+ ),
3370
+ hint && /* @__PURE__ */ jsx(
3371
+ "div",
3372
+ {
3373
+ id: hintId,
3374
+ "data-compa11y-textarea-hint": "",
3375
+ style: unstyled ? {} : {
3376
+ color: "var(--compa11y-textarea-hint-color, #666)",
3377
+ fontSize: "var(--compa11y-textarea-hint-size, 0.8125rem)"
3378
+ },
3379
+ children: hint
3380
+ }
3381
+ ),
3382
+ hasError && /* @__PURE__ */ jsx(
3383
+ "div",
3384
+ {
3385
+ id: errorId,
3386
+ role: "alert",
3387
+ "data-compa11y-textarea-error": "",
3388
+ style: unstyled ? {} : {
3389
+ color: "var(--compa11y-textarea-error-color, #ef4444)",
3390
+ fontSize: "var(--compa11y-textarea-error-size, 0.8125rem)"
3391
+ },
3392
+ children: error
3393
+ }
3394
+ )
3395
+ ] });
3396
+ }
3397
+ );
3398
+ Textarea.displayName = "Textarea";
3399
+ var TextareaCompound = Object.assign(Textarea, {
3400
+ Label: TextareaLabel,
3401
+ Field: TextareaField,
3402
+ Hint: TextareaHint,
3403
+ Error: TextareaError
3404
+ });
427
3405
 
428
- export { Switch, useFocusControl, useFocusManager, useFocusVisible, useFocusWithin, useRovingTabindex, useRovingTabindexMap };
3406
+ export { Button, CheckboxCompound as Checkbox, Checkbox as CheckboxBase, CheckboxGroup, CheckboxIndicator, InputCompound as Input, Input as InputBase, InputError, InputField, InputHint, InputLabel, ListboxCompound as Listbox, Listbox as ListboxBase, ListboxGroup, ListboxOption, Radio, RadioGroupCompound as RadioGroup, RadioGroup as RadioGroupBase, SelectCompound as Select, Select as SelectBase, SelectListbox, SelectOptionItem, SelectTrigger, Switch, TextareaCompound as Textarea, Textarea as TextareaBase, TextareaError, TextareaField, TextareaHint, TextareaLabel, useCheckboxGroupContext, useFocusControl, useFocusManager, useFocusVisible, useFocusWithin, useInputContext, useListboxContext, useRadioGroupContext, useRovingTabindex, useRovingTabindexMap, useTextareaContext };
429
3407
  //# sourceMappingURL=index.js.map
430
3408
  //# sourceMappingURL=index.js.map