reshaped 3.9.0-canary.3 → 3.9.0-canary.30

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 (85) hide show
  1. package/dist/bundle.css +1 -1
  2. package/dist/bundle.d.ts +2 -0
  3. package/dist/bundle.js +2 -2
  4. package/dist/components/Actionable/Actionable.module.css +1 -1
  5. package/dist/components/Avatar/Avatar.js +7 -24
  6. package/dist/components/Avatar/Avatar.module.css +1 -1
  7. package/dist/components/Badge/Badge.js +2 -2
  8. package/dist/components/Badge/Badge.module.css +1 -1
  9. package/dist/components/Badge/Badge.types.d.ts +1 -1
  10. package/dist/components/Button/Button.module.css +1 -1
  11. package/dist/components/Calendar/Calendar.module.css +1 -1
  12. package/dist/components/Calendar/Calendar.types.d.ts +4 -2
  13. package/dist/components/Calendar/CalendarControlled.js +2 -2
  14. package/dist/components/Calendar/CalendarDate.js +9 -7
  15. package/dist/components/Calendar/CalendarMonth.js +2 -2
  16. package/dist/components/Card/Card.d.ts +1 -1
  17. package/dist/components/Card/Card.types.d.ts +5 -5
  18. package/dist/components/Checkbox/Checkbox.js +2 -12
  19. package/dist/components/FileUpload/FileUpload.js +7 -5
  20. package/dist/components/FileUpload/FileUpload.module.css +1 -1
  21. package/dist/components/FileUpload/FileUpload.types.d.ts +2 -0
  22. package/dist/components/Flyout/Flyout.constants.d.ts +3 -3
  23. package/dist/components/Flyout/Flyout.constants.js +1 -0
  24. package/dist/components/Flyout/Flyout.module.css +1 -1
  25. package/dist/components/Flyout/Flyout.types.d.ts +6 -7
  26. package/dist/components/Flyout/FlyoutContent.js +3 -3
  27. package/dist/components/Flyout/FlyoutControlled.js +31 -10
  28. package/dist/components/Flyout/useFlyout.d.ts +2 -2
  29. package/dist/components/Flyout/useFlyout.js +8 -21
  30. package/dist/components/Flyout/utilities/calculatePosition.d.ts +6 -6
  31. package/dist/components/Flyout/utilities/calculatePosition.js +24 -17
  32. package/dist/components/Flyout/utilities/constants.d.ts +1 -0
  33. package/dist/components/Flyout/utilities/constants.js +1 -0
  34. package/dist/components/Flyout/utilities/flyout.js +37 -6
  35. package/dist/components/Flyout/utilities/isFullyVisible.d.ts +4 -3
  36. package/dist/components/Flyout/utilities/isFullyVisible.js +5 -4
  37. package/dist/components/Grid/Grid.types.d.ts +4 -4
  38. package/dist/components/HiddenInput/HiddenInput.js +33 -0
  39. package/dist/components/HiddenInput/HiddenInput.types.d.ts +26 -0
  40. package/dist/components/Image/Image.js +13 -8
  41. package/dist/components/Image/Image.module.css +1 -1
  42. package/dist/components/Image/Image.types.d.ts +3 -1
  43. package/dist/components/Modal/Modal.js +2 -5
  44. package/dist/components/Modal/Modal.module.css +1 -1
  45. package/dist/components/Popover/Popover.module.css +1 -1
  46. package/dist/components/Radio/Radio.js +2 -12
  47. package/dist/components/Reshaped/Reshaped.css +1 -1
  48. package/dist/components/Scrim/Scrim.js +4 -3
  49. package/dist/components/Scrim/Scrim.module.css +1 -1
  50. package/dist/components/Scrim/Scrim.types.d.ts +2 -1
  51. package/dist/components/ScrollArea/ScrollArea.js +7 -7
  52. package/dist/components/Slider/SliderControlled.js +5 -4
  53. package/dist/components/Tabs/Tabs.module.css +1 -1
  54. package/dist/components/Tabs/Tabs.types.d.ts +3 -1
  55. package/dist/components/Tabs/TabsContext.d.ts +1 -0
  56. package/dist/components/Tabs/TabsControlled.js +2 -1
  57. package/dist/components/Tabs/TabsItem.js +3 -3
  58. package/dist/components/Tabs/TabsList.js +9 -5
  59. package/dist/components/Tabs/TabsPanel.js +1 -1
  60. package/dist/components/Text/Text.d.ts +1 -1
  61. package/dist/components/Text/Text.js +2 -2
  62. package/dist/components/Text/Text.module.css +1 -1
  63. package/dist/components/Text/Text.types.d.ts +5 -3
  64. package/dist/components/Tooltip/Tooltip.js +2 -2
  65. package/dist/components/Tooltip/Tooltip.module.css +1 -1
  66. package/dist/components/Tooltip/Tooltip.types.d.ts +1 -1
  67. package/dist/components/View/View.types.d.ts +4 -4
  68. package/dist/hooks/_private/useDrag.js +0 -3
  69. package/dist/index.d.ts +2 -0
  70. package/dist/index.js +1 -0
  71. package/dist/types/global.d.ts +1 -1
  72. package/dist/utilities/dom/index.d.ts +0 -1
  73. package/dist/utilities/dom/index.js +0 -1
  74. package/dist/utilities/scroll/disable.js +4 -2
  75. package/package.json +4 -98
  76. package/README.md +0 -24
  77. package/dist/components/_private/HiddenInput/HiddenInput.js +0 -10
  78. package/dist/components/_private/HiddenInput/HiddenInput.types.d.ts +0 -15
  79. package/dist/utilities/dom/userSelect.d.ts +0 -2
  80. package/dist/utilities/dom/userSelect.js +0 -6
  81. /package/dist/components/{_private/HiddenInput → HiddenInput}/HiddenInput.d.ts +0 -0
  82. /package/dist/components/{_private/HiddenInput → HiddenInput}/HiddenInput.module.css +0 -0
  83. /package/dist/components/{_private/HiddenInput → HiddenInput}/HiddenInput.types.js +0 -0
  84. /package/dist/components/{_private/HiddenInput → HiddenInput}/index.d.ts +0 -0
  85. /package/dist/components/{_private/HiddenInput → HiddenInput}/index.js +0 -0
@@ -15,7 +15,7 @@ import { Provider, useFlyoutTriggerContext, useFlyoutContext, useFlyoutContentCo
15
15
  import useFlyout from "./useFlyout.js";
16
16
  import cooldown from "./utilities/cooldown.js";
17
17
  const FlyoutControlled = (props) => {
18
- const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentMaxHeight, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, positionRef, } = props;
18
+ const { triggerType = "click", groupTimeouts, onOpen, onClose, children, disabled, forcePosition, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, trapFocusMode = "dialog", width, disableHideAnimation, disableContentHover, disableCloseOnOutsideClick, autoFocus = true, originCoordinates, contentGap = 2, contentShift, contentMaxHeight, contentMaxWidth, contentClassName, contentAttributes, position: passedPosition, active: passedActive, id: passedId, instanceRef, containerRef, initialFocusRef, positionRef, } = props;
19
19
  const fallbackPositions = props.fallbackPositions === false || forcePosition ? [] : props.fallbackPositions;
20
20
  const onOpenRef = useHandlerRef(onOpen);
21
21
  const onCloseRef = useHandlerRef(onClose);
@@ -55,12 +55,13 @@ const FlyoutControlled = (props) => {
55
55
  // Touch devices trigger onMouseEnter but we don't need to apply regular hover timeouts
56
56
  // So we're saving a flag on touch start and then change the mouse enter behavior
57
57
  const hoverTriggeredWithTouchEventRef = React.useRef(false);
58
+ const originCoordinatesRef = React.useRef(originCoordinates ?? null);
58
59
  // eslint-disable-next-line react-hooks/refs
60
+ originCoordinatesRef.current = originCoordinates ?? null;
59
61
  const flyout = useFlyout({
60
62
  triggerElRef: positionRef ?? triggerElRef,
61
63
  flyoutElRef,
62
- // eslint-disable-next-line react-hooks/refs
63
- triggerBounds: originCoordinates ?? triggerBoundsRef.current,
64
+ triggerBoundsRef: originCoordinates ? originCoordinatesRef : triggerBoundsRef,
64
65
  width,
65
66
  position: passedPosition,
66
67
  defaultActive: resolvedActive,
@@ -103,7 +104,7 @@ const FlyoutControlled = (props) => {
103
104
  return;
104
105
  onCloseRef.current?.({ reason: options.reason });
105
106
  if (options?.closeParents) {
106
- parentFlyoutContext?.handleClose?.({});
107
+ parentFlyoutContext?.handleClose?.({ closeParents: true, reason: options.reason });
107
108
  }
108
109
  }, [isRendered, isDismissible, triggerType, onCloseRef, disabled, parentFlyoutContext]);
109
110
  /**
@@ -143,14 +144,31 @@ const FlyoutControlled = (props) => {
143
144
  cooldown.warm();
144
145
  timerRef.current = setTimeout(() => {
145
146
  handleOpen();
146
- }, groupTimeouts && cooldown.status === "warming" ? timeouts.mouseEnter : 0);
147
+ }, groupTimeouts && cooldown.status === "warming"
148
+ ? timeouts.mouseEnter
149
+ : isSubmenu
150
+ ? timeouts.mouseEnter
151
+ : 0);
147
152
  }
148
- }, [clearTimer, handleOpen, groupTimeouts]);
149
- const handleMouseLeave = React.useCallback(() => {
153
+ }, [clearTimer, handleOpen, groupTimeouts, isSubmenu]);
154
+ const handleMouseLeave = React.useCallback((e) => {
155
+ if (e.relatedTarget === flyoutElRef.current ||
156
+ (e.relatedTarget instanceof Node && flyoutElRef.current?.contains(e.relatedTarget)))
157
+ return;
158
+ if (e.relatedTarget === triggerElRef.current ||
159
+ (e.relatedTarget instanceof Node && triggerElRef.current?.contains(e.relatedTarget)))
160
+ return;
150
161
  cooldown.cool();
151
162
  clearTimer();
152
- handleClose({});
153
- }, [clearTimer, handleClose]);
163
+ if (isSubmenu) {
164
+ timerRef.current = setTimeout(() => {
165
+ handleClose({});
166
+ }, timeouts.mouseLeave);
167
+ }
168
+ else {
169
+ handleClose({});
170
+ }
171
+ }, [clearTimer, handleClose, triggerElRef, flyoutElRef, isSubmenu]);
154
172
  const handleTriggerClick = React.useCallback(() => {
155
173
  if (!isRendered) {
156
174
  handleOpen();
@@ -286,8 +304,10 @@ const FlyoutControlled = (props) => {
286
304
  resizeObserver.observe(document.body);
287
305
  if (triggerElRef.current)
288
306
  resizeObserver.observe(triggerElRef.current);
307
+ if (flyoutElRef.current)
308
+ resizeObserver.observe(flyoutElRef.current);
289
309
  return () => resizeObserver.disconnect();
290
- }, [updatePosition, triggerElRef, isRendered]);
310
+ }, [updatePosition, triggerElRef, isRendered, flyoutElRef]);
291
311
  React.useEffect(() => {
292
312
  updatePosition({ sync: true });
293
313
  }, [isRTL, updatePosition]);
@@ -332,6 +352,7 @@ const FlyoutControlled = (props) => {
332
352
  contentAttributes,
333
353
  contentGap,
334
354
  contentMaxHeight,
355
+ contentMaxWidth,
335
356
  containerRef,
336
357
  disableContentHover,
337
358
  autoFocus,
@@ -6,8 +6,8 @@ type UseFlyout = (args: Pick<T.Props, "width" | "position" | "defaultActive" | "
6
6
  container?: HTMLElement | null;
7
7
  triggerElRef: React.RefObject<HTMLElement | null>;
8
8
  flyoutElRef: React.RefObject<HTMLElement | null>;
9
- triggerBounds?: DOMRect | G.Coordinates | null;
10
- }) => Pick<T.State, "styles" | "position" | "status"> & {
9
+ triggerBoundsRef: React.RefObject<DOMRect | G.Coordinates | null>;
10
+ }) => Pick<T.State, "position" | "status"> & {
11
11
  updatePosition: (options?: {
12
12
  sync?: boolean;
13
13
  }) => void;
@@ -1,43 +1,32 @@
1
1
  import React from "react";
2
2
  import useRTL from "../../hooks/useRTL.js";
3
- import { defaultStyles, resetStyles } from "./Flyout.constants.js";
4
3
  import flyout from "./utilities/flyout.js";
5
4
  const flyoutReducer = (state, action) => {
6
5
  switch (action.type) {
7
6
  case "render":
8
- if (state.status !== "idle")
9
- return state;
10
7
  // Disable events before it's positioned to avoid mouseleave getting triggered
11
- return { ...state, status: "rendered", styles: { pointerEvents: "none", ...resetStyles } };
8
+ return { ...state, status: "rendered" };
12
9
  case "position":
13
- if (!action.payload.sync && state.status !== "rendered")
14
- return state;
15
- if (action.payload.sync && state.status !== "visible")
16
- return state;
17
10
  return {
18
11
  ...state,
19
- status: action.payload.sync ? "visible" : "positioned",
12
+ status: action.payload.sync ? state.status : "positioned",
20
13
  position: action.payload.position,
21
- styles: { ...defaultStyles, ...action.payload.styles },
22
14
  };
23
15
  case "show":
16
+ // Checking because we're positioning inside nextAnimationFrame
24
17
  if (state.status !== "positioned")
25
18
  return state;
26
19
  return { ...state, status: "visible" };
27
20
  case "hide":
28
- if (state.status !== "visible")
29
- return state;
30
21
  return { ...state, status: "hidden" };
31
22
  case "remove":
32
- if (state.status !== "hidden" && state.status !== "visible")
33
- return state;
34
- return { ...state, status: "idle", styles: resetStyles };
23
+ return { ...state, status: "idle" };
35
24
  default:
36
25
  throw new Error("[Reshaped] Invalid flyout reducer type");
37
26
  }
38
27
  };
39
28
  const useFlyout = (args) => {
40
- const { triggerElRef, flyoutElRef, triggerBounds, contentGap, contentShift, ...options } = args;
29
+ const { triggerElRef, flyoutElRef, triggerBoundsRef, contentGap, contentShift, ...options } = args;
41
30
  const { position: defaultPosition = "bottom", fallbackPositions, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, width, container, } = options;
42
31
  const lastUsedPositionRef = React.useRef(defaultPosition);
43
32
  // Memo the array internally to avoid new arrays triggering useCallback
@@ -47,7 +36,6 @@ const useFlyout = (args) => {
47
36
  const [isRTL] = useRTL();
48
37
  const [state, dispatch] = React.useReducer(flyoutReducer, {
49
38
  position: defaultPosition,
50
- styles: defaultStyles,
51
39
  status: "idle",
52
40
  });
53
41
  const render = React.useCallback(() => {
@@ -72,7 +60,7 @@ const useFlyout = (args) => {
72
60
  const nextFlyoutData = flyout({
73
61
  triggerEl: triggerElRef.current,
74
62
  flyoutEl: flyoutElRef.current,
75
- triggerBounds,
63
+ triggerBounds: triggerBoundsRef.current,
76
64
  width,
77
65
  position: changePositon ? defaultPosition : lastUsedPositionRef.current,
78
66
  fallbackPositions: changePositon ? cachedFallbackPositions : [],
@@ -100,7 +88,7 @@ const useFlyout = (args) => {
100
88
  isRTL,
101
89
  flyoutElRef,
102
90
  triggerElRef,
103
- triggerBounds,
91
+ triggerBoundsRef,
104
92
  width,
105
93
  contentGap,
106
94
  contentShift,
@@ -114,13 +102,12 @@ const useFlyout = (args) => {
114
102
  }, [state.status, updatePosition]);
115
103
  return React.useMemo(() => ({
116
104
  position: state.position,
117
- styles: state.styles,
118
105
  status: state.status,
119
106
  updatePosition,
120
107
  render,
121
108
  hide,
122
109
  remove,
123
110
  show,
124
- }), [render, updatePosition, hide, remove, show, state.position, state.styles, state.status]);
111
+ }), [render, updatePosition, hide, remove, show, state.position, state.status]);
125
112
  };
126
113
  export default useFlyout;
@@ -13,13 +13,13 @@ declare const calculatePosition: (args: {
13
13
  } & Pick<T.Options, "position" | "rtl" | "width" | "contentGap" | "contentShift" | "fallbackAdjustLayout" | "fallbackMinWidth" | "fallbackMinHeight">) => {
14
14
  position: T.Position;
15
15
  styles: {
16
- left: number | undefined;
17
- right: number | undefined;
18
- top: number | undefined;
19
- bottom: number | undefined;
16
+ left: string | null;
17
+ right: string | null;
18
+ top: string | null;
19
+ bottom: string | null;
20
20
  transform: string;
21
- height: number | undefined;
22
- width: string | number | undefined;
21
+ height: string | null;
22
+ width: string | null;
23
23
  };
24
24
  boundaries: {
25
25
  left: number;
@@ -1,10 +1,12 @@
1
+ import { SCREEN_OFFSET } from "./constants.js";
1
2
  import { getRTLPosition, centerBySize } from "./helpers.js";
2
- const SCREEN_OFFSET = 8;
3
3
  /**
4
4
  * Calculate styles for the current position
5
5
  */
6
6
  const calculatePosition = (args) => {
7
- const { triggerBounds, flyoutBounds, containerBounds, position: passedPosition, rtl, width: passedWidth, contentGap = 0, contentShift = 0, passedContainer, fallbackAdjustLayout, fallbackMinWidth, fallbackMinHeight, } = args;
7
+ const { triggerBounds, flyoutBounds, containerBounds, position: passedPosition, rtl, width: passedWidth, contentGap = 0, contentShift = 0, passedContainer, fallbackAdjustLayout,
8
+ // fallbackMinWidth,
9
+ fallbackMinHeight, } = args;
8
10
  const isFullWidth = passedWidth === "full" || passedWidth === "100%";
9
11
  let left = 0;
10
12
  let top = 0;
@@ -137,15 +139,20 @@ const calculatePosition = (args) => {
137
139
  if (bottom !== null)
138
140
  bottom = bottom + (flyoutHeight - height);
139
141
  }
140
- if (updatedOverflow.left > 0) {
141
- width = Math.max(fallbackMinWidth ? parseInt(fallbackMinWidth) : 0, flyoutWidth - updatedOverflow.left);
142
- left = left + (flyoutWidth - width);
143
- }
144
- else if (updatedOverflow.right > 0) {
145
- width = Math.max(fallbackMinWidth ? parseInt(fallbackMinWidth) : 0, flyoutWidth - updatedOverflow.right);
146
- if (right !== null)
147
- right = right + (flyoutWidth - width);
148
- }
142
+ // TODO: Decide if we need horizontal scrolling for the fallbacks, might be a bad practice anyways
143
+ // if (updatedOverflow.left > 0) {
144
+ // width = Math.max(
145
+ // fallbackMinWidth ? parseInt(fallbackMinWidth) : 0,
146
+ // flyoutWidth - updatedOverflow.left
147
+ // );
148
+ // left = left + (flyoutWidth - width);
149
+ // } else if (updatedOverflow.right > 0) {
150
+ // width = Math.max(
151
+ // fallbackMinWidth ? parseInt(fallbackMinWidth) : 0,
152
+ // flyoutWidth - updatedOverflow.right
153
+ // );
154
+ // if (right !== null) right = right + (flyoutWidth - width);
155
+ // }
149
156
  }
150
157
  if (isFullWidth) {
151
158
  left = SCREEN_OFFSET;
@@ -159,13 +166,13 @@ const calculatePosition = (args) => {
159
166
  return {
160
167
  position,
161
168
  styles: {
162
- left: right === null ? 0 : undefined,
163
- right: right === null ? undefined : 0,
164
- top: bottom === null ? 0 : undefined,
165
- bottom: bottom === null ? undefined : 0,
169
+ left: right === null ? "0px" : null,
170
+ right: right === null ? null : "0px",
171
+ top: bottom === null ? "0px" : null,
172
+ bottom: bottom === null ? null : "0px",
166
173
  transform: `translate(${translateX}px, ${translateY}px)`,
167
- height,
168
- width: width ?? passedWidth,
174
+ height: height !== undefined ? `${height}px` : null,
175
+ width: width !== undefined ? `${width}px` : (passedWidth ?? null),
169
176
  },
170
177
  boundaries: {
171
178
  left,
@@ -0,0 +1 @@
1
+ export declare const SCREEN_OFFSET = 8;
@@ -0,0 +1 @@
1
+ export const SCREEN_OFFSET = 8;
@@ -1,6 +1,7 @@
1
1
  import { getRectFromCoordinates, getShadowRoot, findClosestPositionContainer } from "../../../utilities/dom/index.js";
2
2
  import { resetStyles } from "../Flyout.constants.js";
3
3
  import calculatePosition from "./calculatePosition.js";
4
+ import { SCREEN_OFFSET } from "./constants.js";
4
5
  import getPositionFallbacks from "./getPositionFallbacks.js";
5
6
  import isFullyVisible from "./isFullyVisible.js";
6
7
  /**
@@ -33,16 +34,26 @@ const flyout = (args) => {
33
34
  const shadowRoot = triggerEl && getShadowRoot(triggerEl);
34
35
  // Insert inside shadow root if possible to make sure styles are applied correctly
35
36
  (shadowRoot || document.body).appendChild(targetClone);
36
- const cloneRect = targetClone.getBoundingClientRect();
37
- const flyoutBounds = { width: cloneRect.width, height: cloneRect.height };
38
37
  const closestFixedContainer = !passedContainer && triggerEl ? findClosestPositionContainer({ el: triggerEl }) : undefined;
39
38
  const container = passedContainer ||
40
39
  // Render inside fixed position container automatically to keep their position synced on scroll
41
40
  closestFixedContainer ||
42
41
  document.body;
43
42
  const renderContainerBounds = container.getBoundingClientRect();
44
- const visualContainerBounds = (passedContainer || document.body).getBoundingClientRect();
45
- const applyPosition = (position) => {
43
+ const applyPosition = (position, options) => {
44
+ const widthOption = options?.width || width;
45
+ // If there is a width override, apply it to calculate the position and the height correctly
46
+ if (widthOption === "full") {
47
+ targetClone.style.width = `calc(100% - ${SCREEN_OFFSET * 2}px)`;
48
+ }
49
+ else if (widthOption === "trigger") {
50
+ targetClone.style.width = `${resolvedTriggerBounds.width}px`;
51
+ }
52
+ else {
53
+ targetClone.style.width = widthOption || "";
54
+ }
55
+ const cloneRect = targetClone.getBoundingClientRect();
56
+ const flyoutBounds = { width: cloneRect.width, height: cloneRect.height };
46
57
  return calculatePosition({
47
58
  triggerBounds: resolvedTriggerBounds,
48
59
  flyoutBounds,
@@ -51,7 +62,7 @@ const flyout = (args) => {
51
62
  contentGap: contentGap * unitModifier,
52
63
  contentShift: contentShift * unitModifier,
53
64
  rtl,
54
- width,
65
+ width: widthOption,
55
66
  passedContainer: passedContainer ||
56
67
  (closestFixedContainer !== document.body ? closestFixedContainer : undefined),
57
68
  fallbackAdjustLayout,
@@ -60,6 +71,12 @@ const flyout = (args) => {
60
71
  });
61
72
  };
62
73
  const testVisibility = (calculated) => {
74
+ const visualContainerBounds = passedContainer?.getBoundingClientRect() ?? {
75
+ width: window.innerWidth,
76
+ height: window.innerHeight,
77
+ left: window.scrollX,
78
+ top: window.scrollY,
79
+ };
63
80
  return isFullyVisible({
64
81
  flyoutBounds: calculated.boundaries,
65
82
  visualContainerBounds,
@@ -75,10 +92,24 @@ const flyout = (args) => {
75
92
  calculated = tested;
76
93
  return visible;
77
94
  });
95
+ // Try full width positions in case it doesn't fit on any side
96
+ if (!calculated) {
97
+ const smallScreenFallbackPositions = ["top", "bottom"].filter((position) => testOrder.includes(position));
98
+ smallScreenFallbackPositions.some((position) => {
99
+ const tested = applyPosition(position, { width: "full" });
100
+ const visible = testVisibility(tested);
101
+ if (visible)
102
+ calculated = tested;
103
+ return visible;
104
+ });
105
+ }
78
106
  if (!calculated)
79
107
  calculated = applyPosition(lastUsedPosition);
80
108
  onPositionChoose(calculated.position);
81
109
  targetClone.parentNode?.removeChild(targetClone);
82
- return calculated;
110
+ Object.entries(calculated.styles).forEach(([key, value]) => {
111
+ flyoutEl.style.setProperty(key, value);
112
+ });
113
+ return { position: calculated.position };
83
114
  };
84
115
  export default flyout;
@@ -1,12 +1,13 @@
1
+ type Bounds = Pick<DOMRect, "left" | "top" | "width" | "height">;
1
2
  /**
2
3
  * Check if element visually fits within its render container
3
4
  */
4
5
  declare const isFullyVisible: (args: {
5
6
  /** Bounds of the flyout content */
6
- flyoutBounds: Pick<DOMRect, "left" | "top" | "width" | "height">;
7
+ flyoutBounds: Bounds;
7
8
  /** Bounds of the container where the flyout content should fit */
8
- visualContainerBounds: DOMRect;
9
+ visualContainerBounds: Bounds;
9
10
  /** Bounds of the container where flyout content is rendered */
10
- renderContainerBounds: DOMRect;
11
+ renderContainerBounds: Bounds;
11
12
  }) => boolean;
12
13
  export default isFullyVisible;
@@ -1,20 +1,21 @@
1
+ import { SCREEN_OFFSET } from "./constants.js";
1
2
  /**
2
3
  * Check if element visually fits within its render container
3
4
  */
4
5
  const isFullyVisible = (args) => {
5
6
  const { flyoutBounds, visualContainerBounds, renderContainerBounds } = args;
6
- if (renderContainerBounds.left + flyoutBounds.left < visualContainerBounds.left) {
7
+ if (renderContainerBounds.left + flyoutBounds.left < visualContainerBounds.left + SCREEN_OFFSET) {
7
8
  return false;
8
9
  }
9
- if (renderContainerBounds.top + flyoutBounds.top < visualContainerBounds.top) {
10
+ if (renderContainerBounds.top + flyoutBounds.top < visualContainerBounds.top + SCREEN_OFFSET) {
10
11
  return false;
11
12
  }
12
13
  if (renderContainerBounds.left + flyoutBounds.left + flyoutBounds.width >
13
- visualContainerBounds.right) {
14
+ visualContainerBounds.left + visualContainerBounds.width - SCREEN_OFFSET) {
14
15
  return false;
15
16
  }
16
17
  if (renderContainerBounds.top + flyoutBounds.top + flyoutBounds.height >
17
- visualContainerBounds.bottom) {
18
+ visualContainerBounds.top + visualContainerBounds.height - SCREEN_OFFSET) {
18
19
  return false;
19
20
  }
20
21
  return true;
@@ -2,7 +2,7 @@ import type { Property } from "csstype";
2
2
  import type React from "react";
3
3
  import type * as TStyles from "../../styles/types";
4
4
  import type * as G from "../../types/global";
5
- export type Props<TagName extends keyof React.JSX.IntrinsicElements = "div"> = {
5
+ export type Props<TagName extends keyof React.JSX.IntrinsicElements | void = void> = {
6
6
  /** Gap between grid items */
7
7
  gap?: G.Responsive<number>;
8
8
  /** Horizontal gap between grid items */
@@ -34,13 +34,13 @@ export type Props<TagName extends keyof React.JSX.IntrinsicElements = "div"> = {
34
34
  /** Node for inserting children */
35
35
  children?: React.ReactNode;
36
36
  /** Custom root element html tag */
37
- as?: TagName;
37
+ as?: TagName extends keyof React.JSX.IntrinsicElements ? TagName : keyof React.JSX.IntrinsicElements;
38
38
  /** Additional classname for the root element */
39
39
  className?: G.ClassName;
40
40
  /** Additional attributes for the root element */
41
41
  attributes?: G.Attributes<TagName>;
42
42
  };
43
- export type ItemProps<TagName extends keyof React.JSX.IntrinsicElements = "div"> = {
43
+ export type ItemProps<TagName extends keyof React.JSX.IntrinsicElements | void = void> = {
44
44
  /** Grid area for template syntax */
45
45
  area?: string;
46
46
  /** Starting column position */
@@ -58,7 +58,7 @@ export type ItemProps<TagName extends keyof React.JSX.IntrinsicElements = "div">
58
58
  /** Node for inserting children */
59
59
  children?: React.ReactNode;
60
60
  /** Custom item element html tag */
61
- as?: TagName;
61
+ as?: TagName extends keyof React.JSX.IntrinsicElements ? TagName : keyof React.JSX.IntrinsicElements;
62
62
  /** Additional classname for the item element */
63
63
  className?: G.ClassName;
64
64
  /** Additional attributes for the item element */
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useCheckboxGroup } from "../CheckboxGroup/index.js";
3
+ import { useFormControl } from "../FormControl/index.js";
4
+ import { useRadioGroup } from "../RadioGroup/index.js";
5
+ import { classNames } from "../../utilities/props.js";
6
+ import s from "./HiddenInput.module.css";
7
+ const HiddenInput = (props) => {
8
+ const { type, value, className, onBlur, onFocus, onChange, attributes } = props;
9
+ const rootClassNames = classNames(s.root, className);
10
+ const checkboxGroup = useCheckboxGroup();
11
+ const radioGroup = useRadioGroup();
12
+ const formControl = useFormControl();
13
+ const name = checkboxGroup?.name ?? radioGroup?.name ?? props.name;
14
+ const disabled = formControl?.disabled ?? props.disabled ?? checkboxGroup?.disabled ?? radioGroup?.disabled;
15
+ const checked = (value && (checkboxGroup?.value?.includes(value) ?? radioGroup?.value === value)) ||
16
+ props.checked;
17
+ const defaultChecked = checkboxGroup ? undefined : props.defaultChecked;
18
+ const handleChange = (event) => {
19
+ if (!name)
20
+ return;
21
+ const { checked } = event.target;
22
+ const changeArgs = { name, value, checked, event };
23
+ if (onChange)
24
+ onChange(changeArgs);
25
+ if (checkboxGroup?.onChange)
26
+ checkboxGroup.onChange(changeArgs);
27
+ if (radioGroup?.onChange)
28
+ radioGroup.onChange(changeArgs);
29
+ };
30
+ return (_jsx("input", { ...attributes, className: rootClassNames, type: type, name: name, value: value, checked: checked, defaultChecked: defaultChecked, disabled: disabled, onChange: handleChange, onFocus: onFocus || attributes?.onFocus, onBlur: onBlur || attributes?.onBlur, "data-rs-hidden-input": true }));
31
+ };
32
+ HiddenInput.displayName = "HiddenInput";
33
+ export default HiddenInput;
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import type * as G from "../../types/global";
3
+ export type Props = {
4
+ /** Name of the input element */
5
+ name?: string;
6
+ /** Value of the input element that is used for form submission */
7
+ value?: string;
8
+ /** Checked state of the input element, enables controlled mode */
9
+ checked?: boolean;
10
+ /** Default checked state of the input element, enables uncontrolled mode */
11
+ defaultChecked?: boolean;
12
+ /** Disable the input element */
13
+ disabled?: boolean;
14
+ /** Callback when the input value changes */
15
+ onChange?: G.ChangeHandler<boolean>;
16
+ /** Callback when the input or label is focused */
17
+ onFocus?: (e: React.FocusEvent) => void;
18
+ /** Callback when the input or label is blurred */
19
+ onBlur?: (e: React.FocusEvent) => void;
20
+ /** Type of the input element */
21
+ type: "checkbox" | "radio";
22
+ /** Additional classname for the root element */
23
+ className?: G.ClassName;
24
+ /** Additional attributes for the input element */
25
+ attributes?: G.Attributes<"input">;
26
+ };
@@ -5,12 +5,14 @@ import { resolveMixin } from "../../styles/mixin.js";
5
5
  import { classNames } from "../../utilities/props.js";
6
6
  import s from "./Image.module.css";
7
7
  const Image = (props) => {
8
- const { src, alt, width, maxWidth, height, aspectRatio, onLoad, onError, fallback, displayMode = "cover", borderRadius, className, attributes, imageAttributes: passedImageAttributes, renderImage, } = props;
8
+ const { src, alt, width, maxWidth, height, aspectRatio, onLoad, onError, fallback, outline, displayMode = "cover", borderRadius, className, attributes, imageAttributes: passedImageAttributes, renderImage, } = props;
9
9
  const [status, setStatus] = React.useState("loading");
10
10
  const mixinStyles = resolveMixin({ radius: borderRadius, width, height, maxWidth, aspectRatio });
11
- const baseClassNames = classNames(s.root, mixinStyles.classNames, displayMode && s[`--display-mode-${displayMode}`], className);
12
- const imgClassNames = classNames(s.image, baseClassNames);
13
- const fallbackClassNames = classNames(s.fallback, baseClassNames);
11
+ const rootClassNames = classNames(s.root, mixinStyles.classNames, outline && s["--outline"], className);
12
+ const imageClassNames = classNames([
13
+ s.image,
14
+ displayMode && s[`image--display-mode-${displayMode}`],
15
+ ]);
14
16
  const isFallback = (status === "error" || !src) && !!fallback;
15
17
  const style = {
16
18
  ...attributes?.style,
@@ -19,10 +21,12 @@ const Image = (props) => {
19
21
  const handleLoad = (e) => {
20
22
  setStatus("success");
21
23
  onLoad?.(e);
24
+ passedImageAttributes?.onLoad?.(e);
22
25
  };
23
26
  const handleError = (e) => {
24
27
  setStatus("error");
25
28
  onError?.(e);
29
+ passedImageAttributes?.onError?.(e);
26
30
  };
27
31
  React.useEffect(() => {
28
32
  setStatus("loading");
@@ -34,13 +38,13 @@ const Image = (props) => {
34
38
  src: fallback ?? "",
35
39
  alt: alt ?? "",
36
40
  role: alt ? undefined : "presentation",
37
- className: fallbackClassNames,
41
+ className: rootClassNames,
38
42
  style,
39
43
  };
40
44
  // eslint-disable-next-line jsx-a11y/alt-text
41
45
  return renderImage ? renderImage(imageAttributes) : _jsx("img", { ...imageAttributes });
42
46
  }
43
- return (_jsx("div", { ...attributes, className: fallbackClassNames, style: style, children: fallback }));
47
+ return (_jsx("div", { ...attributes, className: classNames([s.fallback, rootClassNames]), style: style, children: fallback }));
44
48
  }
45
49
  const imageAttributes = {
46
50
  ...attributes,
@@ -50,11 +54,12 @@ const Image = (props) => {
50
54
  role: alt ? undefined : "presentation",
51
55
  onLoad: handleLoad,
52
56
  onError: handleError,
53
- className: imgClassNames,
57
+ className: outline ? imageClassNames : classNames([imageClassNames, rootClassNames]),
54
58
  style,
55
59
  };
56
60
  // eslint-disable-next-line jsx-a11y/alt-text
57
- return renderImage ? renderImage(imageAttributes) : _jsx("img", { ...imageAttributes });
61
+ const imageNode = renderImage ? renderImage(imageAttributes) : _jsx("img", { ...imageAttributes });
62
+ return outline ? (_jsx("div", { ...attributes, className: rootClassNames, style: style, children: imageNode })) : (imageNode);
58
63
  };
59
64
  Image.displayName = "Image";
60
65
  export default Image;
@@ -1 +1 @@
1
- .root{display:block;max-width:100%}.fallback{align-items:center;background:var(--rs-color-background-neutral-faded);color:var(--rs-color-foreground-disabled);display:flex;justify-content:center}.--display-mode-cover{object-fit:cover!important}.--display-mode-contain{object-fit:scale-down!important}
1
+ .root{position:relative}.root .image{border-radius:inherit;height:100%;width:100%}.image{display:block;max-width:100%}.fallback{align-items:center;background:var(--rs-color-background-neutral-faded);color:var(--rs-color-foreground-disabled);display:flex;justify-content:center}.--outline:after{border:1px solid var(--rs-color-border-neutral-faded);border-radius:inherit;content:"";inset:0;pointer-events:none;position:absolute}.image--display-mode-cover{object-fit:cover!important}.image--display-mode-contain{object-fit:scale-down!important}
@@ -15,9 +15,11 @@ export type Props = {
15
15
  /** Image aspect ratio, width / height */
16
16
  aspectRatio?: G.Responsive<number>;
17
17
  /** Image border radius, based on the radius tokens */
18
- borderRadius?: Extract<TStyles.Radius, "small" | "medium" | "large">;
18
+ borderRadius?: Extract<TStyles.Radius, "small" | "medium" | "large" | "circular">;
19
19
  /** Image display mode for controlling how it fits into the provided boundaries */
20
20
  displayMode?: "cover" | "contain";
21
+ /** Add a semi-transparent border on top of the image for better background contrast */
22
+ outline?: boolean;
21
23
  /** Image on load event */
22
24
  onLoad?: (e: React.SyntheticEvent) => void;
23
25
  /** Image on error event */
@@ -7,8 +7,7 @@ import useElementId from "../../hooks/useElementId.js";
7
7
  import useHandlerRef from "../../hooks/useHandlerRef.js";
8
8
  import useResponsiveClientValue from "../../hooks/useResponsiveClientValue.js";
9
9
  import { resolveMixin } from "../../styles/mixin.js";
10
- import { enableUserSelect, disableUserSelect } from "../../utilities/dom/index.js";
11
- import { classNames, responsiveVariables, responsiveClassNames, responsivePropDependency, } from "../../utilities/props.js";
10
+ import { classNames, responsiveVariables, responsiveClassNames } from "../../utilities/props.js";
12
11
  import { enableScroll, disableScroll } from "../../utilities/scroll/index.js";
13
12
  import s from "./Modal.module.css";
14
13
  const DRAG_THRESHOLD = 32;
@@ -90,7 +89,6 @@ const Modal = (props) => {
90
89
  // Prevent the drag handling when browser is trying to navigate to a previous page
91
90
  if (clientPosition === "start" && e.targetTouches[0].clientX < DRAG_EDGE_BOUNDARY)
92
91
  return;
93
- disableUserSelect();
94
92
  disableScroll();
95
93
  setDragging(true);
96
94
  };
@@ -108,7 +106,6 @@ const Modal = (props) => {
108
106
  if (!dragging)
109
107
  return;
110
108
  const handleDragEnd = () => {
111
- enableUserSelect();
112
109
  enableScroll();
113
110
  setDragging(false);
114
111
  // Close only when dragging in the closing direction
@@ -168,7 +165,7 @@ const Modal = (props) => {
168
165
  setHideProgress(progress / 2);
169
166
  dragDistanceRef.current = dragDistance;
170
167
  }, [dragDistance, clientPosition, rootRef]);
171
- return (_jsx(Overlay, { onClose: onClose, onOpen: onOpen, onAfterClose: onAfterClose, onAfterOpen: onAfterOpen, disableCloseOnClick: disableCloseOnOutsideClick, active: active, transparent: transparentOverlay || hideProgress, blurred: blurredOverlay, overflow: responsivePropDependency(position, (p) => (p === "center" ? "auto" : "hidden")), className: overlayClassName, containerRef: containerRef, attributes: {
168
+ return (_jsx(Overlay, { onClose: onClose, onOpen: onOpen, onAfterClose: onAfterClose, onAfterOpen: onAfterOpen, disableCloseOnClick: disableCloseOnOutsideClick, active: active, transparent: transparentOverlay || hideProgress, blurred: blurredOverlay, overflow: clientPosition === "center" ? "auto" : "hidden", className: overlayClassName, containerRef: containerRef, attributes: {
172
169
  onTouchStart: handleDragStart,
173
170
  }, children: ({ active }) => {
174
171
  const rootClassNames = classNames(s.root, className, active && s["--active"], dragging && s["--dragging"], overflow && s[`--overflow-${overflow}`], containerRef && s["--contained"], responsiveClassNames(s, "--position", position), mixinStyles.classNames);