@vellira-ui/react-native 2.52.0 → 2.53.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 (2) hide show
  1. package/dist/index.js +184 -57
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,75 +3,185 @@ import { Check, ChevronDown, Close, Search } from "@vellira-ui/icons";
3
3
  import { AccessibilityInfo, ActivityIndicator, Animated, Dimensions, Easing, FlatList, Modal as Modal$1, Platform, Pressable, ScrollView, StyleSheet, Text, TextInput, View, findNodeHandle, useWindowDimensions } from "react-native";
4
4
  import { darkTheme, highContrastTheme, lightTheme } from "@vellira-ui/tokens";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ //#region src/managers/FloatingManager/computeFloatingPosition.ts
7
+ function clamp(value, min, max) {
8
+ return Math.min(Math.max(value, min), Math.max(min, max));
9
+ }
10
+ function parsePlacement(placement) {
11
+ const [side, align = "center"] = placement.split("-");
12
+ return {
13
+ side,
14
+ align
15
+ };
16
+ }
17
+ function createPlacement(side, align) {
18
+ return align === "center" ? side : `${side}-${align}`;
19
+ }
20
+ function getOppositeSide(side) {
21
+ switch (side) {
22
+ case "top": return "bottom";
23
+ case "right": return "left";
24
+ case "bottom": return "top";
25
+ case "left": return "right";
26
+ }
27
+ }
28
+ function computeBasePosition({ reference, floating, side, align, offset }) {
29
+ const referenceCenterX = reference.x + reference.width / 2;
30
+ const referenceCenterY = reference.y + reference.height / 2;
31
+ const alignedLeft = align === "start" ? reference.x : align === "end" ? reference.x + reference.width - floating.width : referenceCenterX - floating.width / 2;
32
+ const alignedTop = align === "start" ? reference.y : align === "end" ? reference.y + reference.height - floating.height : referenceCenterY - floating.height / 2;
33
+ switch (side) {
34
+ case "top": return {
35
+ top: reference.y - floating.height - offset,
36
+ left: alignedLeft
37
+ };
38
+ case "right": return {
39
+ top: alignedTop,
40
+ left: reference.x + reference.width + offset
41
+ };
42
+ case "bottom": return {
43
+ top: reference.y + reference.height + offset,
44
+ left: alignedLeft
45
+ };
46
+ case "left": return {
47
+ top: alignedTop,
48
+ left: reference.x - floating.width - offset
49
+ };
50
+ }
51
+ }
52
+ function getMainAxisOverflow({ side, position, floating, boundary, padding }) {
53
+ switch (side) {
54
+ case "top": return padding - position.top;
55
+ case "right": return position.left + floating.width + padding - boundary.width;
56
+ case "bottom": return position.top + floating.height + padding - boundary.height;
57
+ case "left": return padding - position.left;
58
+ }
59
+ }
60
+ function computeFloatingPosition({ reference, floating, boundary, placement, offset = 8, padding = 12, arrowPadding = 16, flip = true, shift = true }) {
61
+ const parsed = parsePlacement(placement);
62
+ let resolvedSide = parsed.side;
63
+ let position = computeBasePosition({
64
+ reference,
65
+ floating,
66
+ side: resolvedSide,
67
+ align: parsed.align,
68
+ offset
69
+ });
70
+ if (flip && getMainAxisOverflow({
71
+ side: resolvedSide,
72
+ position,
73
+ floating,
74
+ boundary,
75
+ padding
76
+ }) > 0) {
77
+ const oppositeSide = getOppositeSide(resolvedSide);
78
+ const oppositePosition = computeBasePosition({
79
+ reference,
80
+ floating,
81
+ side: oppositeSide,
82
+ align: parsed.align,
83
+ offset
84
+ });
85
+ if (getMainAxisOverflow({
86
+ side: oppositeSide,
87
+ position: oppositePosition,
88
+ floating,
89
+ boundary,
90
+ padding
91
+ }) <= 0) {
92
+ resolvedSide = oppositeSide;
93
+ position = oppositePosition;
94
+ }
95
+ }
96
+ if (shift) position = {
97
+ top: clamp(position.top, padding, boundary.height - floating.height - padding),
98
+ left: clamp(position.left, padding, boundary.width - floating.width - padding)
99
+ };
100
+ const referenceCenterX = reference.x + reference.width / 2;
101
+ const referenceCenterY = reference.y + reference.height / 2;
102
+ const arrowPosition = resolvedSide === "top" || resolvedSide === "bottom" ? { left: clamp(referenceCenterX - position.left, arrowPadding, floating.width - arrowPadding) } : { top: clamp(referenceCenterY - position.top, arrowPadding, floating.height - arrowPadding) };
103
+ return {
104
+ position,
105
+ arrowPosition,
106
+ placement: createPlacement(resolvedSide, parsed.align)
107
+ };
108
+ }
109
+ //#endregion
6
110
  //#region src/managers/FloatingManager/useNativeFloatingPosition.ts
7
- const safePadding = 12;
8
111
  function useNativeFloatingPosition(placement = "top", offset = 8) {
9
- const [position, setPosition] = useState({
10
- top: 0,
11
- left: 0
112
+ const [result, setResult] = useState({
113
+ position: {
114
+ top: 0,
115
+ left: 0
116
+ },
117
+ arrowPosition: {},
118
+ placement
12
119
  });
13
120
  const floatingSizeRef = useRef({
14
121
  width: 0,
15
122
  height: 0
16
123
  });
17
124
  const lastTriggerRef = useRef(null);
18
- const clamp = useCallback((value, min, max) => {
19
- return Math.min(Math.max(value, min), Math.max(min, max));
20
- }, []);
21
- const calculatePosition = useCallback((triggerRect, size) => {
22
- const { width: screenWidth, height: screenHeight } = Dimensions.get("window");
23
- const [side, align = "center"] = placement.split("-");
24
- const horizontalTop = side === "bottom" ? triggerRect.y + triggerRect.height + offset : triggerRect.y - size.height - offset;
25
- const verticalTop = align === "start" ? triggerRect.y : align === "end" ? triggerRect.y + triggerRect.height - size.height : triggerRect.y + triggerRect.height / 2 - size.height / 2;
26
- const horizontalLeft = align === "start" ? triggerRect.x : align === "end" ? triggerRect.x + triggerRect.width - size.width : triggerRect.x + triggerRect.width / 2 - size.width / 2;
27
- const verticalLeft = side === "right" ? triggerRect.x + triggerRect.width + offset : triggerRect.x - size.width - offset;
28
- const rawPosition = side === "left" || side === "right" ? {
29
- top: verticalTop,
30
- left: verticalLeft
31
- } : {
32
- top: horizontalTop,
33
- left: horizontalLeft
34
- };
35
- return {
36
- top: clamp(rawPosition.top, safePadding, screenHeight - size.height - safePadding),
37
- left: clamp(rawPosition.left, safePadding, screenWidth - size.width - safePadding)
38
- };
39
- }, [
40
- placement,
41
- offset,
42
- clamp
43
- ]);
44
- const updatePosition = useCallback((triggerRef, measuredSize = floatingSizeRef.current) => {
125
+ const lastContainerRef = useRef(null);
126
+ const updatePosition = useCallback((triggerRef, containerRef, measuredSize = floatingSizeRef.current) => {
45
127
  lastTriggerRef.current = triggerRef;
46
- const node = triggerRef.current;
47
- if (!node || typeof node.measureInWindow !== "function") {
48
- setPosition({
49
- top: 0,
50
- left: 0
51
- });
128
+ lastContainerRef.current = containerRef ?? null;
129
+ const triggerNode = triggerRef.current;
130
+ const containerNode = containerRef?.current;
131
+ if (!triggerNode || typeof triggerNode.measureInWindow !== "function") {
132
+ setResult((current) => ({
133
+ ...current,
134
+ position: {
135
+ top: 0,
136
+ left: 0
137
+ }
138
+ }));
52
139
  return;
53
140
  }
54
- node.measureInWindow((x, y, width, height) => {
55
- setPosition(calculatePosition({
56
- x,
57
- y,
58
- width,
59
- height
60
- }, measuredSize));
141
+ triggerNode.measureInWindow((x, y, width, height) => {
142
+ const commitPosition = (containerX, containerY, containerWidth, containerHeight) => {
143
+ const nextResult = computeFloatingPosition({
144
+ reference: {
145
+ x: x - containerX,
146
+ y: y - containerY,
147
+ width,
148
+ height
149
+ },
150
+ floating: measuredSize,
151
+ boundary: {
152
+ width: containerWidth,
153
+ height: containerHeight
154
+ },
155
+ placement,
156
+ offset
157
+ });
158
+ setResult(nextResult);
159
+ };
160
+ if (!containerNode || typeof containerNode.measureInWindow !== "function") {
161
+ const window = Dimensions.get("window");
162
+ commitPosition(0, 0, window.width, window.height);
163
+ return;
164
+ }
165
+ containerNode.measureInWindow((containerX, containerY, containerWidth, containerHeight) => {
166
+ commitPosition(containerX, containerY, containerWidth, containerHeight);
167
+ });
61
168
  });
62
- }, [calculatePosition]);
169
+ }, [placement, offset]);
170
+ const onFloatingLayout = useCallback((event) => {
171
+ const { width, height } = event.nativeEvent.layout;
172
+ const nextSize = {
173
+ width,
174
+ height
175
+ };
176
+ floatingSizeRef.current = nextSize;
177
+ if (lastTriggerRef.current) updatePosition(lastTriggerRef.current, lastContainerRef.current ?? void 0, nextSize);
178
+ }, [updatePosition]);
63
179
  return {
64
- position,
180
+ position: result.position,
181
+ arrowPosition: result.arrowPosition,
182
+ placement: result.placement,
65
183
  updatePosition,
66
- onFloatingLayout: useCallback((event) => {
67
- const { width, height } = event.nativeEvent.layout;
68
- const nextSize = {
69
- width,
70
- height
71
- };
72
- floatingSizeRef.current = nextSize;
73
- if (lastTriggerRef.current) updatePosition(lastTriggerRef.current, nextSize);
74
- }, [updatePosition])
184
+ onFloatingLayout
75
185
  };
76
186
  }
77
187
  //#endregion
@@ -5675,13 +5785,30 @@ Input.displayName = "Input";
5675
5785
  //#endregion
5676
5786
  //#region src/primitives/Portal/Portal.tsx
5677
5787
  const PortalContext = createContext(null);
5788
+ const styles = StyleSheet.create({ host: { flex: 1 } });
5678
5789
  const PortalProvider = ({ children, container = null }) => /* @__PURE__ */ jsx(PortalContext.Provider, {
5679
5790
  value: container,
5680
5791
  children
5681
5792
  });
5682
- const Portal = ({ children }) => {
5793
+ const Portal = ({ children, visible = true, animationType = "none", hardwareAccelerated = true, navigationBarTranslucent = true, statusBarTranslucent = true, onDismiss, onRequestClose }) => {
5683
5794
  useContext(PortalContext);
5684
- return /* @__PURE__ */ jsx(Fragment, { children });
5795
+ if (!visible) return null;
5796
+ return /* @__PURE__ */ jsx(Modal$1, {
5797
+ animationType,
5798
+ hardwareAccelerated,
5799
+ navigationBarTranslucent,
5800
+ onDismiss,
5801
+ onRequestClose,
5802
+ presentationStyle: "overFullScreen",
5803
+ statusBarTranslucent,
5804
+ transparent: true,
5805
+ visible: true,
5806
+ children: /* @__PURE__ */ jsx(View, {
5807
+ pointerEvents: "box-none",
5808
+ style: styles.host,
5809
+ children
5810
+ })
5811
+ });
5685
5812
  };
5686
5813
  Portal.__velliraPortal = true;
5687
5814
  Portal.displayName = "Portal";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellira-ui/react-native",
3
- "version": "2.52.0",
3
+ "version": "2.53.0",
4
4
  "description": "React Native components for Vellira Design System",
5
5
  "author": "Roman Bakurov",
6
6
  "license": "MIT",