@spacing-ui/core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -21,7 +21,7 @@ var Root = ({ value, onValueChange, children }) => {
21
21
  const triggerRef = react.useRef(null);
22
22
  const listboxRef = react.useRef(null);
23
23
  const reactId = react.useId();
24
- const triggerId = `${reactId}trigger`;
24
+ const triggerId2 = `${reactId}trigger`;
25
25
  const listboxId = `${reactId}listbox`;
26
26
  const optionIdPrefix = `${reactId}option-`;
27
27
  const getOptionId = react.useCallback(
@@ -60,7 +60,7 @@ var Root = ({ value, onValueChange, children }) => {
60
60
  onValueChange,
61
61
  activeValue,
62
62
  setActiveValue,
63
- triggerId,
63
+ triggerId: triggerId2,
64
64
  listboxId,
65
65
  triggerRef,
66
66
  listboxRef,
@@ -73,7 +73,7 @@ var Root = ({ value, onValueChange, children }) => {
73
73
  value,
74
74
  onValueChange,
75
75
  activeValue,
76
- triggerId,
76
+ triggerId2,
77
77
  listboxId,
78
78
  getOptionId,
79
79
  getOrderedOptions
@@ -252,7 +252,992 @@ var Select = Object.assign(Root, {
252
252
  Content,
253
253
  Option
254
254
  });
255
+ var DEFAULT_DELAY_MS = 700;
256
+ var DEFAULT_SKIP_DELAY_MS = 300;
257
+ var ProviderContext = react.createContext(null);
258
+ var Provider = ({
259
+ children,
260
+ delayDuration = DEFAULT_DELAY_MS,
261
+ skipDelayDuration = DEFAULT_SKIP_DELAY_MS
262
+ }) => {
263
+ const lastCloseAt = react.useRef(0);
264
+ const isInSkipWindow = react.useCallback(
265
+ () => Date.now() - lastCloseAt.current < skipDelayDuration,
266
+ [skipDelayDuration]
267
+ );
268
+ const noteClose = react.useCallback(() => {
269
+ lastCloseAt.current = Date.now();
270
+ }, []);
271
+ const value = react.useMemo(
272
+ () => ({ delayDuration, skipDelayDuration, isInSkipWindow, noteClose }),
273
+ [delayDuration, skipDelayDuration, isInSkipWindow, noteClose]
274
+ );
275
+ return /* @__PURE__ */ jsxRuntime.jsx(ProviderContext.Provider, { value, children });
276
+ };
277
+ var TooltipContext = react.createContext(null);
278
+ function useTooltipContext(component) {
279
+ const ctx = react.useContext(TooltipContext);
280
+ if (!ctx) {
281
+ throw new Error(`<Tooltip.${component}> must be rendered inside <Tooltip>`);
282
+ }
283
+ return ctx;
284
+ }
285
+ var Root2 = ({
286
+ children,
287
+ delayDuration,
288
+ open: controlledOpen,
289
+ defaultOpen = false,
290
+ onOpenChange
291
+ }) => {
292
+ const provider = react.useContext(ProviderContext);
293
+ const effectiveDelay = delayDuration ?? provider?.delayDuration ?? DEFAULT_DELAY_MS;
294
+ const isControlled = controlledOpen !== void 0;
295
+ const [uncontrolledOpen, setUncontrolledOpen] = react.useState(defaultOpen);
296
+ const open = isControlled ? controlledOpen : uncontrolledOpen;
297
+ const openTimerRef = react.useRef(null);
298
+ const triggerRef = react.useRef(null);
299
+ const reactId = react.useId();
300
+ const contentId2 = `${reactId}content`;
301
+ const clearOpenTimer = react.useCallback(() => {
302
+ if (openTimerRef.current !== null) {
303
+ clearTimeout(openTimerRef.current);
304
+ openTimerRef.current = null;
305
+ }
306
+ }, []);
307
+ const setOpen = react.useCallback(
308
+ (next) => {
309
+ if (!isControlled) setUncontrolledOpen(next);
310
+ onOpenChange?.(next);
311
+ if (!next) provider?.noteClose();
312
+ },
313
+ [isControlled, onOpenChange, provider]
314
+ );
315
+ const scheduleOpen = react.useCallback(() => {
316
+ clearOpenTimer();
317
+ if (provider?.isInSkipWindow()) {
318
+ setOpen(true);
319
+ return;
320
+ }
321
+ openTimerRef.current = setTimeout(() => {
322
+ setOpen(true);
323
+ openTimerRef.current = null;
324
+ }, effectiveDelay);
325
+ }, [clearOpenTimer, effectiveDelay, provider, setOpen]);
326
+ const closeImmediately = react.useCallback(() => {
327
+ clearOpenTimer();
328
+ setOpen(false);
329
+ }, [clearOpenTimer, setOpen]);
330
+ react.useEffect(() => clearOpenTimer, [clearOpenTimer]);
331
+ const setTriggerRef = react.useCallback((node) => {
332
+ triggerRef.current = node;
333
+ }, []);
334
+ const triggerProps = react.useMemo(
335
+ () => ({
336
+ onMouseEnter: () => scheduleOpen(),
337
+ onMouseLeave: () => closeImmediately(),
338
+ onFocus: () => {
339
+ clearOpenTimer();
340
+ setOpen(true);
341
+ },
342
+ onBlur: () => closeImmediately(),
343
+ onKeyDown: (e) => {
344
+ if (e.key === "Escape" && open) {
345
+ closeImmediately();
346
+ }
347
+ },
348
+ "aria-describedby": open ? contentId2 : void 0
349
+ }),
350
+ [scheduleOpen, closeImmediately, clearOpenTimer, setOpen, open, contentId2]
351
+ );
352
+ const value = react.useMemo(
353
+ () => ({ open, contentId: contentId2, triggerProps, setTriggerRef }),
354
+ [open, contentId2, triggerProps, setTriggerRef]
355
+ );
356
+ return /* @__PURE__ */ jsxRuntime.jsx(TooltipContext.Provider, { value, children });
357
+ };
358
+ function composeRef(...refs) {
359
+ return (node) => {
360
+ for (const ref of refs) {
361
+ if (!ref) continue;
362
+ if (typeof ref === "function") ref(node);
363
+ else ref.current = node;
364
+ }
365
+ };
366
+ }
367
+ function composeHandler(...handlers) {
368
+ return (e) => {
369
+ for (const handler of handlers) {
370
+ handler?.(e);
371
+ if (typeof e.defaultPrevented === "boolean" && e.defaultPrevented) {
372
+ return;
373
+ }
374
+ }
375
+ };
376
+ }
377
+ var Trigger3 = ({ children }) => {
378
+ const ctx = useTooltipContext("Trigger");
379
+ if (!react.isValidElement(children)) {
380
+ throw new Error("<Tooltip.Trigger> expects a single React element child.");
381
+ }
382
+ const child = children;
383
+ const childProps = child.props;
384
+ const childRef = child.ref;
385
+ const merged = {
386
+ ...childProps,
387
+ onMouseEnter: composeHandler(
388
+ childProps.onMouseEnter,
389
+ ctx.triggerProps.onMouseEnter
390
+ ),
391
+ onMouseLeave: composeHandler(
392
+ childProps.onMouseLeave,
393
+ ctx.triggerProps.onMouseLeave
394
+ ),
395
+ onFocus: composeHandler(
396
+ childProps.onFocus,
397
+ ctx.triggerProps.onFocus
398
+ ),
399
+ onBlur: composeHandler(
400
+ childProps.onBlur,
401
+ ctx.triggerProps.onBlur
402
+ ),
403
+ onKeyDown: composeHandler(
404
+ childProps.onKeyDown,
405
+ ctx.triggerProps.onKeyDown
406
+ ),
407
+ "aria-describedby": [childProps["aria-describedby"], ctx.triggerProps["aria-describedby"]].filter(Boolean).join(" ") || void 0,
408
+ ref: composeRef(childRef, ctx.setTriggerRef)
409
+ };
410
+ return react.cloneElement(child, merged);
411
+ };
412
+ var Content2 = ({ children, forceMount, id, ...rest }) => {
413
+ const ctx = useTooltipContext("Content");
414
+ if (!ctx.open && !forceMount) return null;
415
+ return /* @__PURE__ */ jsxRuntime.jsx(
416
+ "div",
417
+ {
418
+ id: id ?? ctx.contentId,
419
+ role: "tooltip",
420
+ "data-state": ctx.open ? "open" : "closed",
421
+ ...rest,
422
+ children
423
+ }
424
+ );
425
+ };
426
+ var Tooltip = Object.assign(Root2, {
427
+ Provider,
428
+ Trigger: Trigger3,
429
+ Content: Content2
430
+ });
431
+ var Switch = react.forwardRef(function Switch2({
432
+ checked: controlledChecked,
433
+ defaultChecked = false,
434
+ onCheckedChange,
435
+ disabled,
436
+ onClick,
437
+ onKeyDown,
438
+ ...rest
439
+ }, ref) {
440
+ const isControlled = controlledChecked !== void 0;
441
+ const [uncontrolled, setUncontrolled] = react.useState(defaultChecked);
442
+ const checked = isControlled ? controlledChecked : uncontrolled;
443
+ const toggle = react.useCallback(() => {
444
+ if (disabled) return;
445
+ const next = !checked;
446
+ if (!isControlled) setUncontrolled(next);
447
+ onCheckedChange?.(next);
448
+ }, [checked, disabled, isControlled, onCheckedChange]);
449
+ const handleClick = (e) => {
450
+ onClick?.(e);
451
+ if (e.defaultPrevented) return;
452
+ toggle();
453
+ };
454
+ const handleKeyDown = (e) => {
455
+ onKeyDown?.(e);
456
+ if (e.defaultPrevented) return;
457
+ if (e.key === " " || e.key === "Enter") {
458
+ e.preventDefault();
459
+ toggle();
460
+ }
461
+ };
462
+ return /* @__PURE__ */ jsxRuntime.jsx(
463
+ "button",
464
+ {
465
+ ref,
466
+ type: "button",
467
+ role: "switch",
468
+ "aria-checked": checked,
469
+ "aria-disabled": disabled || void 0,
470
+ disabled,
471
+ "data-state": checked ? "checked" : "unchecked",
472
+ "data-disabled": disabled || void 0,
473
+ onClick: handleClick,
474
+ onKeyDown: handleKeyDown,
475
+ ...rest
476
+ }
477
+ );
478
+ });
479
+ var isChecked = (s) => s === true;
480
+ var isIndeterminate = (s) => s === "indeterminate";
481
+ var Checkbox = react.forwardRef(function Checkbox2({
482
+ checked: controlled,
483
+ defaultChecked = false,
484
+ onCheckedChange,
485
+ disabled,
486
+ required,
487
+ onClick,
488
+ onKeyDown,
489
+ ...rest
490
+ }, ref) {
491
+ const isControlled = controlled !== void 0;
492
+ const [uncontrolled, setUncontrolled] = react.useState(defaultChecked);
493
+ const value = isControlled ? controlled : uncontrolled;
494
+ const toggle = react.useCallback(() => {
495
+ if (disabled) return;
496
+ const next = isChecked(value) ? false : true;
497
+ if (!isControlled) setUncontrolled(next);
498
+ onCheckedChange?.(next);
499
+ }, [disabled, isControlled, onCheckedChange, value]);
500
+ const handleClick = (e) => {
501
+ onClick?.(e);
502
+ if (e.defaultPrevented) return;
503
+ toggle();
504
+ };
505
+ const handleKeyDown = (e) => {
506
+ onKeyDown?.(e);
507
+ if (e.defaultPrevented) return;
508
+ if (e.key === " ") {
509
+ e.preventDefault();
510
+ toggle();
511
+ }
512
+ };
513
+ const ariaChecked = isIndeterminate(value) ? "mixed" : isChecked(value) ? "true" : "false";
514
+ const dataState = isIndeterminate(value) ? "indeterminate" : isChecked(value) ? "checked" : "unchecked";
515
+ return /* @__PURE__ */ jsxRuntime.jsx(
516
+ "button",
517
+ {
518
+ ref,
519
+ type: "button",
520
+ role: "checkbox",
521
+ "aria-checked": ariaChecked,
522
+ "aria-required": required || void 0,
523
+ "aria-disabled": disabled || void 0,
524
+ disabled,
525
+ "data-state": dataState,
526
+ "data-disabled": disabled || void 0,
527
+ onClick: handleClick,
528
+ onKeyDown: handleKeyDown,
529
+ ...rest
530
+ }
531
+ );
532
+ });
533
+ var RadioGroupContext = react.createContext(null);
534
+ function useRadioGroupContext(component) {
535
+ const ctx = react.useContext(RadioGroupContext);
536
+ if (!ctx) {
537
+ throw new Error(`<${component}> must be rendered inside <RadioGroup>`);
538
+ }
539
+ return ctx;
540
+ }
541
+ var Root3 = react.forwardRef(function RadioGroup({
542
+ value: controlled,
543
+ defaultValue,
544
+ onValueChange,
545
+ name,
546
+ disabled = false,
547
+ orientation = "vertical",
548
+ children,
549
+ ...rest
550
+ }, forwardedRef) {
551
+ const isControlled = controlled !== void 0;
552
+ const [uncontrolled, setUncontrolled] = react.useState(defaultValue ?? null);
553
+ const value = isControlled ? controlled ?? null : uncontrolled;
554
+ const reactId = react.useId();
555
+ const generatedName = name ?? `${reactId}name`;
556
+ const groupRef = react.useRef(null);
557
+ const setRefs = (node) => {
558
+ groupRef.current = node;
559
+ if (typeof forwardedRef === "function") forwardedRef(node);
560
+ else if (forwardedRef) forwardedRef.current = node;
561
+ };
562
+ const setValue = react.useCallback(
563
+ (next) => {
564
+ if (disabled) return;
565
+ if (!isControlled) setUncontrolled(next);
566
+ onValueChange?.(next);
567
+ },
568
+ [disabled, isControlled, onValueChange]
569
+ );
570
+ react.useEffect(() => {
571
+ const root = groupRef.current;
572
+ if (!root) return;
573
+ const items = Array.from(root.querySelectorAll('[role="radio"]'));
574
+ const tabbable = value !== null ? items.find((el) => el.getAttribute("data-value") === value && !el.disabled) : items.find((el) => !el.disabled);
575
+ items.forEach((el) => {
576
+ el.tabIndex = el === tabbable ? 0 : -1;
577
+ });
578
+ });
579
+ const ctx = react.useMemo(
580
+ () => ({
581
+ value,
582
+ onValueChange: setValue,
583
+ name: generatedName,
584
+ disabled,
585
+ orientation,
586
+ groupRef
587
+ }),
588
+ [value, setValue, generatedName, disabled, orientation]
589
+ );
590
+ return /* @__PURE__ */ jsxRuntime.jsx(RadioGroupContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
591
+ "div",
592
+ {
593
+ ref: setRefs,
594
+ role: "radiogroup",
595
+ "aria-orientation": orientation,
596
+ "aria-disabled": disabled || void 0,
597
+ "data-disabled": disabled || void 0,
598
+ ...rest,
599
+ children
600
+ }
601
+ ) });
602
+ });
603
+ var Item = react.forwardRef(function RadioGroupItem({ value, disabled: itemDisabled, onClick, onKeyDown, ...rest }, ref) {
604
+ const ctx = useRadioGroupContext("RadioGroup.Item");
605
+ const disabled = ctx.disabled || !!itemDisabled;
606
+ const checked = ctx.value === value;
607
+ const handleClick = (e) => {
608
+ onClick?.(e);
609
+ if (e.defaultPrevented || disabled) return;
610
+ ctx.onValueChange(value);
611
+ };
612
+ const move = (direction) => {
613
+ const root = ctx.groupRef.current;
614
+ if (!root) return;
615
+ const items = Array.from(root.querySelectorAll('[role="radio"]')).filter(
616
+ (el) => !el.disabled
617
+ );
618
+ if (items.length === 0) return;
619
+ const idx = items.findIndex((el) => el === document.activeElement);
620
+ const nextIdx = (idx + direction + items.length) % items.length;
621
+ const next = items[nextIdx];
622
+ if (!next) return;
623
+ next.focus();
624
+ const nextValue = next.getAttribute("data-value");
625
+ if (nextValue) ctx.onValueChange(nextValue);
626
+ };
627
+ const handleKeyDown = (e) => {
628
+ onKeyDown?.(e);
629
+ if (e.defaultPrevented || disabled) return;
630
+ const nextKey = ctx.orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
631
+ const prevKey = ctx.orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
632
+ if (e.key === nextKey) {
633
+ e.preventDefault();
634
+ move(1);
635
+ } else if (e.key === prevKey) {
636
+ e.preventDefault();
637
+ move(-1);
638
+ }
639
+ };
640
+ return /* @__PURE__ */ jsxRuntime.jsx(
641
+ "button",
642
+ {
643
+ ref,
644
+ type: "button",
645
+ role: "radio",
646
+ name: ctx.name,
647
+ value,
648
+ "aria-checked": checked,
649
+ "aria-disabled": disabled || void 0,
650
+ disabled,
651
+ tabIndex: -1,
652
+ "data-value": value,
653
+ "data-state": checked ? "checked" : "unchecked",
654
+ "data-disabled": disabled || void 0,
655
+ onClick: handleClick,
656
+ onKeyDown: handleKeyDown,
657
+ ...rest
658
+ }
659
+ );
660
+ });
661
+ var RadioGroup2 = Object.assign(Root3, { Item });
662
+ var TabsContext = react.createContext(null);
663
+ function useTabsContext(component) {
664
+ const ctx = react.useContext(TabsContext);
665
+ if (!ctx) {
666
+ throw new Error(`<Tabs.${component}> must be rendered inside <Tabs>`);
667
+ }
668
+ return ctx;
669
+ }
670
+ var triggerId = (prefix, value) => `${prefix}trigger-${value.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
671
+ var contentId = (prefix, value) => `${prefix}content-${value.replace(/[^a-zA-Z0-9_-]/g, "_")}`;
672
+ var Root4 = react.forwardRef(function Tabs({
673
+ value: controlled,
674
+ defaultValue,
675
+ onValueChange,
676
+ orientation = "horizontal",
677
+ activationMode = "automatic",
678
+ children,
679
+ ...rest
680
+ }, ref) {
681
+ const isControlled = controlled !== void 0;
682
+ const [uncontrolled, setUncontrolled] = react.useState(defaultValue ?? null);
683
+ const value = isControlled ? controlled ?? null : uncontrolled;
684
+ const reactId = react.useId();
685
+ const listRef = react.useRef(null);
686
+ const setValue = react.useCallback(
687
+ (next) => {
688
+ if (!isControlled) setUncontrolled(next);
689
+ onValueChange?.(next);
690
+ },
691
+ [isControlled, onValueChange]
692
+ );
693
+ const ctx = react.useMemo(
694
+ () => ({
695
+ value,
696
+ onValueChange: setValue,
697
+ activationMode,
698
+ orientation,
699
+ idPrefix: reactId,
700
+ listRef
701
+ }),
702
+ [value, setValue, activationMode, orientation, reactId]
703
+ );
704
+ return /* @__PURE__ */ jsxRuntime.jsx(TabsContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-orientation": orientation, ...rest, children }) });
705
+ });
706
+ var List = react.forwardRef(function TabsList({ loop = true, children, ...rest }, forwardedRef) {
707
+ const ctx = useTabsContext("List");
708
+ const setRefs = (node) => {
709
+ ctx.listRef.current = node;
710
+ if (typeof forwardedRef === "function") forwardedRef(node);
711
+ else if (forwardedRef) forwardedRef.current = node;
712
+ };
713
+ return /* @__PURE__ */ jsxRuntime.jsx(
714
+ "div",
715
+ {
716
+ ref: setRefs,
717
+ role: "tablist",
718
+ "aria-orientation": ctx.orientation,
719
+ "data-orientation": ctx.orientation,
720
+ "data-loop": loop || void 0,
721
+ ...rest,
722
+ children
723
+ }
724
+ );
725
+ });
726
+ var Trigger4 = react.forwardRef(function TabsTrigger({ value, disabled, onClick, onKeyDown, onFocus, ...rest }, ref) {
727
+ const ctx = useTabsContext("Trigger");
728
+ const selected = ctx.value === value;
729
+ const handleClick = (e) => {
730
+ onClick?.(e);
731
+ if (e.defaultPrevented || disabled) return;
732
+ ctx.onValueChange(value);
733
+ };
734
+ const move = (direction) => {
735
+ const root = ctx.listRef.current;
736
+ if (!root) return;
737
+ const triggers = Array.from(root.querySelectorAll('[role="tab"]')).filter(
738
+ (el) => !el.disabled
739
+ );
740
+ if (triggers.length === 0) return;
741
+ let nextIdx;
742
+ if (direction === "first") nextIdx = 0;
743
+ else if (direction === "last") nextIdx = triggers.length - 1;
744
+ else {
745
+ const idx = triggers.findIndex((el) => el === document.activeElement);
746
+ const loopList = (root.getAttribute("data-loop") ?? "true") !== "false";
747
+ const candidate = idx + direction;
748
+ if (candidate < 0) nextIdx = loopList ? triggers.length - 1 : 0;
749
+ else if (candidate >= triggers.length) nextIdx = loopList ? 0 : triggers.length - 1;
750
+ else nextIdx = candidate;
751
+ }
752
+ const target = triggers[nextIdx];
753
+ if (!target) return;
754
+ target.focus();
755
+ if (ctx.activationMode === "automatic") {
756
+ const v = target.getAttribute("data-value");
757
+ if (v) ctx.onValueChange(v);
758
+ }
759
+ };
760
+ const handleKeyDown = (e) => {
761
+ onKeyDown?.(e);
762
+ if (e.defaultPrevented || disabled) return;
763
+ const nextKey = ctx.orientation === "horizontal" ? "ArrowRight" : "ArrowDown";
764
+ const prevKey = ctx.orientation === "horizontal" ? "ArrowLeft" : "ArrowUp";
765
+ if (e.key === nextKey) {
766
+ e.preventDefault();
767
+ move(1);
768
+ } else if (e.key === prevKey) {
769
+ e.preventDefault();
770
+ move(-1);
771
+ } else if (e.key === "Home") {
772
+ e.preventDefault();
773
+ move("first");
774
+ } else if (e.key === "End") {
775
+ e.preventDefault();
776
+ move("last");
777
+ }
778
+ };
779
+ return /* @__PURE__ */ jsxRuntime.jsx(
780
+ "button",
781
+ {
782
+ ref,
783
+ type: "button",
784
+ role: "tab",
785
+ id: triggerId(ctx.idPrefix, value),
786
+ "aria-selected": selected,
787
+ "aria-controls": contentId(ctx.idPrefix, value),
788
+ "aria-disabled": disabled || void 0,
789
+ disabled,
790
+ tabIndex: selected ? 0 : -1,
791
+ "data-value": value,
792
+ "data-state": selected ? "active" : "inactive",
793
+ "data-disabled": disabled || void 0,
794
+ "data-orientation": ctx.orientation,
795
+ onClick: handleClick,
796
+ onKeyDown: handleKeyDown,
797
+ onFocus,
798
+ ...rest
799
+ }
800
+ );
801
+ });
802
+ var Content3 = react.forwardRef(function TabsContent({ value, forceMount, hidden, ...rest }, ref) {
803
+ const ctx = useTabsContext("Content");
804
+ const selected = ctx.value === value;
805
+ if (!selected && !forceMount) return null;
806
+ return /* @__PURE__ */ jsxRuntime.jsx(
807
+ "div",
808
+ {
809
+ ref,
810
+ role: "tabpanel",
811
+ id: contentId(ctx.idPrefix, value),
812
+ "aria-labelledby": triggerId(ctx.idPrefix, value),
813
+ "data-state": selected ? "active" : "inactive",
814
+ "data-orientation": ctx.orientation,
815
+ hidden: hidden ?? !selected,
816
+ tabIndex: 0,
817
+ ...rest
818
+ }
819
+ );
820
+ });
821
+ var Tabs2 = Object.assign(Root4, { List, Trigger: Trigger4, Content: Content3 });
822
+ var AccordionContext = react.createContext(null);
823
+ function useAccordionContext(component) {
824
+ const ctx = react.useContext(AccordionContext);
825
+ if (!ctx) {
826
+ throw new Error(`<Accordion.${component}> must be rendered inside <Accordion>`);
827
+ }
828
+ return ctx;
829
+ }
830
+ var ItemContext = react.createContext(null);
831
+ function useItemContext(component) {
832
+ const ctx = react.useContext(ItemContext);
833
+ if (!ctx) {
834
+ throw new Error(`<Accordion.${component}> must be rendered inside <Accordion.Item>`);
835
+ }
836
+ return ctx;
837
+ }
838
+ var Root5 = react.forwardRef(function Accordion(props, forwardedRef) {
839
+ const {
840
+ type,
841
+ disabled = false,
842
+ children,
843
+ // strip controlled-state props so they don't leak onto the DOM
844
+ value: _v,
845
+ defaultValue: _dv,
846
+ onValueChange: _ovc,
847
+ collapsible: _col,
848
+ ...rest
849
+ } = props;
850
+ const isMultiple = type === "multiple";
851
+ const controlled = "value" in props ? isMultiple ? props.value : props.value : void 0;
852
+ const isControlled = controlled !== void 0;
853
+ const defaultValue = "defaultValue" in props ? isMultiple ? props.defaultValue ?? [] : props.defaultValue ?? null : isMultiple ? [] : null;
854
+ const [uncontrolledArr, setUncontrolledArr] = react.useState(
855
+ isMultiple ? defaultValue ?? [] : []
856
+ );
857
+ const [uncontrolledStr, setUncontrolledStr] = react.useState(
858
+ isMultiple ? null : defaultValue ?? null
859
+ );
860
+ const values = react.useMemo(() => {
861
+ if (isMultiple) {
862
+ return isControlled ? controlled ?? [] : uncontrolledArr;
863
+ }
864
+ return isControlled ? controlled ? [controlled] : [] : uncontrolledStr ? [uncontrolledStr] : [];
865
+ }, [isMultiple, isControlled, controlled, uncontrolledArr, uncontrolledStr]);
866
+ const collapsible = !isMultiple && "collapsible" in props ? !!props.collapsible : isMultiple;
867
+ const reactId = react.useId();
868
+ const rootRef = react.useRef(null);
869
+ const setRefs = (node) => {
870
+ rootRef.current = node;
871
+ if (typeof forwardedRef === "function") forwardedRef(node);
872
+ else if (forwardedRef) forwardedRef.current = node;
873
+ };
874
+ const toggle = react.useCallback(
875
+ (value) => {
876
+ if (disabled) return;
877
+ if (isMultiple) {
878
+ const arr = isControlled ? controlled ?? [] : uncontrolledArr;
879
+ const next = arr.includes(value) ? arr.filter((v) => v !== value) : [...arr, value];
880
+ if (!isControlled) setUncontrolledArr(next);
881
+ props.onValueChange?.(next);
882
+ } else {
883
+ const current = isControlled ? controlled : uncontrolledStr;
884
+ const allowEmpty = "collapsible" in props ? !!props.collapsible : false;
885
+ let next;
886
+ if (current === value) {
887
+ if (!allowEmpty) return;
888
+ next = "";
889
+ } else {
890
+ next = value;
891
+ }
892
+ if (!isControlled) setUncontrolledStr(next || null);
893
+ props.onValueChange?.(next);
894
+ }
895
+ },
896
+ [disabled, isMultiple, isControlled, controlled, uncontrolledArr, uncontrolledStr, props]
897
+ );
898
+ const ctx = react.useMemo(
899
+ () => ({
900
+ type,
901
+ values,
902
+ toggle,
903
+ collapsible,
904
+ disabled,
905
+ idPrefix: reactId,
906
+ rootRef
907
+ }),
908
+ [type, values, toggle, collapsible, disabled, reactId]
909
+ );
910
+ return /* @__PURE__ */ jsxRuntime.jsx(AccordionContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: setRefs, "data-disabled": disabled || void 0, ...rest, children }) });
911
+ });
912
+ var Item2 = react.forwardRef(function AccordionItem({ value, disabled: itemDisabled, children, ...rest }, ref) {
913
+ const ctx = useAccordionContext("Item");
914
+ const open = ctx.values.includes(value);
915
+ const disabled = ctx.disabled || !!itemDisabled;
916
+ const safeValue = value.replace(/[^a-zA-Z0-9_-]/g, "_");
917
+ const triggerId2 = `${ctx.idPrefix}trigger-${safeValue}`;
918
+ const contentId2 = `${ctx.idPrefix}content-${safeValue}`;
919
+ const itemCtx = react.useMemo(
920
+ () => ({ value, open, disabled, triggerId: triggerId2, contentId: contentId2 }),
921
+ [value, open, disabled, triggerId2, contentId2]
922
+ );
923
+ return /* @__PURE__ */ jsxRuntime.jsx(ItemContext.Provider, { value: itemCtx, children: /* @__PURE__ */ jsxRuntime.jsx(
924
+ "div",
925
+ {
926
+ ref,
927
+ "data-state": open ? "open" : "closed",
928
+ "data-disabled": disabled || void 0,
929
+ ...rest,
930
+ children
931
+ }
932
+ ) });
933
+ });
934
+ var Trigger5 = react.forwardRef(function AccordionTrigger({ onClick, onKeyDown, ...rest }, ref) {
935
+ const ctx = useAccordionContext("Trigger");
936
+ const item = useItemContext("Trigger");
937
+ const handleClick = (e) => {
938
+ onClick?.(e);
939
+ if (e.defaultPrevented || item.disabled) return;
940
+ ctx.toggle(item.value);
941
+ };
942
+ const move = (direction) => {
943
+ const root = ctx.rootRef.current;
944
+ if (!root) return;
945
+ const triggers = Array.from(
946
+ root.querySelectorAll('[data-accordion-trigger="true"]')
947
+ ).filter((el) => !el.disabled);
948
+ if (triggers.length === 0) return;
949
+ let nextIdx;
950
+ if (direction === "first") nextIdx = 0;
951
+ else if (direction === "last") nextIdx = triggers.length - 1;
952
+ else {
953
+ const idx = triggers.findIndex((el) => el === document.activeElement);
954
+ const candidate = idx + direction;
955
+ nextIdx = (candidate + triggers.length) % triggers.length;
956
+ }
957
+ triggers[nextIdx]?.focus();
958
+ };
959
+ const handleKeyDown = (e) => {
960
+ onKeyDown?.(e);
961
+ if (e.defaultPrevented) return;
962
+ if (e.key === "ArrowDown") {
963
+ e.preventDefault();
964
+ move(1);
965
+ } else if (e.key === "ArrowUp") {
966
+ e.preventDefault();
967
+ move(-1);
968
+ } else if (e.key === "Home") {
969
+ e.preventDefault();
970
+ move("first");
971
+ } else if (e.key === "End") {
972
+ e.preventDefault();
973
+ move("last");
974
+ }
975
+ };
976
+ return /* @__PURE__ */ jsxRuntime.jsx(
977
+ "button",
978
+ {
979
+ ref,
980
+ type: "button",
981
+ id: item.triggerId,
982
+ "aria-expanded": item.open,
983
+ "aria-controls": item.contentId,
984
+ "aria-disabled": item.disabled || void 0,
985
+ disabled: item.disabled,
986
+ "data-state": item.open ? "open" : "closed",
987
+ "data-disabled": item.disabled || void 0,
988
+ "data-accordion-trigger": "true",
989
+ onClick: handleClick,
990
+ onKeyDown: handleKeyDown,
991
+ ...rest
992
+ }
993
+ );
994
+ });
995
+ var Content4 = react.forwardRef(function AccordionContent({ forceMount, hidden, children, ...rest }, ref) {
996
+ const item = useItemContext("Content");
997
+ if (!item.open && !forceMount) return null;
998
+ return /* @__PURE__ */ jsxRuntime.jsx(
999
+ "div",
1000
+ {
1001
+ ref,
1002
+ role: "region",
1003
+ id: item.contentId,
1004
+ "aria-labelledby": item.triggerId,
1005
+ "data-state": item.open ? "open" : "closed",
1006
+ hidden: hidden ?? !item.open,
1007
+ ...rest,
1008
+ children
1009
+ }
1010
+ );
1011
+ });
1012
+ var Accordion2 = Object.assign(Root5, { Item: Item2, Trigger: Trigger5, Content: Content4 });
1013
+ var FOCUSABLE_SELECTOR = [
1014
+ "a[href]",
1015
+ "button:not([disabled])",
1016
+ "input:not([disabled])",
1017
+ "select:not([disabled])",
1018
+ "textarea:not([disabled])",
1019
+ '[tabindex]:not([tabindex="-1"])'
1020
+ ].join(",");
1021
+ function getFocusables(root) {
1022
+ if (!root) return [];
1023
+ return Array.from(root.querySelectorAll(FOCUSABLE_SELECTOR)).filter(
1024
+ (el) => !el.hasAttribute("disabled") && el.getAttribute("aria-hidden") !== "true"
1025
+ );
1026
+ }
1027
+ var DialogContext = react.createContext(null);
1028
+ function useDialogContext(component) {
1029
+ const ctx = react.useContext(DialogContext);
1030
+ if (!ctx) {
1031
+ throw new Error(`<Dialog.${component}> must be rendered inside <Dialog>`);
1032
+ }
1033
+ return ctx;
1034
+ }
1035
+ var Root6 = ({
1036
+ children,
1037
+ open: controlled,
1038
+ defaultOpen = false,
1039
+ onOpenChange,
1040
+ modal = true
1041
+ }) => {
1042
+ const isControlled = controlled !== void 0;
1043
+ const [uncontrolled, setUncontrolled] = react.useState(defaultOpen);
1044
+ const open = isControlled ? controlled : uncontrolled;
1045
+ const triggerRef = react.useRef(null);
1046
+ const contentRef = react.useRef(null);
1047
+ const [hasTitle, setHasTitle] = react.useState(false);
1048
+ const [hasDescription, setHasDescription] = react.useState(false);
1049
+ const reactId = react.useId();
1050
+ const contentId2 = `${reactId}content`;
1051
+ const titleId = `${reactId}title`;
1052
+ const descriptionId = `${reactId}description`;
1053
+ const setOpen = react.useCallback(
1054
+ (next) => {
1055
+ if (!isControlled) setUncontrolled(next);
1056
+ onOpenChange?.(next);
1057
+ },
1058
+ [isControlled, onOpenChange]
1059
+ );
1060
+ const ctx = react.useMemo(
1061
+ () => ({
1062
+ open,
1063
+ setOpen,
1064
+ triggerRef,
1065
+ contentRef,
1066
+ contentId: contentId2,
1067
+ titleId,
1068
+ descriptionId,
1069
+ hasTitle,
1070
+ setHasTitle,
1071
+ hasDescription,
1072
+ setHasDescription,
1073
+ modal
1074
+ }),
1075
+ [open, setOpen, contentId2, titleId, descriptionId, hasTitle, hasDescription, modal]
1076
+ );
1077
+ return /* @__PURE__ */ jsxRuntime.jsx(DialogContext.Provider, { value: ctx, children });
1078
+ };
1079
+ var Trigger6 = react.forwardRef(function DialogTrigger({ onClick, ...rest }, forwardedRef) {
1080
+ const ctx = useDialogContext("Trigger");
1081
+ const setRefs = (node) => {
1082
+ ctx.triggerRef.current = node;
1083
+ if (typeof forwardedRef === "function") forwardedRef(node);
1084
+ else if (forwardedRef) forwardedRef.current = node;
1085
+ };
1086
+ const handleClick = (e) => {
1087
+ onClick?.(e);
1088
+ if (e.defaultPrevented) return;
1089
+ ctx.setOpen(true);
1090
+ };
1091
+ return /* @__PURE__ */ jsxRuntime.jsx(
1092
+ "button",
1093
+ {
1094
+ ref: setRefs,
1095
+ type: "button",
1096
+ "aria-haspopup": "dialog",
1097
+ "aria-expanded": ctx.open,
1098
+ "aria-controls": ctx.open ? ctx.contentId : void 0,
1099
+ "data-state": ctx.open ? "open" : "closed",
1100
+ onClick: handleClick,
1101
+ ...rest
1102
+ }
1103
+ );
1104
+ });
1105
+ var Overlay = react.forwardRef(function DialogOverlay({ forceMount, onClick, ...rest }, ref) {
1106
+ const ctx = useDialogContext("Overlay");
1107
+ if (!ctx.open && !forceMount) return null;
1108
+ const handleClick = (e) => {
1109
+ onClick?.(e);
1110
+ if (e.defaultPrevented || !ctx.modal) return;
1111
+ if (e.target === e.currentTarget) {
1112
+ ctx.setOpen(false);
1113
+ }
1114
+ };
1115
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref, "data-state": ctx.open ? "open" : "closed", onClick: handleClick, ...rest });
1116
+ });
1117
+ var Content5 = react.forwardRef(function DialogContent({ forceMount, onEscapeKeyDown, onKeyDown, children, ...rest }, forwardedRef) {
1118
+ const ctx = useDialogContext("Content");
1119
+ const setRefs = (node) => {
1120
+ ctx.contentRef.current = node;
1121
+ if (typeof forwardedRef === "function") forwardedRef(node);
1122
+ else if (forwardedRef) forwardedRef.current = node;
1123
+ };
1124
+ react.useEffect(() => {
1125
+ if (!ctx.open) return;
1126
+ const previouslyFocused = document.activeElement;
1127
+ const id = requestAnimationFrame(() => {
1128
+ const root = ctx.contentRef.current;
1129
+ if (!root) return;
1130
+ const focusables = getFocusables(root);
1131
+ const target = focusables[0] ?? root;
1132
+ target.focus();
1133
+ });
1134
+ return () => {
1135
+ cancelAnimationFrame(id);
1136
+ const trigger = ctx.triggerRef.current;
1137
+ (trigger ?? previouslyFocused)?.focus?.();
1138
+ };
1139
+ }, [ctx.open, ctx.contentRef, ctx.triggerRef]);
1140
+ react.useEffect(() => {
1141
+ if (!ctx.open || !ctx.modal) return;
1142
+ const prev = document.body.style.overflow;
1143
+ document.body.style.overflow = "hidden";
1144
+ return () => {
1145
+ document.body.style.overflow = prev;
1146
+ };
1147
+ }, [ctx.open, ctx.modal]);
1148
+ const { open: ctxOpen, setOpen: ctxSetOpen } = ctx;
1149
+ react.useEffect(() => {
1150
+ if (!ctxOpen) return;
1151
+ const onKey = (e) => {
1152
+ if (e.key !== "Escape") return;
1153
+ onEscapeKeyDown?.(e);
1154
+ if (e.defaultPrevented) return;
1155
+ ctxSetOpen(false);
1156
+ };
1157
+ document.addEventListener("keydown", onKey);
1158
+ return () => document.removeEventListener("keydown", onKey);
1159
+ }, [ctxOpen, ctxSetOpen, onEscapeKeyDown]);
1160
+ if (!ctx.open && !forceMount) return null;
1161
+ const handleKeyDown = (e) => {
1162
+ onKeyDown?.(e);
1163
+ if (e.defaultPrevented) return;
1164
+ if (e.key !== "Tab" || !ctx.modal) return;
1165
+ const focusables = getFocusables(ctx.contentRef.current);
1166
+ if (focusables.length === 0) {
1167
+ e.preventDefault();
1168
+ return;
1169
+ }
1170
+ const first = focusables[0];
1171
+ const last = focusables[focusables.length - 1];
1172
+ const active = document.activeElement;
1173
+ if (e.shiftKey && (active === first || !ctx.contentRef.current?.contains(active))) {
1174
+ e.preventDefault();
1175
+ last.focus();
1176
+ } else if (!e.shiftKey && active === last) {
1177
+ e.preventDefault();
1178
+ first.focus();
1179
+ }
1180
+ };
1181
+ return /* @__PURE__ */ jsxRuntime.jsx(
1182
+ "div",
1183
+ {
1184
+ ref: setRefs,
1185
+ role: "dialog",
1186
+ "aria-modal": ctx.modal || void 0,
1187
+ id: ctx.contentId,
1188
+ "aria-labelledby": ctx.hasTitle ? ctx.titleId : void 0,
1189
+ "aria-describedby": ctx.hasDescription ? ctx.descriptionId : void 0,
1190
+ tabIndex: -1,
1191
+ "data-state": ctx.open ? "open" : "closed",
1192
+ onKeyDown: handleKeyDown,
1193
+ ...rest,
1194
+ children
1195
+ }
1196
+ );
1197
+ });
1198
+ var Title = react.forwardRef(function DialogTitle(props, ref) {
1199
+ const ctx = useDialogContext("Title");
1200
+ react.useEffect(() => {
1201
+ ctx.setHasTitle(true);
1202
+ return () => ctx.setHasTitle(false);
1203
+ }, [ctx]);
1204
+ return /* @__PURE__ */ jsxRuntime.jsx("h2", { ref, id: ctx.titleId, ...props });
1205
+ });
1206
+ var Description = react.forwardRef(
1207
+ function DialogDescription(props, ref) {
1208
+ const ctx = useDialogContext("Description");
1209
+ react.useEffect(() => {
1210
+ ctx.setHasDescription(true);
1211
+ return () => ctx.setHasDescription(false);
1212
+ }, [ctx]);
1213
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { ref, id: ctx.descriptionId, ...props });
1214
+ }
1215
+ );
1216
+ var Close = react.forwardRef(function DialogClose({ onClick, ...rest }, ref) {
1217
+ const ctx = useDialogContext("Close");
1218
+ const handleClick = (e) => {
1219
+ onClick?.(e);
1220
+ if (e.defaultPrevented) return;
1221
+ ctx.setOpen(false);
1222
+ };
1223
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { ref, type: "button", onClick: handleClick, ...rest });
1224
+ });
1225
+ var Dialog = Object.assign(Root6, {
1226
+ Trigger: Trigger6,
1227
+ Overlay,
1228
+ Content: Content5,
1229
+ Title,
1230
+ Description,
1231
+ Close
1232
+ });
255
1233
 
1234
+ exports.Accordion = Accordion2;
1235
+ exports.Checkbox = Checkbox;
1236
+ exports.Dialog = Dialog;
1237
+ exports.RadioGroup = RadioGroup2;
256
1238
  exports.Select = Select;
1239
+ exports.Switch = Switch;
1240
+ exports.Tabs = Tabs2;
1241
+ exports.Tooltip = Tooltip;
257
1242
  //# sourceMappingURL=index.cjs.map
258
1243
  //# sourceMappingURL=index.cjs.map