@reshaped/headless 3.10.1 → 3.11.0-canary.1

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.
@@ -0,0 +1,11 @@
1
+ import { StoryObj } from "@storybook/react-vite";
2
+ declare const _default: {
3
+ title: string;
4
+ parameters: {
5
+ chromatic: {
6
+ disableSnapshot: boolean;
7
+ };
8
+ };
9
+ };
10
+ export default _default;
11
+ export declare const id: StoryObj;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { expect, waitFor } from "storybook/test";
3
+ import useElementId from "../useElementId.js";
4
+ export default {
5
+ title: "Hooks/useElementId",
6
+ parameters: {
7
+ chromatic: { disableSnapshot: true },
8
+ },
9
+ };
10
+ const Component = (props) => {
11
+ const id = useElementId(props.id);
12
+ return _jsx("div", { id: id, children: id });
13
+ };
14
+ export const id = {
15
+ name: "passed id",
16
+ render: () => {
17
+ return _jsx(Component, { id: "hey" });
18
+ },
19
+ play: async () => {
20
+ waitFor(() => {
21
+ expect(document.querySelector("#hey")).toBeTruthy();
22
+ });
23
+ },
24
+ };
@@ -0,0 +1,2 @@
1
+ declare const useElementId: (id?: string) => string;
2
+ export default useElementId;
@@ -0,0 +1,8 @@
1
+ import React from "react";
2
+ const useElementId = (id) => {
3
+ const generatedId = React.useId();
4
+ if (id)
5
+ return id;
6
+ return generatedId;
7
+ };
8
+ export default useElementId;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Singleton hook to check if multiple elements can be dismissed and returns flag only for the latest one
3
+ * Example: Use to only close the latest opened Flyout/Modal
4
+ */
5
+ import React from "react";
6
+ type Ref = React.RefObject<HTMLElement | null>;
7
+ declare const useIsDismissible: (args: {
8
+ active?: boolean;
9
+ contentRef: Ref;
10
+ triggerRef?: Ref;
11
+ }) => () => boolean;
12
+ export default useIsDismissible;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Singleton hook to check if multiple elements can be dismissed and returns flag only for the latest one
3
+ * Example: Use to only close the latest opened Flyout/Modal
4
+ */
5
+ import React from "react";
6
+ import useElementId from "./useElementId.js";
7
+ let queue = {};
8
+ let latestId = null;
9
+ const removeFromQueue = (id) => {
10
+ // Ignore removal of non-existing ids when called on component mount with active: false
11
+ if (!queue[id])
12
+ return;
13
+ if (id === latestId)
14
+ latestId = queue[id].parentId;
15
+ delete queue[id];
16
+ // Clear up all unused ids after the queue is resolved
17
+ if (latestId === null)
18
+ queue = {};
19
+ };
20
+ const addToQueue = (id, contentRef, triggerRef) => {
21
+ queue[id] = { parentId: latestId, triggerRef, contentRef };
22
+ latestId = id;
23
+ };
24
+ const useIsDismissible = (args) => {
25
+ const { active, contentRef, triggerRef } = args;
26
+ const id = useElementId();
27
+ React.useEffect(() => {
28
+ if (!active)
29
+ return;
30
+ addToQueue(id, contentRef, triggerRef);
31
+ return () => removeFromQueue(id);
32
+ }, [active, id, contentRef, triggerRef]);
33
+ return React.useCallback(() => {
34
+ if (!active)
35
+ return true;
36
+ const latest = latestId ? queue[latestId] : undefined;
37
+ const latestTrigger = latest?.triggerRef?.current;
38
+ const prev = latest?.parentId ? queue[latest.parentId] : undefined;
39
+ const prevContent = prev?.contentRef.current;
40
+ const nested = prevContent && latestTrigger && prevContent.contains(latestTrigger);
41
+ // Don't block independently rendered components that are not nested in each other
42
+ if (triggerRef?.current && !nested)
43
+ return true;
44
+ return !latestId || latestId === id;
45
+ }, [id, active, triggerRef]);
46
+ };
47
+ export default useIsDismissible;
package/dist/index.d.ts CHANGED
@@ -10,5 +10,7 @@ export { default as useIsomorphicLayoutEffect } from "./hooks/useIsomorphicLayou
10
10
  export { default as useOnClickOutside } from "./hooks/useOnClickOutside";
11
11
  export { default as useScrollLock } from "./hooks/useScrollLock";
12
12
  export { default as useToggle } from "./hooks/useToggle";
13
+ export { default as useElementId } from "./hooks/useElementId";
14
+ export { default as useIsDismissible } from "./hooks/useIsDismissible";
13
15
  export type { ClassName } from "@reshaped/utilities";
14
16
  export type { Attributes, CSSVariable, StyleAttribute } from "./types/global";
package/dist/index.js CHANGED
@@ -13,3 +13,5 @@ export { default as useIsomorphicLayoutEffect } from "./hooks/useIsomorphicLayou
13
13
  export { default as useOnClickOutside } from "./hooks/useOnClickOutside.js";
14
14
  export { default as useScrollLock } from "./hooks/useScrollLock.js";
15
15
  export { default as useToggle } from "./hooks/useToggle.js";
16
+ export { default as useElementId } from "./hooks/useElementId.js";
17
+ export { default as useIsDismissible } from "./hooks/useIsDismissible.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reshaped/headless",
3
3
  "description": "Headless components and utilities for React",
4
- "version": "3.10.1",
4
+ "version": "3.11.0-canary.1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://reshaped.so",
7
7
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "react-dom": "^18 || ^19"
40
40
  },
41
41
  "dependencies": {
42
- "@reshaped/utilities": "3.10.1"
42
+ "@reshaped/utilities": "3.11.0-canary.1"
43
43
  },
44
44
  "scripts": {
45
45
  "clean": "rm -rf dist",