@blockle/blocks-react 3.0.1 → 3.1.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.
Files changed (66) hide show
  1. package/README.md +138 -0
  2. package/dist/components/feedback/Alert/Alert.d.ts +6 -0
  3. package/dist/components/feedback/Alert/Alert.d.ts.map +1 -1
  4. package/dist/components/feedback/Alert/Alert.js +2 -0
  5. package/dist/components/feedback/Toast/Toast.css.d.ts +10 -1
  6. package/dist/components/feedback/Toast/Toast.css.d.ts.map +1 -1
  7. package/dist/components/feedback/Toast/Toast.css.js +37 -10
  8. package/dist/components/feedback/Toast/ToastProvider.d.ts +9 -1
  9. package/dist/components/feedback/Toast/ToastProvider.d.ts.map +1 -1
  10. package/dist/components/feedback/Toast/ToastProvider.js +47 -24
  11. package/dist/components/form/Checkbox/Checkbox.d.ts +0 -1
  12. package/dist/components/form/Checkbox/Checkbox.d.ts.map +1 -1
  13. package/dist/components/form/Checkbox/Checkbox.js +0 -2
  14. package/dist/components/form/Checkbox/checkbox.css.d.ts.map +1 -1
  15. package/dist/components/form/Checkbox/checkbox.css.js +1 -0
  16. package/dist/components/form/Radio/Radio.d.ts +0 -1
  17. package/dist/components/form/Radio/Radio.d.ts.map +1 -1
  18. package/dist/components/form/Radio/Radio.js +1 -11
  19. package/dist/components/form/Radio/radio.css.d.ts.map +1 -1
  20. package/dist/components/form/Radio/radio.css.js +1 -0
  21. package/dist/components/form/Select/Select.d.ts +2 -1
  22. package/dist/components/form/Select/Select.d.ts.map +1 -1
  23. package/dist/components/form/Select/Select.js +2 -1
  24. package/dist/components/form/Switch/Switch.d.ts +5 -1
  25. package/dist/components/form/Switch/Switch.d.ts.map +1 -1
  26. package/dist/components/form/Switch/Switch.js +19 -29
  27. package/dist/components/form/Switch/switch.css.d.ts.map +1 -1
  28. package/dist/components/form/Switch/switch.css.js +1 -0
  29. package/dist/components/form/TextInput/TextInput.d.ts +0 -1
  30. package/dist/components/form/TextInput/TextInput.d.ts.map +1 -1
  31. package/dist/components/form/TextInput/TextInput.js +0 -2
  32. package/dist/components/overlay/Dialog/Dialog.d.ts +4 -1
  33. package/dist/components/overlay/Dialog/Dialog.d.ts.map +1 -1
  34. package/dist/components/overlay/Dialog/Dialog.js +19 -46
  35. package/dist/components/overlay/Drawer/Drawer.d.ts +9 -2
  36. package/dist/components/overlay/Drawer/Drawer.d.ts.map +1 -1
  37. package/dist/components/overlay/Drawer/Drawer.js +25 -44
  38. package/dist/components/overlay/Drawer/drawer.css.d.ts.map +1 -1
  39. package/dist/components/overlay/Drawer/drawer.css.js +4 -8
  40. package/dist/components/overlay/Popover/Popover.d.ts +15 -3
  41. package/dist/components/overlay/Popover/Popover.d.ts.map +1 -1
  42. package/dist/components/overlay/Popover/Popover.js +35 -11
  43. package/dist/components/overlay/Popover/popover-utils.d.ts +38 -1
  44. package/dist/components/overlay/Popover/popover-utils.d.ts.map +1 -1
  45. package/dist/components/overlay/Popover/popover-utils.js +92 -41
  46. package/dist/components/overlay/Tooltip/Tooltip.d.ts +8 -0
  47. package/dist/components/overlay/Tooltip/Tooltip.d.ts.map +1 -1
  48. package/dist/components/overlay/Tooltip/Tooltip.js +3 -2
  49. package/dist/components/overlay/requestCloseReason.d.ts +6 -0
  50. package/dist/components/overlay/requestCloseReason.d.ts.map +1 -0
  51. package/dist/components/overlay/useModalOverlay.d.ts +35 -0
  52. package/dist/components/overlay/useModalOverlay.d.ts.map +1 -0
  53. package/dist/components/overlay/useModalOverlay.js +85 -0
  54. package/dist/hooks/useClickOutside/useClickOutside.d.ts.map +1 -1
  55. package/dist/hooks/useClickOutside/useClickOutside.js +27 -7
  56. package/dist/hooks/useModalDialog/useModalDialog.d.ts +13 -0
  57. package/dist/hooks/useModalDialog/useModalDialog.d.ts.map +1 -0
  58. package/dist/hooks/useModalDialog/useModalDialog.js +39 -0
  59. package/dist/index.d.ts +3 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/testUtils/browser/pointer.d.ts +18 -0
  62. package/dist/testUtils/browser/pointer.d.ts.map +1 -0
  63. package/package.json +2 -2
  64. package/dist/components/overlay/Dialog/dialogHelper.d.ts +0 -7
  65. package/dist/components/overlay/Dialog/dialogHelper.d.ts.map +0 -1
  66. package/dist/components/overlay/Dialog/dialogHelper.js +0 -22
@@ -1,57 +1,108 @@
1
1
  import { getOriginalElementSize, cssValueToNumber } from "@blockle/blocks-core";
2
- function getPopoverPosition(position, anchorRef, popoverRef) {
2
+ function computePopoverPosition({
3
+ placement,
4
+ anchor,
5
+ popover,
6
+ viewport
7
+ }) {
8
+ const [preferredSide, edge] = placement.split("-");
9
+ const { margin } = popover;
10
+ const side = flipSide(preferredSide, anchor, popover, viewport);
11
+ let left;
12
+ let top;
13
+ if (side === "top" || side === "bottom") {
14
+ top = side === "top" ? anchor.top - margin.bottom - popover.height : anchor.top + anchor.height + margin.top;
15
+ left = alignOnCrossAxis(
16
+ edge,
17
+ anchor.left,
18
+ anchor.width,
19
+ popover.width,
20
+ viewport.width
21
+ );
22
+ } else {
23
+ left = side === "left" ? anchor.left - margin.right - popover.width : anchor.left + anchor.width + margin.left;
24
+ top = alignOnCrossAxis(
25
+ edge,
26
+ anchor.top,
27
+ anchor.height,
28
+ popover.height,
29
+ viewport.height
30
+ );
31
+ }
32
+ return [
33
+ left - margin.left + viewport.scrollX,
34
+ top - margin.top + viewport.scrollY
35
+ ];
36
+ }
37
+ const VIEWPORT_PADDING = 8;
38
+ const oppositeSide = {
39
+ top: "bottom",
40
+ bottom: "top",
41
+ left: "right",
42
+ right: "left"
43
+ };
44
+ function flipSide(side, anchor, popover, viewport) {
45
+ const { margin } = popover;
46
+ const room = {
47
+ top: anchor.top,
48
+ bottom: viewport.height - anchor.top - anchor.height,
49
+ left: anchor.left,
50
+ right: viewport.width - anchor.left - anchor.width
51
+ };
52
+ const needed = side === "top" || side === "bottom" ? popover.height + margin.top + margin.bottom : popover.width + margin.left + margin.right;
53
+ const opposite = oppositeSide[side];
54
+ if (room[side] < needed && room[opposite] > room[side]) {
55
+ return opposite;
56
+ }
57
+ return side;
58
+ }
59
+ function alignOnCrossAxis(edge, anchorStart, anchorSize, popoverSize, viewportSize) {
60
+ let start = anchorStart + (anchorSize - popoverSize) / 2;
61
+ if (edge === "start") {
62
+ start = anchorStart;
63
+ } else if (edge === "end") {
64
+ start = anchorStart + anchorSize - popoverSize;
65
+ }
66
+ const max = viewportSize - VIEWPORT_PADDING - popoverSize;
67
+ return Math.max(VIEWPORT_PADDING, Math.min(start, max));
68
+ }
69
+ function getPopoverPosition(placement, anchorRef, popoverRef) {
3
70
  if (!anchorRef.current || !popoverRef.current) {
4
71
  return [0, 0];
5
72
  }
6
73
  const anchorRect = anchorRef.current.getBoundingClientRect();
7
74
  const popoverRect = popoverRef.current.getBoundingClientRect();
8
75
  const popoverStyles = getComputedStyle(popoverRef.current);
9
- const [popoverWidth, popoverHeight] = getOriginalElementSize(
76
+ const [width, height] = getOriginalElementSize(
10
77
  popoverStyles,
11
78
  popoverRect.width,
12
79
  popoverRect.height
13
80
  );
14
- const marginTop = cssValueToNumber(
15
- popoverStyles.getPropertyValue("margin-top")
16
- );
17
- const marginRight = cssValueToNumber(
18
- popoverStyles.getPropertyValue("margin-right")
19
- );
20
- const marginBottom = cssValueToNumber(
21
- popoverStyles.getPropertyValue("margin-bottom")
22
- );
23
- const marginLeft = cssValueToNumber(
24
- popoverStyles.getPropertyValue("margin-left")
25
- );
26
- const marginY = marginTop + marginBottom;
27
- const marginX = marginRight + marginLeft;
28
- const docHeight = document.documentElement.clientHeight;
29
- const docWidth = document.documentElement.clientWidth;
30
- const docScrollTop = document.documentElement.scrollTop;
31
- const docScrollLeft = document.documentElement.scrollLeft;
32
- const anchorLeft = anchorRect.left + docScrollLeft;
33
- const anchorTop = anchorRect.top + docScrollTop;
34
- const topPosition = anchorRect.top - (popoverHeight + marginTop);
35
- const rightPosition = anchorRect.left + anchorRect.width + popoverWidth;
36
- const bottomPosition = anchorRect.top + anchorRect.height + popoverHeight;
37
- const leftPosition = anchorRect.left - popoverWidth;
38
- const offsetX = anchorLeft - marginLeft - (popoverWidth - anchorRect.width) / 2;
39
- const offsetY = anchorTop - marginTop - (popoverHeight - anchorRect.height) / 2;
40
- switch (position) {
41
- case "top": {
42
- return topPosition > 0 ? [offsetX, anchorTop - popoverHeight - marginY] : [offsetX, anchorTop + anchorRect.height];
43
- }
44
- case "bottom": {
45
- return bottomPosition < docHeight || topPosition < 0 ? [offsetX, anchorTop + anchorRect.height] : [offsetX, anchorTop - popoverHeight - marginY];
81
+ const { documentElement } = document;
82
+ return computePopoverPosition({
83
+ placement,
84
+ anchor: anchorRect,
85
+ popover: {
86
+ width,
87
+ height,
88
+ margin: {
89
+ top: cssValueToNumber(popoverStyles.getPropertyValue("margin-top")),
90
+ right: cssValueToNumber(popoverStyles.getPropertyValue("margin-right")),
91
+ bottom: cssValueToNumber(
92
+ popoverStyles.getPropertyValue("margin-bottom")
93
+ ),
94
+ left: cssValueToNumber(popoverStyles.getPropertyValue("margin-left"))
95
+ }
96
+ },
97
+ viewport: {
98
+ width: documentElement.clientWidth,
99
+ height: documentElement.clientHeight,
100
+ scrollX: documentElement.scrollLeft,
101
+ scrollY: documentElement.scrollTop
46
102
  }
47
- case "left": {
48
- return leftPosition > docWidth || leftPosition > 0 ? [anchorLeft - popoverWidth - marginX, offsetY] : [anchorLeft + anchorRect.width, offsetY];
49
- }
50
- case "right": {
51
- return rightPosition < docWidth || leftPosition < 0 ? [anchorLeft + anchorRect.width, offsetY] : [anchorLeft - popoverWidth - marginX, offsetY];
52
- }
53
- }
103
+ });
54
104
  }
55
105
  export {
106
+ computePopoverPosition,
56
107
  getPopoverPosition
57
108
  };
@@ -3,6 +3,14 @@ import { PopoverProps } from '../Popover/Popover.js';
3
3
  type ReactElement = React.ReactElement<any, string | React.JSXElementConstructor<any>>;
4
4
  type TooltipTheme = ComponentThemes['tooltip'];
5
5
  export type TooltipProps = {
6
+ /**
7
+ * Side of the child to open on, and which of its edges to line up with
8
+ * @default 'top'
9
+ */
10
+ placement?: PopoverProps['placement'];
11
+ /**
12
+ * @deprecated Use `placement`, which wins when both are passed
13
+ */
6
14
  position?: PopoverProps['position'];
7
15
  children: ReactElement;
8
16
  content: React.ReactNode;
@@ -1 +1 @@
1
- {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../../src/components/overlay/Tooltip/Tooltip.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAY9B,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEnE,KAAK,YAAY,GAAG,KAAK,CAAC,YAAY,CAEpC,GAAG,EAEH,MAAM,GAAG,KAAK,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAC1C,CAAC;AAEF,KAAK,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;IACpC,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC7C,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,CAAC;AAE/D,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CA+E1C,CAAC"}
1
+ {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../../../../src/components/overlay/Tooltip/Tooltip.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAY9B,OAAO,EAAW,KAAK,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEnE,KAAK,YAAY,GAAG,KAAK,CAAC,YAAY,CAEpC,GAAG,EAEH,MAAM,GAAG,KAAK,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAC1C,CAAC;AAEF,KAAK,YAAY,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;AAE/C,MAAM,MAAM,YAAY,GAAG;IACzB;;;OAGG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,CAAC;IACtC;;OAEG;IACH,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;IACpC,QAAQ,EAAE,YAAY,CAAC;IACvB,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC,QAAQ,CAAC,CAAC;CAC7C,GAAG,IAAI,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,KAAK,GAAG,UAAU,CAAC,CAAC;AAE/D,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAAC,YAAY,CAgF1C,CAAC"}
@@ -5,7 +5,8 @@ import { useId, useRef, useState, useEffect, Children, isValidElement, cloneElem
5
5
  import { useComponentStyles } from "../../../hooks/useComponentStyles/useComponentStyles.js";
6
6
  import { Popover } from "../Popover/Popover.js";
7
7
  const Tooltip = ({
8
- position = "top",
8
+ placement,
9
+ position,
9
10
  children,
10
11
  content,
11
12
  intent
@@ -61,7 +62,7 @@ const Tooltip = ({
61
62
  onRequestClose: () => {
62
63
  setOpen(false);
63
64
  },
64
- position,
65
+ placement: placement ?? position,
65
66
  className: tooltipClassName,
66
67
  children: content
67
68
  }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Why an overlay asks to close: an Escape (or another close request, such as
3
+ * the Android back gesture), or a click outside of it.
4
+ */
5
+ export type RequestCloseReason = 'escape' | 'outside-click';
6
+ //# sourceMappingURL=requestCloseReason.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requestCloseReason.d.ts","sourceRoot":"","sources":["../../../src/components/overlay/requestCloseReason.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,eAAe,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { RequestCloseReason } from './requestCloseReason.js';
2
+ type DialogContextValue = {
3
+ setEnabled: (active: boolean) => void;
4
+ };
5
+ export declare const DialogContext: import('react').Context<DialogContextValue | undefined>;
6
+ export declare const useNestedDialog: (open: boolean) => boolean;
7
+ type UseDialogCloseRequestsOptions = {
8
+ enabled: boolean;
9
+ onRequestClose: (reason: RequestCloseReason) => void;
10
+ onKeyDown?: React.KeyboardEventHandler<HTMLDialogElement>;
11
+ onKeyDownCapture?: React.KeyboardEventHandler<HTMLDialogElement>;
12
+ onCancel?: React.ReactEventHandler<HTMLDialogElement>;
13
+ };
14
+ type UseModalOverlayOptions = Omit<UseDialogCloseRequestsOptions, 'enabled'> & {
15
+ open: boolean;
16
+ };
17
+ /**
18
+ * The behaviour Dialog and Drawer share while open: focus is restored when
19
+ * closed, the body does not scroll, and an Escape, close request or click
20
+ * outside requests to close.
21
+ *
22
+ * While a nested Dialog or Drawer is open, the outer one ignores Escape and
23
+ * clicks outside, and leaves the body scroll lock to the outermost one.
24
+ * Provide `contextValue` through DialogContext to the dialog's content.
25
+ */
26
+ export declare const useModalOverlay: (dialogRef: React.RefObject<HTMLDialogElement | null>, { open, onRequestClose, ...handlers }: UseModalOverlayOptions) => {
27
+ contextValue: DialogContextValue;
28
+ closeRequestProps: {
29
+ onKeyDown: (event: React.KeyboardEvent<HTMLDialogElement>) => void;
30
+ onKeyDownCapture: (event: React.KeyboardEvent<HTMLDialogElement>) => void;
31
+ onCancel: (event: React.SyntheticEvent<HTMLDialogElement>) => void;
32
+ };
33
+ };
34
+ export {};
35
+ //# sourceMappingURL=useModalOverlay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useModalOverlay.d.ts","sourceRoot":"","sources":["../../../src/components/overlay/useModalOverlay.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,KAAK,kBAAkB,GAAG;IACxB,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,aAAa,yDAEzB,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,MAAM,OAAO,KAAG,OAgB/C,CAAC;AAEF,KAAK,6BAA6B,GAAG;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,IAAI,CAAC;IACrD,SAAS,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IAC1D,gBAAgB,CAAC,EAAE,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,CAAC;IACjE,QAAQ,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,iBAAiB,CAAC,CAAC;CACvD,CAAC;AA6EF,KAAK,sBAAsB,GAAG,IAAI,CAAC,6BAA6B,EAAE,SAAS,CAAC,GAAG;IAC7E,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EACpD,uCAAuC,sBAAsB;;;2BA/D/B,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC;kCAR3D,KAAK,CAAC,aAAa,CAAC,iBAAiB,CAAC;0BA2BlB,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC;;CAqErE,CAAC"}
@@ -0,0 +1,85 @@
1
+ "use client";
2
+ import { useState, createContext, useContext, useEffect, useRef } from "react";
3
+ import { useClickOutside } from "../../hooks/useClickOutside/useClickOutside.js";
4
+ import { usePreventBodyScroll } from "../../hooks/usePreventBodyScroll/usePreventBodyScroll.js";
5
+ import { useRestoreFocus } from "../../hooks/useRestoreFocus/useRestoreFocus.js";
6
+ const DialogContext = createContext(
7
+ void 0
8
+ );
9
+ const useNestedDialog = (open) => {
10
+ const parentDialog = useContext(DialogContext);
11
+ useEffect(() => {
12
+ if (!parentDialog || !open) {
13
+ return;
14
+ }
15
+ parentDialog.setEnabled(false);
16
+ return () => {
17
+ parentDialog.setEnabled(true);
18
+ };
19
+ }, [parentDialog, open]);
20
+ return !!parentDialog;
21
+ };
22
+ const useDialogCloseRequests = ({
23
+ enabled,
24
+ onRequestClose,
25
+ onKeyDown,
26
+ onKeyDownCapture,
27
+ onCancel
28
+ }) => {
29
+ const stoppedEscapeRef = useRef(null);
30
+ const handleKeyDownCapture = (event) => {
31
+ onKeyDownCapture?.(event);
32
+ stoppedEscapeRef.current = event.key === "Escape" ? event.nativeEvent : null;
33
+ };
34
+ const handleKeyDown = (event) => {
35
+ onKeyDown?.(event);
36
+ if (event.key !== "Escape") {
37
+ return;
38
+ }
39
+ stoppedEscapeRef.current = null;
40
+ if (!enabled || event.defaultPrevented) {
41
+ return;
42
+ }
43
+ event.preventDefault();
44
+ onRequestClose("escape");
45
+ };
46
+ const handleCancel = (event) => {
47
+ onCancel?.(event);
48
+ if (event.target !== event.currentTarget) {
49
+ return;
50
+ }
51
+ event.preventDefault();
52
+ const stoppedEscape = stoppedEscapeRef.current;
53
+ const claimed = stoppedEscape !== null && !stoppedEscape.defaultPrevented;
54
+ stoppedEscapeRef.current = null;
55
+ if (enabled && !claimed) {
56
+ onRequestClose("escape");
57
+ }
58
+ };
59
+ return {
60
+ onKeyDown: handleKeyDown,
61
+ onKeyDownCapture: handleKeyDownCapture,
62
+ onCancel: handleCancel
63
+ };
64
+ };
65
+ const useModalOverlay = (dialogRef, { open, onRequestClose, ...handlers }) => {
66
+ const [enabled, setEnabled] = useState(true);
67
+ const [contextValue] = useState(() => ({ setEnabled }));
68
+ useRestoreFocus(open);
69
+ const isNested = useNestedDialog(open);
70
+ usePreventBodyScroll(open && !isNested);
71
+ useClickOutside(dialogRef, () => onRequestClose("outside-click"), {
72
+ enabled: open && enabled
73
+ });
74
+ const closeRequestProps = useDialogCloseRequests({
75
+ enabled: open && enabled,
76
+ onRequestClose,
77
+ ...handlers
78
+ });
79
+ return { contextValue, closeRequestProps };
80
+ };
81
+ export {
82
+ DialogContext,
83
+ useModalOverlay,
84
+ useNestedDialog
85
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"useClickOutside.d.ts","sourceRoot":"","sources":["../../../src/hooks/useClickOutside/useClickOutside.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,KAAK,sBAAsB,GAAG;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,KAAK,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACxC,gBAAgB,MAAM,IAAI,EAC1B,cAAoB,sBAA2B,KAC9C,IAuBF,CAAC"}
1
+ {"version":3,"file":"useClickOutside.d.ts","sourceRoot":"","sources":["../../../src/hooks/useClickOutside/useClickOutside.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,KAAK,sBAAsB,GAAG;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,KAAK,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACxC,gBAAgB,MAAM,IAAI,EAC1B,cAAoB,sBAA2B,KAC9C,IA0DF,CAAC"}
@@ -1,23 +1,43 @@
1
1
  "use client";
2
- import { useEffect } from "react";
2
+ import { useRef, useEffect } from "react";
3
+ import { useIsomorphicLayoutEffect } from "../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js";
3
4
  const useClickOutside = (ref, onClickOutside, { enabled = true } = {}) => {
5
+ const onClickOutsideRef = useRef(onClickOutside);
6
+ useIsomorphicLayoutEffect(() => {
7
+ onClickOutsideRef.current = onClickOutside;
8
+ }, [onClickOutside]);
4
9
  useEffect(() => {
5
10
  if (!enabled) {
6
11
  return;
7
12
  }
8
- const listener = (event) => {
9
- if (ref.current && !ref.current.contains(event.target)) {
10
- onClickOutside();
13
+ let pointerInside = false;
14
+ const isInside = (event) => ref.current !== null && event.composedPath().includes(ref.current);
15
+ const onPointerDown = (event) => {
16
+ pointerInside = isInside(event);
17
+ };
18
+ const onPointerUp = (event) => {
19
+ pointerInside ||= isInside(event);
20
+ };
21
+ const onClick = (event) => {
22
+ const fromPointer = event.detail > 0;
23
+ const inside = fromPointer && pointerInside || isInside(event);
24
+ pointerInside = false;
25
+ if (ref.current && !inside) {
26
+ onClickOutsideRef.current();
11
27
  }
12
28
  };
13
29
  const rafId = requestAnimationFrame(() => {
14
- document.addEventListener("click", listener);
30
+ document.addEventListener("pointerdown", onPointerDown, true);
31
+ document.addEventListener("pointerup", onPointerUp, true);
32
+ document.addEventListener("click", onClick);
15
33
  });
16
34
  return () => {
17
35
  cancelAnimationFrame(rafId);
18
- document.removeEventListener("click", listener);
36
+ document.removeEventListener("pointerdown", onPointerDown, true);
37
+ document.removeEventListener("pointerup", onPointerUp, true);
38
+ document.removeEventListener("click", onClick);
19
39
  };
20
- }, [ref, onClickOutside, enabled]);
40
+ }, [ref, enabled]);
21
41
  };
22
42
  export {
23
43
  useClickOutside
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shows a <dialog> as modal while `open` is true, and keeps it rendered
3
+ * (`visible`) until its closing animation has ended.
4
+ *
5
+ * The dialog also closes whenever the effect is cleaned up while open, such as
6
+ * when React hides it in an <Activity> or unmounts it, so it never lingers in
7
+ * the top layer. Revealing it again shows it as modal.
8
+ */
9
+ export declare const useModalDialog: (dialogRef: React.RefObject<HTMLDialogElement | null>, open: boolean) => {
10
+ visible: boolean;
11
+ onAnimationEnd: () => void;
12
+ };
13
+ //# sourceMappingURL=useModalDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useModalDialog.d.ts","sourceRoot":"","sources":["../../../src/hooks/useModalDialog/useModalDialog.ts"],"names":[],"mappings":"AAOA;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GACzB,WAAW,KAAK,CAAC,SAAS,CAAC,iBAAiB,GAAG,IAAI,CAAC,EACpD,MAAM,OAAO;;;CA+Cd,CAAC"}
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { hasAnimationDuration } from "@blockle/blocks-core";
3
+ import { useState, useCallback } from "react";
4
+ import { useIsomorphicLayoutEffect } from "../useIsomorphicLayoutEffect/useIsomorphicLayoutEffect.js";
5
+ const useModalDialog = (dialogRef, open) => {
6
+ const [visible, setVisible] = useState(open);
7
+ useIsomorphicLayoutEffect(() => {
8
+ const dialog = dialogRef.current;
9
+ if (open && visible) {
10
+ if (!dialog?.showModal) {
11
+ return;
12
+ }
13
+ if (dialog.open) {
14
+ dialog.close();
15
+ }
16
+ dialog.showModal();
17
+ return () => {
18
+ dialog.close();
19
+ };
20
+ }
21
+ if (open) {
22
+ setVisible(true);
23
+ return;
24
+ }
25
+ if (!hasAnimationDuration(dialog)) {
26
+ setVisible(false);
27
+ }
28
+ dialog?.close?.();
29
+ }, [open, visible]);
30
+ const onAnimationEnd = useCallback(() => {
31
+ if (!open) {
32
+ setVisible(false);
33
+ }
34
+ }, [open]);
35
+ return { visible, onAnimationEnd };
36
+ };
37
+ export {
38
+ useModalDialog
39
+ };
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ export { Alert, type AlertProps, } from './components/feedback/Alert/Alert.js';
6
6
  export { Progress, type ProgressProps, } from './components/feedback/Progress/Progress.js';
7
7
  export { Skeleton, type SkeletonProps, } from './components/feedback/Skeleton/Skeleton.js';
8
8
  export { Spinner, type SpinnerProps, } from './components/feedback/Spinner/Spinner.js';
9
- export { ToastProvider, type ToastProviderProps, } from './components/feedback/Toast/ToastProvider.js';
9
+ export { type ToastPlacement, ToastProvider, type ToastProviderProps, } from './components/feedback/Toast/ToastProvider.js';
10
10
  export { type ToastProps, useToast, } from './components/feedback/Toast/useToast.js';
11
11
  export { Button, type ButtonProps } from './components/form/Button/Button.js';
12
12
  export { Checkbox, type CheckboxProps, } from './components/form/Checkbox/Checkbox.js';
@@ -27,8 +27,9 @@ export { Stack, type StackProps } from './components/layout/Stack/Stack.js';
27
27
  export { Link, type LinkProps } from './components/navigation/Link/Link.js';
28
28
  export { Dialog, type DialogProps, } from './components/overlay/Dialog/Dialog.js';
29
29
  export { Drawer, type DrawerProps, } from './components/overlay/Drawer/Drawer.js';
30
- export { Popover, type PopoverProps, } from './components/overlay/Popover/Popover.js';
30
+ export { Popover, type PopoverPlacement, type PopoverProps, } from './components/overlay/Popover/Popover.js';
31
31
  export { Portal, type PortalProps, } from './components/overlay/Portal/Portal.js';
32
+ export type { RequestCloseReason } from './components/overlay/requestCloseReason.js';
32
33
  export { Tooltip, type TooltipProps, } from './components/overlay/Tooltip/Tooltip.js';
33
34
  export { BlocksProvider, type BlocksProviderProps, } from './components/providers/BlocksProvider/BlocksProvider.js';
34
35
  export { Heading, type HeadingProps, } from './components/typography/Heading/Heading.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ export type Point = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ /**
6
+ * Dispatches a trusted mouse event at a point in the test frame. Chromium only.
7
+ */
8
+ export declare const mouse: (type: "mousePressed" | "mouseMoved" | "mouseReleased", point: Point) => Promise<void>;
9
+ /**
10
+ * Presses the left button at `from`, moves to `to` and releases it there.
11
+ */
12
+ export declare const press: (from: Point, to?: Point) => Promise<void>;
13
+ export declare const centerOf: (element: Element) => Point;
14
+ /**
15
+ * The bottom right corner of the viewport, clear of the overlays under test.
16
+ */
17
+ export declare const outside: () => Point;
18
+ //# sourceMappingURL=pointer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pointer.d.ts","sourceRoot":"","sources":["../../../src/testUtils/browser/pointer.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,KAAK,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAgB7C;;GAEG;AACH,eAAO,MAAM,KAAK,GAChB,MAAM,cAAc,GAAG,YAAY,GAAG,eAAe,EACrD,OAAO,KAAK,kBASb,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,KAAK,GAAU,MAAM,KAAK,EAAE,KAAI,KAAY,kBAQxD,CAAC;AAEF,eAAO,MAAM,QAAQ,GAAI,SAAS,OAAO,KAAG,KAI3C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,QAAO,KAGzB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockle/blocks-react",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "React hooks and components for Blockle",
5
5
  "type": "module",
6
6
  "exports": {
@@ -34,7 +34,7 @@
34
34
  "@vanilla-extract/css": "^1.18.0"
35
35
  },
36
36
  "peerDependencies": {
37
- "@blockle/blocks-core": ">=3.0.x",
37
+ "@blockle/blocks-core": ">=3.1.x",
38
38
  "@blockle/blocks-react-slot": ">=1.2.x",
39
39
  "@vanilla-extract/css": "^1.18.0",
40
40
  "react": ">=19.1.0",
@@ -1,7 +0,0 @@
1
- type DialogContextValue = {
2
- setEnabled: (active: boolean) => void;
3
- };
4
- export declare const DialogContext: import('react').Context<DialogContextValue | undefined>;
5
- export declare const useNestedDialog: (open: boolean) => boolean;
6
- export {};
7
- //# sourceMappingURL=dialogHelper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dialogHelper.d.ts","sourceRoot":"","sources":["../../../../src/components/overlay/Dialog/dialogHelper.tsx"],"names":[],"mappings":"AAIA,KAAK,kBAAkB,GAAG;IACxB,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,aAAa,yDAEzB,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,MAAM,OAAO,KAAG,OAgB/C,CAAC"}
@@ -1,22 +0,0 @@
1
- "use client";
2
- import { useContext, useEffect, createContext } from "react";
3
- const DialogContext = createContext(
4
- void 0
5
- );
6
- const useNestedDialog = (open) => {
7
- const parentDialog = useContext(DialogContext);
8
- useEffect(() => {
9
- if (!parentDialog || !open) {
10
- return;
11
- }
12
- parentDialog.setEnabled(false);
13
- return () => {
14
- parentDialog.setEnabled(true);
15
- };
16
- }, [parentDialog, open]);
17
- return !!parentDialog;
18
- };
19
- export {
20
- DialogContext,
21
- useNestedDialog
22
- };