@octanejs/radix 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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/package.json +45 -0
  4. package/src/AccessibleIcon.ts +26 -0
  5. package/src/Accordion.ts +282 -0
  6. package/src/AlertDialog.ts +110 -0
  7. package/src/Arrow.ts +21 -0
  8. package/src/AspectRatio.ts +34 -0
  9. package/src/Avatar.ts +152 -0
  10. package/src/Checkbox.ts +325 -0
  11. package/src/Collapsible.ts +170 -0
  12. package/src/ContextMenu.ts +303 -0
  13. package/src/Dialog.ts +308 -0
  14. package/src/DismissableLayer.ts +413 -0
  15. package/src/DropdownMenu.ts +275 -0
  16. package/src/FocusScope.ts +266 -0
  17. package/src/Form.ts +621 -0
  18. package/src/HoverCard.ts +318 -0
  19. package/src/Label.ts +25 -0
  20. package/src/Menu.ts +1057 -0
  21. package/src/Menubar.ts +572 -0
  22. package/src/NavigationMenu.ts +1236 -0
  23. package/src/OneTimePasswordField.ts +977 -0
  24. package/src/PasswordToggleField.ts +495 -0
  25. package/src/Popover.ts +354 -0
  26. package/src/Popper.ts +401 -0
  27. package/src/Portal.ts +19 -0
  28. package/src/Presence.ts +225 -0
  29. package/src/Primitive.ts +53 -0
  30. package/src/Progress.ts +92 -0
  31. package/src/Radio.ts +262 -0
  32. package/src/RadioGroup.ts +212 -0
  33. package/src/RovingFocusGroup.ts +259 -0
  34. package/src/ScrollArea.ts +966 -0
  35. package/src/Select.ts +1901 -0
  36. package/src/Separator.ts +36 -0
  37. package/src/Slider.ts +745 -0
  38. package/src/Slot.ts +105 -0
  39. package/src/Switch.ts +278 -0
  40. package/src/Tabs.ts +177 -0
  41. package/src/Toast.ts +923 -0
  42. package/src/Toggle.ts +31 -0
  43. package/src/ToggleGroup.ts +157 -0
  44. package/src/Toolbar.ts +130 -0
  45. package/src/Tooltip.ts +669 -0
  46. package/src/VisuallyHidden.ts +29 -0
  47. package/src/collection.ts +97 -0
  48. package/src/compose-event-handlers.ts +15 -0
  49. package/src/compose-refs.ts +53 -0
  50. package/src/context.ts +109 -0
  51. package/src/direction.ts +19 -0
  52. package/src/focus-guards.ts +56 -0
  53. package/src/index.ts +54 -0
  54. package/src/internal.ts +42 -0
  55. package/src/scroll-lock.ts +57 -0
  56. package/src/use-callback-ref.ts +28 -0
  57. package/src/use-effect-event.ts +36 -0
  58. package/src/use-is-hydrated.ts +23 -0
  59. package/src/use-previous.ts +28 -0
  60. package/src/use-size.ts +57 -0
  61. package/src/useControllableState.ts +78 -0
  62. package/src/useId.ts +15 -0
@@ -0,0 +1,57 @@
1
+ // Ported from @radix-ui/react-use-size (source:
2
+ // .radix-primitives/packages/react/use-size/src/use-size.tsx). Tracks an element's
3
+ // border-box size via ResizeObserver (guarded for environments without it, e.g. jsdom —
4
+ // there the initial offset measurement is still reported).
5
+ import { useLayoutEffect, useState } from 'octane';
6
+
7
+ import { S, splitSlot, subSlot } from './internal';
8
+
9
+ export function useSize(...args: any[]): { width: number; height: number } | undefined {
10
+ const [user, slotArg] = splitSlot(args);
11
+ const slot = slotArg ?? S('useSize');
12
+ const element = user[0] as HTMLElement | null;
13
+ const [size, setSize] = useState<{ width: number; height: number } | undefined>(
14
+ undefined,
15
+ subSlot(slot, 'size'),
16
+ );
17
+
18
+ useLayoutEffect(
19
+ () => {
20
+ if (element) {
21
+ // Provide size as early as possible.
22
+ setSize({ width: element.offsetWidth, height: element.offsetHeight });
23
+ if (typeof ResizeObserver === 'undefined') return;
24
+ const resizeObserver = new ResizeObserver((entries) => {
25
+ if (!Array.isArray(entries) || !entries.length) return;
26
+ const entry = entries[0]!;
27
+ let width: number;
28
+ let height: number;
29
+ if ('borderBoxSize' in entry) {
30
+ const borderSizeEntry = (entry as any)['borderBoxSize'];
31
+ // Iron out differences between browsers.
32
+ const borderSize = Array.isArray(borderSizeEntry)
33
+ ? borderSizeEntry[0]
34
+ : borderSizeEntry;
35
+ width = borderSize['inlineSize'];
36
+ height = borderSize['blockSize'];
37
+ } else {
38
+ // For browsers that don't support `borderBoxSize` we calculate it
39
+ // ourselves to get the correct border box.
40
+ width = element.offsetWidth;
41
+ height = element.offsetHeight;
42
+ }
43
+ setSize({ width, height });
44
+ });
45
+ resizeObserver.observe(element, { box: 'border-box' });
46
+ return () => resizeObserver.unobserve(element);
47
+ } else {
48
+ // Only reset to `undefined` when the element becomes `null`, not if it
49
+ // changes to another element.
50
+ setSize(undefined);
51
+ }
52
+ },
53
+ [element],
54
+ subSlot(slot, 'e'),
55
+ );
56
+ return size;
57
+ }
@@ -0,0 +1,78 @@
1
+ // Ported from @radix-ui/react-use-controllable-state (source:
2
+ // .radix-primitives/packages/react/use-controllable-state/src/use-controllable-state.tsx).
3
+ // A value that is either CONTROLLED (a `prop` is passed) or UNCONTROLLED (internal state
4
+ // seeded by `defaultProp`), always calling `onChange` on updates. This is a STATE-layer
5
+ // concept, so octane's no-controlled-DOM-inputs divergence doesn't apply. The dev-only
6
+ // controlled↔uncontrolled switch warning is intentionally not ported (repo policy:
7
+ // octane's warning surface differs; port the functional outcome only).
8
+ import { useCallback, useEffect, useInsertionEffect, useRef, useState } from 'octane';
9
+
10
+ import { S, splitSlot, subSlot } from './internal';
11
+
12
+ type SetStateFn<T> = (next: T | ((prev: T) => T)) => void;
13
+
14
+ export function useControllableState<T>(...args: any[]): [T, SetStateFn<T>] {
15
+ const [user, slotArg] = splitSlot(args);
16
+ const slot = slotArg ?? S('useControllableState');
17
+ const { prop, defaultProp, onChange } = (user[0] as any) ?? {};
18
+
19
+ const [uncontrolledProp, setUncontrolledProp, onChangeRef] = useUncontrolledState<T>(
20
+ defaultProp,
21
+ onChange,
22
+ slot,
23
+ );
24
+ const isControlled = prop !== undefined;
25
+ const value = (isControlled ? prop : uncontrolledProp) as T;
26
+
27
+ const setValue = useCallback(
28
+ ((nextValue: any) => {
29
+ if (isControlled) {
30
+ const resolved = isFunction(nextValue) ? nextValue(prop) : nextValue;
31
+ if (resolved !== prop) onChangeRef.current?.(resolved);
32
+ } else {
33
+ setUncontrolledProp(nextValue);
34
+ }
35
+ }) as SetStateFn<T>,
36
+ [isControlled, prop, setUncontrolledProp],
37
+ subSlot(slot, 'setValue'),
38
+ );
39
+
40
+ return [value, setValue];
41
+ }
42
+
43
+ // The uncontrolled half: plain state whose changes fire the LATEST onChange (synced pre-
44
+ // layout via useInsertionEffect, matching the source) from a post-commit effect.
45
+ function useUncontrolledState<T>(
46
+ defaultProp: T,
47
+ onChange: ((v: T) => void) | undefined,
48
+ slot: symbol | undefined,
49
+ ): [T, SetStateFn<T>, { current: ((v: T) => void) | undefined }] {
50
+ const [value, setValue] = useState<T>(defaultProp, subSlot(slot, 'state'));
51
+ const prevValueRef = useRef(value, subSlot(slot, 'prev'));
52
+
53
+ const onChangeRef = useRef(onChange, subSlot(slot, 'onChangeRef'));
54
+ useInsertionEffect(
55
+ () => {
56
+ onChangeRef.current = onChange;
57
+ },
58
+ [onChange],
59
+ subSlot(slot, 'onChangeEffect'),
60
+ );
61
+
62
+ useEffect(
63
+ () => {
64
+ if (prevValueRef.current !== value) {
65
+ onChangeRef.current?.(value);
66
+ prevValueRef.current = value;
67
+ }
68
+ },
69
+ [value],
70
+ subSlot(slot, 'changeEffect'),
71
+ );
72
+
73
+ return [value, setValue, onChangeRef];
74
+ }
75
+
76
+ function isFunction(value: unknown): value is (...args: any[]) => any {
77
+ return typeof value === 'function';
78
+ }
package/src/useId.ts ADDED
@@ -0,0 +1,15 @@
1
+ // Ported from @radix-ui/react-id. Radix ids are `radix-` + the framework's useId (so they
2
+ // read as Radix-owned in the DOM and never collide with app ids); a caller-supplied
3
+ // `deterministicId` wins. React's mount-guard dance (useState + layout-effect fallback for
4
+ // pre-18 React) collapses away — octane's `useId` is always available and SSR-stable.
5
+ import { useId as octaneUseId } from 'octane';
6
+
7
+ import { S, splitSlot } from './internal';
8
+
9
+ export function useId(...args: any[]): string {
10
+ const [user, slotArg] = splitSlot(args);
11
+ const slot = slotArg ?? S('useId');
12
+ const deterministicId = user[0] as string | undefined;
13
+ const id = octaneUseId(slot);
14
+ return deterministicId || `radix-${id}`;
15
+ }