@idealyst/components 1.2.132 → 1.2.134

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idealyst/components",
3
- "version": "1.2.132",
3
+ "version": "1.2.134",
4
4
  "description": "Shared component library for React and React Native",
5
5
  "documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
6
6
  "readme": "README.md",
@@ -56,7 +56,7 @@
56
56
  "publish:npm": "npm publish"
57
57
  },
58
58
  "peerDependencies": {
59
- "@idealyst/theme": "^1.2.132",
59
+ "@idealyst/theme": "^1.2.134",
60
60
  "@mdi/js": ">=7.0.0",
61
61
  "@mdi/react": ">=1.0.0",
62
62
  "@react-native-vector-icons/common": ">=12.0.0",
@@ -111,8 +111,8 @@
111
111
  },
112
112
  "devDependencies": {
113
113
  "@idealyst/blur": "^1.2.40",
114
- "@idealyst/theme": "^1.2.132",
115
- "@idealyst/tooling": "^1.2.132",
114
+ "@idealyst/theme": "^1.2.134",
115
+ "@idealyst/tooling": "^1.2.134",
116
116
  "@mdi/react": "^1.6.1",
117
117
  "@types/react": "^19.1.0",
118
118
  "react": "^19.1.0",
@@ -1,10 +1,12 @@
1
- import { forwardRef, useMemo } from 'react';
1
+ import { forwardRef, useRef, useMemo } from 'react';
2
2
  import { ActivityIndicator, StyleSheet as RNStyleSheet, Text, TouchableOpacity, View } from 'react-native';
3
3
  import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
4
4
  import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
5
5
  import { buttonStyles } from './Button.styles';
6
6
  import { ButtonProps } from './types';
7
+ import { createPressEvent } from '../utils/events';
7
8
  import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
9
+ import useMergeRefs from '../hooks/useMergeRefs';
8
10
  import type { IdealystElement } from '../utils/refTypes';
9
11
 
10
12
  const Button = forwardRef<IdealystElement, ButtonProps>((props, ref) => {
@@ -36,6 +38,9 @@ const Button = forwardRef<IdealystElement, ButtonProps>((props, ref) => {
36
38
  accessibilityPressed,
37
39
  } = props;
38
40
 
41
+ const internalRef = useRef<IdealystElement>(null);
42
+ const mergedRef = useMergeRefs(ref, internalRef);
43
+
39
44
  // Button is effectively disabled when loading
40
45
  const isDisabled = disabled || loading;
41
46
 
@@ -168,8 +173,8 @@ const Button = forwardRef<IdealystElement, ButtonProps>((props, ref) => {
168
173
 
169
174
  // TouchableOpacity types don't include nativeID but it's a valid RN prop
170
175
  const touchableProps = {
171
- ref,
172
- onPress: pressHandler,
176
+ ref: mergedRef,
177
+ onPress: pressHandler ? (e: any) => pressHandler(createPressEvent(e, 'press', internalRef)) : undefined,
173
178
  disabled: isDisabled,
174
179
  testID,
175
180
  nativeID: id,
@@ -3,6 +3,7 @@ import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { ButtonProps } from './types';
4
4
  import { buttonStyles } from './Button.styles';
5
5
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
6
+ import { createPressEvent } from '../utils/events';
6
7
  import useMergeRefs from '../hooks/useMergeRefs';
7
8
  import { getWebInteractiveAriaProps, generateAccessibilityId } from '../utils/accessibility';
8
9
  import type { IdealystElement } from '../utils/refTypes';
@@ -67,7 +68,7 @@ const Button = forwardRef<IdealystElement, ButtonProps>((props, ref) => {
67
68
  e.preventDefault();
68
69
  e.stopPropagation();
69
70
  if (!isDisabled && pressHandler) {
70
- pressHandler();
71
+ pressHandler(createPressEvent(e));
71
72
  }
72
73
  };
73
74
 
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
3
  import type { IconName } from '../Icon/icon-types';
4
+ import type { PressEvent } from '../utils/events';
4
5
  import { Intent, Size } from '@idealyst/theme';
5
6
  import { BaseProps } from '../utils/viewStyleProps';
6
7
  import { InteractiveAccessibilityProps } from '../utils/accessibility';
@@ -31,13 +32,13 @@ export interface ButtonProps extends BaseProps, InteractiveAccessibilityProps {
31
32
  /**
32
33
  * Called when the button is pressed
33
34
  */
34
- onPress?: () => void;
35
+ onPress?: (event: PressEvent) => void;
35
36
 
36
37
  /**
37
38
  * @deprecated Use `onPress` instead. This prop exists for web compatibility only.
38
39
  * Using onClick will log a deprecation warning in development.
39
40
  */
40
- onClick?: () => void;
41
+ onClick?: (event: PressEvent) => void;
41
42
 
42
43
  /**
43
44
  * Whether the button is disabled
@@ -1,11 +1,13 @@
1
- import { forwardRef, useMemo } from 'react';
1
+ import { forwardRef, useRef, useMemo } from 'react';
2
2
  import { ActivityIndicator, StyleSheet as RNStyleSheet, TouchableOpacity, View } from 'react-native';
3
3
  import MaterialDesignIcons from '@react-native-vector-icons/material-design-icons';
4
4
  import Svg, { Defs, LinearGradient, Stop, Rect } from 'react-native-svg';
5
5
  import { useUnistyles } from 'react-native-unistyles';
6
6
  import { iconButtonStyles } from './IconButton.styles';
7
7
  import { IconButtonProps } from './types';
8
+ import { createPressEvent } from '../utils/events';
8
9
  import { getNativeInteractiveAccessibilityProps } from '../utils/accessibility';
10
+ import useMergeRefs from '../hooks/useMergeRefs';
9
11
  import type { IdealystElement } from '../utils/refTypes';
10
12
  import type { Theme } from '@idealyst/theme';
11
13
 
@@ -36,6 +38,9 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
36
38
  accessibilityPressed,
37
39
  } = props;
38
40
 
41
+ const internalRef = useRef<IdealystElement>(null);
42
+ const mergedRef = useMergeRefs(ref, internalRef);
43
+
39
44
  // Get theme for icon size
40
45
  const { theme } = useUnistyles() as { theme: Theme };
41
46
 
@@ -153,8 +158,8 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
153
158
 
154
159
  // TouchableOpacity types don't include nativeID but it's a valid RN prop
155
160
  const touchableProps = {
156
- ref,
157
- onPress: pressHandler,
161
+ ref: mergedRef,
162
+ onPress: pressHandler ? (e: any) => pressHandler(createPressEvent(e, 'press', internalRef)) : undefined,
158
163
  disabled: isDisabled,
159
164
  testID,
160
165
  nativeID: id,
@@ -4,6 +4,7 @@ import { useUnistyles } from 'react-native-unistyles';
4
4
  import { IconButtonProps } from './types';
5
5
  import { iconButtonStyles } from './IconButton.styles';
6
6
  import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
7
+ import { createPressEvent } from '../utils/events';
7
8
  import useMergeRefs from '../hooks/useMergeRefs';
8
9
  import { getWebInteractiveAriaProps, generateAccessibilityId } from '../utils/accessibility';
9
10
  import type { IdealystElement } from '../utils/refTypes';
@@ -72,7 +73,7 @@ const IconButton = forwardRef<IdealystElement, IconButtonProps>((props, ref) =>
72
73
  e.preventDefault();
73
74
  e.stopPropagation();
74
75
  if (!isDisabled && pressHandler) {
75
- pressHandler();
76
+ pressHandler(createPressEvent(e));
76
77
  }
77
78
  };
78
79
 
@@ -4,6 +4,7 @@ import type { IconName } from '../Icon/icon-types';
4
4
  import { Intent, Size } from '@idealyst/theme';
5
5
  import { BaseProps } from '../utils/viewStyleProps';
6
6
  import { InteractiveAccessibilityProps } from '../utils/accessibility';
7
+ import type { PressEvent } from '../utils/events';
7
8
 
8
9
  // Component-specific type aliases for future extensibility
9
10
  export type IconButtonType = 'contained' | 'outlined' | 'text';
@@ -31,13 +32,13 @@ export interface IconButtonProps extends BaseProps, InteractiveAccessibilityProp
31
32
  /**
32
33
  * Called when the button is pressed
33
34
  */
34
- onPress?: () => void;
35
+ onPress?: (event: PressEvent) => void;
35
36
 
36
37
  /**
37
38
  * @deprecated Use `onPress` instead. This prop exists for web compatibility only.
38
39
  * Using onClick will log a deprecation warning in development.
39
40
  */
40
- onClick?: () => void;
41
+ onClick?: (event: PressEvent) => void;
41
42
 
42
43
  /**
43
44
  * Whether the button is disabled
@@ -1,7 +1,9 @@
1
- import { forwardRef } from 'react';
1
+ import { forwardRef, useRef } from 'react';
2
2
  import { TouchableWithoutFeedback, View } from 'react-native';
3
3
  import { PressableProps } from './types';
4
4
  import { pressableStyles } from './Pressable.styles';
5
+ import { createPressEvent } from '../utils/events';
6
+ import useMergeRefs from '../hooks/useMergeRefs';
5
7
  import type { IdealystElement } from '../utils/refTypes';
6
8
 
7
9
  const Pressable = forwardRef<IdealystElement, PressableProps>(({
@@ -20,6 +22,9 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
20
22
  accessibilityRole: _accessibilityRole,
21
23
  id,
22
24
  }, ref) => {
25
+ const internalRef = useRef<IdealystElement>(null);
26
+ const mergedRef = useMergeRefs(ref, internalRef);
27
+
23
28
  // Apply spacing variants
24
29
  pressableStyles.useVariants({
25
30
  padding,
@@ -31,14 +36,14 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
31
36
 
32
37
  return (
33
38
  <TouchableWithoutFeedback
34
- onPress={disabled ? undefined : onPress}
35
- onPressIn={disabled ? undefined : onPressIn}
36
- onPressOut={disabled ? undefined : onPressOut}
39
+ onPress={disabled ? undefined : (e) => onPress?.(createPressEvent(e, 'press', internalRef))}
40
+ onPressIn={disabled ? undefined : (e) => onPressIn?.(createPressEvent(e, 'pressIn', internalRef))}
41
+ onPressOut={disabled ? undefined : (e) => onPressOut?.(createPressEvent(e, 'pressOut', internalRef))}
37
42
  disabled={disabled}
38
43
  testID={testID}
39
44
  accessibilityLabel={accessibilityLabel}
40
45
  >
41
- <View ref={ref as any} nativeID={id} style={[pressableStyle, style]}>
46
+ <View ref={mergedRef as any} nativeID={id} style={[pressableStyle, style]}>
42
47
  {children}
43
48
  </View>
44
49
  </TouchableWithoutFeedback>
@@ -47,4 +52,4 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
47
52
 
48
53
  Pressable.displayName = 'Pressable';
49
54
 
50
- export default Pressable;
55
+ export default Pressable;
@@ -1,7 +1,9 @@
1
- import React, { useCallback, useState, forwardRef } from 'react';
1
+ import React, { useCallback, useRef, useState, forwardRef } from 'react';
2
2
  import { getWebProps } from 'react-native-unistyles/web';
3
3
  import { PressableProps } from './types';
4
4
  import { pressableStyles } from './Pressable.styles';
5
+ import { createPressEvent, createBaseSyntheticEvent } from '../utils/events';
6
+ import type { PressEvent } from '../utils/events';
5
7
  import useMergeRefs from '../hooks/useMergeRefs';
6
8
  import type { IdealystElement } from '../utils/refTypes';
7
9
  import { flattenStyle } from '../utils/flattenStyle';
@@ -22,6 +24,7 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
22
24
  accessibilityRole = 'button',
23
25
  id,
24
26
  }, ref) => {
27
+ const internalRef = useRef<IdealystElement>(null);
25
28
  const [_isPressed, setIsPressed] = useState(false);
26
29
 
27
30
  const handleMouseDown = useCallback((e: React.MouseEvent) => {
@@ -29,7 +32,7 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
29
32
  e.stopPropagation();
30
33
  if (disabled) return;
31
34
  setIsPressed(true);
32
- onPressIn?.();
35
+ onPressIn?.(createPressEvent(e as React.MouseEvent<HTMLElement>, 'pressIn'));
33
36
  }, [disabled, onPressIn]);
34
37
 
35
38
  const handleMouseUp = useCallback((e: React.MouseEvent) => {
@@ -37,14 +40,14 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
37
40
  e.stopPropagation();
38
41
  if (disabled) return;
39
42
  setIsPressed(false);
40
- onPressOut?.();
43
+ onPressOut?.(createPressEvent(e as React.MouseEvent<HTMLElement>, 'pressOut'));
41
44
  }, [disabled, onPressOut]);
42
45
 
43
46
  const handleClick = useCallback((e: React.MouseEvent) => {
44
47
  e.preventDefault();
45
48
  e.stopPropagation();
46
49
  if (disabled) return;
47
- onPress?.();
50
+ onPress?.(createPressEvent(e as React.MouseEvent<HTMLElement>));
48
51
  }, [disabled, onPress]);
49
52
 
50
53
  const handleKeyDown = useCallback((event: React.KeyboardEvent) => {
@@ -52,7 +55,12 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
52
55
  if (event.key === 'Enter' || event.key === ' ') {
53
56
  event.preventDefault();
54
57
  event.stopPropagation();
55
- onPress?.();
58
+ const pressEvent: PressEvent = {
59
+ ...createBaseSyntheticEvent(event.nativeEvent),
60
+ type: 'press',
61
+ targetRef: internalRef,
62
+ };
63
+ onPress?.(pressEvent);
56
64
  }
57
65
  }, [disabled, onPress]);
58
66
 
@@ -65,8 +73,8 @@ const Pressable = forwardRef<IdealystElement, PressableProps>(({
65
73
 
66
74
  const webProps = getWebProps([(pressableStyles.pressable as any)({ disabled }), flattenStyle(style)]);
67
75
 
68
- // Merge ref from getWebProps with forwarded ref
69
- const mergedRef = useMergeRefs(ref as any, webProps.ref as any);
76
+ // Merge ref from getWebProps with forwarded ref and internal ref
77
+ const mergedRef = useMergeRefs(ref as any, webProps.ref as any, internalRef);
70
78
 
71
79
  return (
72
80
  <div
@@ -1,6 +1,7 @@
1
1
  import type { ReactNode } from 'react';
2
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
3
  import { PressableSpacingStyleProps } from '../utils/viewStyleProps';
4
+ import type { PressEvent } from '../utils/events';
4
5
 
5
6
  export interface PressableProps extends PressableSpacingStyleProps {
6
7
  /**
@@ -11,17 +12,17 @@ export interface PressableProps extends PressableSpacingStyleProps {
11
12
  /**
12
13
  * Called when the press gesture is activated
13
14
  */
14
- onPress?: () => void;
15
+ onPress?: (event: PressEvent) => void;
15
16
 
16
17
  /**
17
18
  * Called when the press gesture starts
18
19
  */
19
- onPressIn?: () => void;
20
+ onPressIn?: (event: PressEvent) => void;
20
21
 
21
22
  /**
22
23
  * Called when the press gesture ends
23
24
  */
24
- onPressOut?: () => void;
25
+ onPressOut?: (event: PressEvent) => void;
25
26
 
26
27
  /**
27
28
  * Whether the pressable is disabled
@@ -24,16 +24,20 @@ import type {
24
24
  ScrollEvent,
25
25
  SubmitEvent,
26
26
  } from './types';
27
+ import type { RefObject } from 'react';
28
+ import type { IdealystElement } from '../refTypes';
27
29
 
28
30
  // No-op functions for native (these concepts don't apply)
29
31
  const noop = () => {};
30
32
 
31
33
  /**
32
- * Wraps a React Native GestureResponderEvent into a standardized PressEvent
34
+ * Wraps a React Native GestureResponderEvent into a standardized PressEvent.
35
+ * Pass the component's ref object as `targetRef` so consumers can use it for anchoring.
33
36
  */
34
37
  export function createPressEvent(
35
38
  event: GestureResponderEvent,
36
- type: PressEvent['type'] = 'press'
39
+ type: PressEvent['type'] = 'press',
40
+ targetRef?: RefObject<IdealystElement>
37
41
  ): PressEvent {
38
42
  return {
39
43
  nativeEvent: event.nativeEvent,
@@ -41,6 +45,7 @@ export function createPressEvent(
41
45
  defaultPrevented: false,
42
46
  propagationStopped: false,
43
47
  type,
48
+ targetRef: targetRef ?? { current: event.target },
44
49
  preventDefault: noop,
45
50
  stopPropagation: noop,
46
51
  };
@@ -35,6 +35,7 @@ export function createPressEvent(
35
35
  defaultPrevented: event.defaultPrevented,
36
36
  propagationStopped: false,
37
37
  type,
38
+ targetRef: { current: event.currentTarget },
38
39
  preventDefault: () => event.preventDefault(),
39
40
  stopPropagation: () => event.stopPropagation(),
40
41
  };
@@ -6,6 +6,9 @@
6
6
  * React Native's event system doesn't have these concepts in the same way.
7
7
  */
8
8
 
9
+ import type { RefObject } from 'react';
10
+ import type { IdealystElement } from '../refTypes';
11
+
9
12
  /**
10
13
  * Base synthetic event interface that normalizes web and native events
11
14
  */
@@ -53,6 +56,24 @@ export interface PressEvent extends SyntheticEvent {
53
56
  * The type of press event
54
57
  */
55
58
  type: 'press' | 'pressIn' | 'pressOut' | 'longPress';
59
+
60
+ /**
61
+ * A ref pointing to the element that fired the event.
62
+ * Can be passed directly as an anchor to Menu, Popover, etc.
63
+ *
64
+ * @example
65
+ * ```tsx
66
+ * const [open, setOpen] = useState(false);
67
+ * const [anchor, setAnchor] = useState<RefObject<IdealystElement> | null>(null);
68
+ *
69
+ * <Button onPress={(e) => {
70
+ * setAnchor(e.targetRef);
71
+ * setOpen(true);
72
+ * }}>Actions</Button>
73
+ * <Menu anchor={anchor} open={open} onOpenChange={setOpen} items={items} />
74
+ * ```
75
+ */
76
+ targetRef: RefObject<IdealystElement>;
56
77
  }
57
78
 
58
79
  /**