etudes 6.2.2 → 7.0.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.
@@ -14,7 +14,7 @@ export type AccordionSelection = Record<number, number[]>;
14
14
  */
15
15
  export type AccordionSection<T> = Pick<CollectionProps<T>, 'isSelectionTogglable' | 'itemLength' | 'itemPadding' | 'items' | 'layout' | 'numSegments'> & {
16
16
  /**
17
- * Padding (in pixels) between the sectionheader and the internal collection.
17
+ * Padding (in pixels) between the section header and the internal collection.
18
18
  */
19
19
  collectionPadding?: number;
20
20
  /**
@@ -118,7 +118,7 @@ export const Accordion = forwardRef(({ children, style, autoCollapseSections = f
118
118
  const selection = sanitizeSelection(externalSelection ?? {});
119
119
  const expandedSectionIndices = sanitizeExpandedSectionIndices(externalExpandedSectionIndices ?? []);
120
120
  const fixedStyles = getFixedStyles({ orientation });
121
- const prevSelectionRef = useRef();
121
+ const prevSelectionRef = useRef(undefined);
122
122
  const prevSelection = prevSelectionRef.current;
123
123
  const components = asComponentDict(children, {
124
124
  collapseIcon: AccordionCollapseIcon,
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import clsx from 'clsx';
3
- import { Link } from 'react-router-dom';
3
+ import { Link } from 'react-router';
4
4
  export function Button({ children, className, href, isDisabled = false, label, opensInNewTab, to, type, ...props }) {
5
5
  if (href) {
6
6
  return (_jsx("a", { ...props, "aria-disabled": isDisabled, "aria-label": label, className: clsx(className, { disabled: isDisabled }), href: href, rel: opensInNewTab ? 'noopener,noreferrer' : undefined, target: opensInNewTab ? '_blank' : undefined, children: children ?? label }));
@@ -31,10 +31,10 @@ export const Carousel = forwardRef(({ autoAdvanceInterval = 0, index = 0, isDrag
31
31
  pointerUpPositionRef.current = undefined;
32
32
  };
33
33
  const normalizeScrollPosition = () => scrollToIndex(viewportRef, index, orientation);
34
- const prevIndexRef = useRef();
34
+ const prevIndexRef = useRef(undefined);
35
35
  const viewportRef = useRef(null);
36
- const pointerDownPositionRef = useRef();
37
- const pointerUpPositionRef = useRef();
36
+ const pointerDownPositionRef = useRef(undefined);
37
+ const pointerUpPositionRef = useRef(undefined);
38
38
  const [exposures, setExposures] = useState(getItemExposures(viewportRef, orientation));
39
39
  const [isPointerDown, setIsPointerDown] = useState(false);
40
40
  const fixedStyles = getFixedStyles({ scrollSnapEnabled: !isPointerDown, orientation });
@@ -114,7 +114,7 @@ export const Carousel = forwardRef(({ autoAdvanceInterval = 0, index = 0, isDrag
114
114
  return (_jsx("div", { ...props, ref: ref, role: 'region', onClick: event => handleClick(event), onPointerCancel: event => handlePointerUp(event), onPointerDown: event => handlePointerDown(event), onPointerLeave: event => handlePointerUp(event), onPointerUp: event => handlePointerUp(event), children: _jsx("div", { ref: viewportRef, style: styles(fixedStyles.viewport), children: _jsx(Each, { in: items, children: ({ style: itemStyle, ...itemProps }, idx) => (_jsx("div", { style: styles(fixedStyles.itemContainer), children: _jsx(ItemComponent, { "aria-hidden": idx !== index, exposure: tracksItemExposure ? exposures?.[idx] : undefined, style: styles(itemStyle, fixedStyles.item), ...itemProps }) })) }) }) }));
115
115
  });
116
116
  function scrollToIndex(ref, index, orientation) {
117
- const viewport = ref.current;
117
+ const viewport = ref?.current;
118
118
  if (!viewport)
119
119
  return;
120
120
  const top = orientation === 'horizontal' ? 0 : viewport.clientHeight * index;
@@ -124,7 +124,7 @@ function scrollToIndex(ref, index, orientation) {
124
124
  viewport.scrollTo({ top, left, behavior: 'smooth' });
125
125
  }
126
126
  function getItemExposures(ref, orientation) {
127
- const viewport = ref.current;
127
+ const viewport = ref?.current;
128
128
  if (!viewport)
129
129
  return undefined;
130
130
  const exposures = [];
@@ -134,7 +134,7 @@ function getItemExposures(ref, orientation) {
134
134
  return exposures;
135
135
  }
136
136
  function getItemExposureAt(idx, ref, orientation) {
137
- const viewport = ref.current;
137
+ const viewport = ref?.current;
138
138
  const child = viewport?.children[idx];
139
139
  if (!child)
140
140
  return 0;
@@ -79,7 +79,7 @@ export const Collection = forwardRef(({ className, style, isSelectionTogglable =
79
79
  };
80
80
  const selection = sanitizeSelection(externalSelection ?? []);
81
81
  const fixedStyles = getFixedStyles({ itemLength, itemPadding, layout, numSegments, orientation });
82
- const prevSelectionRef = useRef();
82
+ const prevSelectionRef = useRef(undefined);
83
83
  const prevSelection = prevSelectionRef.current;
84
84
  useEffect(() => {
85
85
  prevSelectionRef.current = selection;
@@ -61,7 +61,7 @@ export type DropdownProps<T extends DropdownItemData = DropdownItemData> = HTMLA
61
61
  */
62
62
  isInverted?: boolean;
63
63
  /**
64
- * Maximum number of items that are viside when the component expands. When a
64
+ * Maximum number of items that are visible when the component expands. When a
65
65
  * value greater than or equal to 0 is specified, only that number of items
66
66
  * will be visible at a time and a scrollbar will appear to enable scrolling
67
67
  * to remaining items. Any value less than 0 indicates that all items will be
@@ -34,7 +34,7 @@ export function WithTooltip({ children, className, style, alignment: externalAli
34
34
  dialogRef.current.ariaHidden = 'true';
35
35
  };
36
36
  const targetRef = useRef(null);
37
- const dialogRef = useRef();
37
+ const dialogRef = useRef(undefined);
38
38
  const targetRect = useRect(targetRef);
39
39
  useEffect(() => {
40
40
  const dialogNode = createDialog();
@@ -1,2 +1,2 @@
1
1
  import { type DependencyList, type RefObject } from 'react';
2
- export declare function useClickOutsideEffect(targetRef: RefObject<HTMLElement>, handler: () => void, deps?: DependencyList): void;
2
+ export declare function useClickOutsideEffect(targetRef: RefObject<HTMLElement> | RefObject<HTMLElement | undefined> | RefObject<HTMLElement | null>, handler: () => void, deps?: DependencyList): void;
@@ -44,5 +44,5 @@ type Options = {
44
44
  *
45
45
  * @returns The states created for this effect.
46
46
  */
47
- export declare function useDragEffect(targetRef: RefObject<HTMLElement>, { isEnabled, updatesCursor, onDragStart, onDragMove, onDragEnd, }: Options, deps?: DependencyList): void;
47
+ export declare function useDragEffect(targetRef: RefObject<HTMLElement> | RefObject<HTMLElement | undefined> | RefObject<HTMLElement | null>, { isEnabled, updatesCursor, onDragStart, onDragMove, onDragEnd, }: Options, deps?: DependencyList): void;
48
48
  export {};
@@ -13,8 +13,8 @@ import { Point } from 'spase';
13
13
  */
14
14
  export function useDragEffect(targetRef, { isEnabled = true, updatesCursor = true, onDragStart, onDragMove, onDragEnd, }, deps = []) {
15
15
  const element = targetRef.current;
16
- const startPositionRef = useRef();
17
- const dragPositionRef = useRef();
16
+ const startPositionRef = useRef(undefined);
17
+ const dragPositionRef = useRef(undefined);
18
18
  if (updatesCursor && element)
19
19
  element.style.cursor = 'grab';
20
20
  useEffect(() => {
@@ -52,5 +52,5 @@ type Options<T> = Omit<InteractDraggableOptions, 'onstart' | 'onmove' | 'onend'>
52
52
  *
53
53
  * @returns The states created for this effect.
54
54
  */
55
- export declare function useDragValueEffect<T = [number, number]>(targetRef: RefObject<HTMLElement>, { initialValue, transform, onDragStart, onDragMove, onDragEnd, ...options }: Options<T>, deps?: DependencyList): ReturnedStates<T>;
55
+ export declare function useDragValueEffect<T = [number, number]>(targetRef: RefObject<HTMLElement> | RefObject<HTMLElement | undefined> | RefObject<HTMLElement | null>, { initialValue, transform, onDragStart, onDragMove, onDragEnd, ...options }: Options<T>, deps?: DependencyList): ReturnedStates<T>;
56
56
  export {};
@@ -7,7 +7,7 @@ type Options = {
7
7
  shouldInvokeInitially?: boolean;
8
8
  };
9
9
  /**
10
- * Hoook for invoking a method repeatedly on every set interval.
10
+ * Hook for invoking a method repeatedly on every set interval.
11
11
  *
12
12
  * @param handler The method to invoke on every interval.
13
13
  * @param interval Time (in milliseconds) between each invocation.
@@ -1,6 +1,6 @@
1
1
  import { useEffect, useRef } from 'react';
2
2
  /**
3
- * Hoook for invoking a method repeatedly on every set interval.
3
+ * Hook for invoking a method repeatedly on every set interval.
4
4
  *
5
5
  * @param handler The method to invoke on every interval.
6
6
  * @param interval Time (in milliseconds) between each invocation.
@@ -8,7 +8,7 @@ import { useEffect, useRef } from 'react';
8
8
  * @param deps Dependencies that trigger this effect.
9
9
  */
10
10
  export function useInterval(handler, interval, { shouldInvokeInitially = false } = {}, deps = []) {
11
- const handlerRef = useRef();
11
+ const handlerRef = useRef(undefined);
12
12
  useEffect(() => {
13
13
  handlerRef.current = handler;
14
14
  }, [handler]);
@@ -6,7 +6,7 @@ import { useEffect, useRef } from 'react';
6
6
  * @param options See {@link Options}.
7
7
  */
8
8
  export function usePrevious(value, { sanitizeDependency = t => t } = {}) {
9
- const ref = useRef();
9
+ const ref = useRef(undefined);
10
10
  useEffect(() => {
11
11
  ref.current = value;
12
12
  }, [sanitizeDependency(value)]);
@@ -8,4 +8,4 @@ import { Rect } from 'spase';
8
8
  *
9
9
  * @returns The most current {@link Rect} of the target element.
10
10
  */
11
- export declare function useRect(targetRef: RefObject<HTMLElement>): Rect;
11
+ export declare function useRect(targetRef: RefObject<HTMLElement> | RefObject<HTMLElement | undefined> | RefObject<HTMLElement | null>): Rect;
@@ -14,4 +14,4 @@ export type UseResizeEffectOptions = {
14
14
  * @param options See {@link Options}.
15
15
  * @param deps Additional dependencies.
16
16
  */
17
- export declare function useResizeEffect(targetRef: RefObject<HTMLElement>, { onResize }?: UseResizeEffectOptions, deps?: DependencyList): void;
17
+ export declare function useResizeEffect(targetRef: RefObject<HTMLElement> | RefObject<HTMLElement | undefined> | RefObject<HTMLElement | null>, { onResize }?: UseResizeEffectOptions, deps?: DependencyList): void;
@@ -23,7 +23,7 @@ export function useScrollPositionEffect({ onChange }, deps = []) {
23
23
  step,
24
24
  };
25
25
  };
26
- const prevInfo = useRef();
26
+ const prevInfo = useRef(undefined);
27
27
  useEffect(() => {
28
28
  window.addEventListener('scroll', handleScrollPositionChange);
29
29
  window.addEventListener('resize', handleScrollPositionChange);
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useState } from 'react';
2
- import { useSearchParams } from 'react-router-dom';
2
+ import { useSearchParams } from 'react-router';
3
3
  /**
4
4
  * Hook for mapping a search param to a state. Whenever the value of the target
5
5
  * search param changes, the mapped state will change as well, and vice versa.
@@ -7,4 +7,4 @@ import { type Size } from 'spase';
7
7
  *
8
8
  * @returns The most current {@link Size} of the target element.
9
9
  */
10
- export declare function useSize(targetRef: RefObject<HTMLElement>): Size;
10
+ export declare function useSize(targetRef: RefObject<HTMLElement> | RefObject<HTMLElement | undefined> | RefObject<HTMLElement | null>): Size;
@@ -6,7 +6,7 @@ type Options = {
6
6
  type ReturnValue = {
7
7
  start: () => void;
8
8
  stop: () => void;
9
- ref: RefObject<NodeJS.Timeout | undefined>;
9
+ ref: RefObject<NodeJS.Timeout> | RefObject<NodeJS.Timeout | undefined> | RefObject<NodeJS.Timeout | null>;
10
10
  };
11
11
  export declare function useTimeout(timeout?: number, { autoStart, onTimeout }?: Options, deps?: DependencyList): ReturnValue;
12
12
  export {};
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useEffect, useRef } from 'react';
2
2
  export function useTimeout(timeout = 0, { autoStart = true, onTimeout } = {}, deps = []) {
3
- const timeoutRef = useRef();
4
- const handlerRef = useRef();
3
+ const timeoutRef = useRef(undefined);
4
+ const handlerRef = useRef(undefined);
5
5
  const start = useCallback(() => {
6
6
  stop();
7
7
  if (timeout < 0)
package/package.json CHANGED
@@ -1,16 +1,17 @@
1
1
  {
2
2
  "name": "etudes",
3
- "version": "6.2.2",
3
+ "version": "7.0.0",
4
4
  "description": "A study of headless React components",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "clean": "rimraf lib",
8
- "prebuild": "npm run lint && npm run clean",
8
+ "prebuild": "npm run clean && npm run lint",
9
9
  "build": "tsc",
10
10
  "prepages": "rimraf .gh-pages",
11
11
  "pages": "vite build -c demo/vite.config.ts",
12
12
  "dev": "concurrently \"tsc -w\" \"wait-on -t 30s lib && vite dev -c demo/vite.config.ts --port 8080\"",
13
- "test": "echo \"No tests yet :)\"",
13
+ "test": "npm run unit",
14
+ "unit": "vitest run --coverage",
14
15
  "lint": "eslint",
15
16
  "lint:fix": "eslint --fix"
16
17
  },
@@ -34,46 +35,47 @@
34
35
  "utils"
35
36
  ],
36
37
  "devDependencies": {
37
- "@eslint/js": "^9.10.0",
38
+ "@commitlint/config-conventional": "^19.6.0",
39
+ "@eslint/js": "^9.17.0",
38
40
  "@semantic-release/changelog": "^6.0.3",
39
41
  "@semantic-release/git": "^10.0.1",
40
- "@stylistic/eslint-plugin": "^2.8.0",
41
- "@types/node": "^22.5.4",
42
- "@types/react": "^18.3.5",
43
- "@types/react-dom": "^18.3.0",
44
- "@vitejs/plugin-react": "^4.3.1",
42
+ "@stylistic/eslint-plugin": "^2.12.1",
43
+ "@types/node": "^22.10.2",
44
+ "@types/react": "^19.0.2",
45
+ "@types/react-dom": "^19.0.2",
46
+ "@vitejs/plugin-react": "^4.3.4",
47
+ "@vitest/coverage-v8": "^2.1.8",
45
48
  "autoprefixer": "^10.4.20",
46
- "concurrently": "^9.0.0",
47
- "eslint": "^9.10.0",
48
- "eslint-plugin-tailwindcss": "^3.17.4",
49
- "postcss": "^8.4.45",
50
- "react": "^18.3.1",
51
- "react-dom": "^18.3.1",
52
- "react-router": "^6.26.2",
53
- "react-router-dom": "^6.26.2",
49
+ "concurrently": "^9.1.0",
50
+ "eslint": "^9.17.0",
51
+ "eslint-plugin-tailwindcss": "^3.17.5",
52
+ "postcss": "^8.4.49",
53
+ "react": "^19.0.0",
54
+ "react-dom": "^19.0.0",
55
+ "react-router": "^7.0.2",
54
56
  "rimraf": "^6.0.1",
55
- "semantic-release": "^24.1.1",
56
- "tailwindcss": "^3.4.10",
57
+ "semantic-release": "^24.2.0",
58
+ "tailwindcss": "^3.4.17",
57
59
  "tailwindcss-safe-area": "^0.6.0",
58
- "typescript": "^5.6.2",
59
- "typescript-eslint": "^8.5.0",
60
- "vite": "^5.4.4",
60
+ "typescript": "^5.7.2",
61
+ "typescript-eslint": "^8.18.1",
62
+ "vite": "^6.0.4",
63
+ "vitest": "^2.1.8",
61
64
  "wait-on": "^8.0.1"
62
65
  },
63
66
  "dependencies": {
64
67
  "clsx": "^2.1.1",
65
68
  "fast-deep-equal": "^3.1.3",
66
- "fast-xml-parser": "^4.5.0",
69
+ "fast-xml-parser": "^4.5.1",
67
70
  "interactjs": "^1.10.27",
68
71
  "resize-observer-polyfill": "^1.5.1",
69
- "spase": "^9.0.1"
72
+ "spase": "^9.1.0"
70
73
  },
71
74
  "peerDependencies": {
72
- "react": "^18.2.0"
75
+ "react": "^19.0.0"
73
76
  },
74
77
  "optionalDependencies": {
75
- "react-router": "^6.26.2",
76
- "react-router-dom": "^6.26.2"
78
+ "react-router": "^7.0.2"
77
79
  },
78
- "packageManager": "pnpm@9.7.0+sha512.dc09430156b427f5ecfc79888899e1c39d2d690f004be70e05230b72cb173d96839587545d09429b55ac3c429c801b4dc3c0e002f653830a420fa2dd4e3cf9cf"
80
+ "packageManager": "pnpm@9.15.0"
79
81
  }
@@ -11,5 +11,5 @@ type ScrollPositionContextValue = ScrollPosition & {
11
11
  type ScrollPositionProviderProps = PropsWithChildren;
12
12
  export declare const ScrollPositionContext: import("react").Context<ScrollPositionContextValue | undefined>;
13
13
  export declare function ScrollPositionProvider({ children, }: Readonly<ScrollPositionProviderProps>): import("react/jsx-runtime").JSX.Element;
14
- export declare function useScrollPosition(targetRef?: RefObject<Element>): ScrollPosition;
14
+ export declare function useScrollPosition(targetRef?: RefObject<Element> | RefObject<Element | undefined> | RefObject<Element | null>): ScrollPosition;
15
15
  export {};
@@ -1,4 +1,4 @@
1
- import { type JSXElementConstructor, type ReactNode } from 'react';
1
+ import { type JSX, type JSXElementConstructor, type ReactNode } from 'react';
2
2
  type ComponentTypeDict = Record<string, JSXElementConstructor<any>>;
3
3
  type ComponentElementDict<T extends ComponentTypeDict> = Record<keyof T, JSX.Element>;
4
4
  export declare function asComponentDict<T extends ComponentTypeDict>(children?: ReactNode, typeDict?: T): Partial<ComponentElementDict<T>>;
@@ -1,4 +1,4 @@
1
- import { type Attributes, type CElement, type ClassAttributes, type Component, type ComponentState, type FunctionComponentElement, type ReactElement, type ReactNode } from 'react';
1
+ import { type Attributes, type ClassAttributes, type Component, type ComponentState, type ReactElement, type ReactNode } from 'react';
2
2
  /**
3
3
  * Wrapper for {@link cloneElement} but instead of overwriting `className` and
4
4
  * `style` of the cloned element with the values specified in the `props`
@@ -12,6 +12,6 @@ import { type Attributes, type CElement, type ClassAttributes, type Component, t
12
12
  *
13
13
  * @returns The cloned element.
14
14
  */
15
- export declare function cloneStyledElement<P>(element: FunctionComponentElement<P>, props?: Partial<P> & Attributes, ...children: ReactNode[]): FunctionComponentElement<P>;
16
- export declare function cloneStyledElement<P, T extends Component<P, ComponentState>>(element: CElement<P, T>, props?: Partial<P> & ClassAttributes<T>, ...children: ReactNode[]): CElement<P, T>;
15
+ export declare function cloneStyledElement<P>(element: ReactElement<P, React.FunctionComponent<P>>, props?: Partial<P> & Attributes, ...children: ReactNode[]): ReactElement<P, React.FunctionComponent<P>>;
16
+ export declare function cloneStyledElement<P, T extends Component<P, ComponentState>>(element: ReactElement<P, React.ComponentClass<T>>, props?: Partial<P> & ClassAttributes<T>, ...children: ReactNode[]): ReactElement<P, React.ComponentClass<T>>;
17
17
  export declare function cloneStyledElement<P>(element: ReactElement<P>, props?: Partial<P> & Attributes, ...children: ReactNode[]): ReactElement<P>;