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