@idealyst/components 1.0.95 → 1.0.97
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 +3 -3
- package/src/Button/Button.native.tsx +6 -6
- package/src/Input/Input.native.tsx +9 -1
- package/src/Input/Input.web.tsx +22 -5
- package/src/Input/types.ts +6 -0
- package/src/Select/Select.styles.tsx +18 -26
- package/src/Select/Select.web.tsx +68 -28
- package/src/index.native.ts +3 -0
- package/src/index.ts +3 -0
- package/src/utils/events/events.native.ts +212 -0
- package/src/utils/events/events.web.ts +184 -0
- package/src/utils/events/index.ts +46 -0
- package/src/utils/events/index.web.ts +44 -0
- package/src/utils/events/types.ts +231 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.97",
|
|
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",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"publish:npm": "npm publish"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
|
-
"@idealyst/theme": "^1.0.
|
|
44
|
+
"@idealyst/theme": "^1.0.97",
|
|
45
45
|
"@mdi/js": ">=7.0.0",
|
|
46
46
|
"@mdi/react": ">=1.0.0",
|
|
47
47
|
"@react-native-vector-icons/common": ">=12.0.0",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"devDependencies": {
|
|
94
|
-
"@idealyst/theme": "^1.0.
|
|
94
|
+
"@idealyst/theme": "^1.0.97",
|
|
95
95
|
"@mdi/react": "^1.6.1",
|
|
96
96
|
"@types/react": "^19.1.0",
|
|
97
97
|
"react": "^19.1.0",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
1
|
+
import React, { ComponentRef, forwardRef, isValidElement } from 'react';
|
|
2
|
+
import { Text, TouchableOpacity, View } from 'react-native';
|
|
3
3
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
4
|
-
import { ButtonProps } from './types';
|
|
5
4
|
import { buttonStyles } from './Button.styles';
|
|
5
|
+
import { ButtonProps } from './types';
|
|
6
6
|
|
|
7
7
|
const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((props, ref) => {
|
|
8
8
|
const {
|
|
@@ -20,9 +20,9 @@ const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonProps>((p
|
|
|
20
20
|
} = props;
|
|
21
21
|
|
|
22
22
|
// Compute dynamic styles
|
|
23
|
-
const buttonStyle = buttonStyles.button
|
|
24
|
-
const textStyle = buttonStyles.text
|
|
25
|
-
const iconStyle = buttonStyles.icon
|
|
23
|
+
const buttonStyle = buttonStyles.button;
|
|
24
|
+
const textStyle = buttonStyles.text;
|
|
25
|
+
const iconStyle = buttonStyles.icon;
|
|
26
26
|
|
|
27
27
|
// Map button size to icon size
|
|
28
28
|
const iconSizeMap = {
|
|
@@ -9,6 +9,7 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
9
9
|
onChangeText,
|
|
10
10
|
onFocus,
|
|
11
11
|
onBlur,
|
|
12
|
+
onPress,
|
|
12
13
|
placeholder,
|
|
13
14
|
disabled = false,
|
|
14
15
|
inputType = 'text',
|
|
@@ -50,6 +51,12 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
|
|
54
|
+
const handlePress = () => {
|
|
55
|
+
if (onPress) {
|
|
56
|
+
onPress();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
53
60
|
const handleBlur = () => {
|
|
54
61
|
setIsFocused(false);
|
|
55
62
|
if (onBlur) {
|
|
@@ -111,7 +118,7 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
111
118
|
};
|
|
112
119
|
|
|
113
120
|
return (
|
|
114
|
-
<View style={[inputStyles.container
|
|
121
|
+
<View style={[inputStyles.container, style]} testID={testID}>
|
|
115
122
|
{/* Left Icon */}
|
|
116
123
|
{leftIcon && (
|
|
117
124
|
<View style={inputStyles.leftIconContainer}>
|
|
@@ -121,6 +128,7 @@ const Input = React.forwardRef<TextInput, InputProps>(({
|
|
|
121
128
|
|
|
122
129
|
{/* Input */}
|
|
123
130
|
<TextInput
|
|
131
|
+
onPress={handlePress}
|
|
124
132
|
ref={ref}
|
|
125
133
|
value={value}
|
|
126
134
|
onChangeText={onChangeText}
|
package/src/Input/Input.web.tsx
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { isValidElement, useState } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
-
import { InputProps } from './types';
|
|
4
|
-
import { inputStyles } from './Input.styles';
|
|
5
3
|
import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
|
|
6
|
-
import {
|
|
4
|
+
import { isIconName, resolveIconPath } from '../Icon/icon-resolver';
|
|
7
5
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
6
|
+
import { inputStyles } from './Input.styles';
|
|
7
|
+
import { InputProps } from './types';
|
|
8
8
|
|
|
9
9
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
|
10
10
|
value,
|
|
11
11
|
onChangeText,
|
|
12
12
|
onFocus,
|
|
13
13
|
onBlur,
|
|
14
|
+
onPress,
|
|
14
15
|
placeholder,
|
|
15
16
|
disabled = false,
|
|
16
17
|
inputType = 'text',
|
|
@@ -58,6 +59,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
|
|
58
59
|
}
|
|
59
60
|
};
|
|
60
61
|
|
|
62
|
+
const handlePress = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
63
|
+
// For web compatibility, we can trigger onFocus when pressed
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
e.stopPropagation();
|
|
66
|
+
if (onPress) {
|
|
67
|
+
onPress();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
62
72
|
e.preventDefault();
|
|
63
73
|
e.stopPropagation();
|
|
@@ -99,6 +109,12 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
|
|
99
109
|
// Get input props
|
|
100
110
|
const inputWebProps = getWebProps([inputStyles.input]);
|
|
101
111
|
|
|
112
|
+
const handleContainerPress = (e: React.MouseEvent<HTMLDivElement>) => {
|
|
113
|
+
e.preventDefault();
|
|
114
|
+
e.stopPropagation();
|
|
115
|
+
inputWebProps.ref?.current?.focus();
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
// Merge the forwarded ref with unistyles ref for the input
|
|
103
119
|
const mergedInputRef = useMergeRefs(ref, inputWebProps.ref);
|
|
104
120
|
|
|
@@ -156,7 +172,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
|
|
156
172
|
};
|
|
157
173
|
|
|
158
174
|
return (
|
|
159
|
-
<div {...containerProps} data-testid={testID}>
|
|
175
|
+
<div onClick={handleContainerPress} {...containerProps} data-testid={testID}>
|
|
160
176
|
{/* Left Icon */}
|
|
161
177
|
{leftIcon && (
|
|
162
178
|
<span {...leftIconContainerProps}>
|
|
@@ -170,6 +186,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(({
|
|
|
170
186
|
ref={mergedInputRef}
|
|
171
187
|
type={getInputType()}
|
|
172
188
|
value={value}
|
|
189
|
+
onClick={handlePress}
|
|
173
190
|
onChange={handleChange}
|
|
174
191
|
onFocus={handleFocus}
|
|
175
192
|
onBlur={handleBlur}
|
package/src/Input/types.ts
CHANGED
|
@@ -138,7 +138,6 @@ export const selectStyles = StyleSheet.create((theme: Theme) => {
|
|
|
138
138
|
return {
|
|
139
139
|
container: {
|
|
140
140
|
position: 'relative',
|
|
141
|
-
backgroundColor: theme.colors.surface.primary,
|
|
142
141
|
},
|
|
143
142
|
label: {
|
|
144
143
|
fontSize: 14,
|
|
@@ -257,35 +256,28 @@ export const selectStyles = StyleSheet.create((theme: Theme) => {
|
|
|
257
256
|
flexDirection: 'row',
|
|
258
257
|
alignItems: 'center',
|
|
259
258
|
minHeight: 36,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
false: {},
|
|
259
|
+
_web: {
|
|
260
|
+
display: 'flex',
|
|
261
|
+
cursor: 'pointer',
|
|
262
|
+
_hover: {
|
|
263
|
+
backgroundColor: theme.colors.surface.secondary,
|
|
266
264
|
},
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
opacity: 0.5,
|
|
270
|
-
_web: {
|
|
271
|
-
cursor: 'not-allowed',
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
false: {
|
|
275
|
-
_web: {
|
|
276
|
-
cursor: 'pointer',
|
|
277
|
-
_hover: {
|
|
278
|
-
backgroundColor: theme.colors.surface.secondary,
|
|
279
|
-
},
|
|
280
|
-
_active: {
|
|
281
|
-
opacity: 0.8,
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
},
|
|
265
|
+
_active: {
|
|
266
|
+
opacity: 0.8,
|
|
285
267
|
},
|
|
286
268
|
},
|
|
269
|
+
},
|
|
270
|
+
optionFocused: {
|
|
271
|
+
backgroundColor: theme.interaction.focusedBackground,
|
|
287
272
|
_web: {
|
|
288
|
-
|
|
273
|
+
outline: `1px solid ${theme.interaction.focusBorder}`,
|
|
274
|
+
outlineOffset: -1,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
optionDisabled: {
|
|
278
|
+
opacity: theme.interaction.opacity.disabled,
|
|
279
|
+
_web: {
|
|
280
|
+
cursor: 'not-allowed',
|
|
289
281
|
},
|
|
290
282
|
},
|
|
291
283
|
optionContent: {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
|
2
2
|
// @ts-ignore - web-specific import
|
|
3
3
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
4
|
-
import { SelectProps, SelectOption } from './types';
|
|
5
|
-
import { selectStyles } from './Select.styles';
|
|
6
4
|
import { IconSvg } from '../Icon/IconSvg/IconSvg.web';
|
|
7
5
|
import { resolveIconPath } from '../Icon/icon-resolver';
|
|
8
|
-
import { PositionedPortal } from '../internal/PositionedPortal';
|
|
9
6
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
7
|
+
import { PositionedPortal } from '../internal/PositionedPortal';
|
|
8
|
+
import { selectStyles } from './Select.styles';
|
|
9
|
+
import { SelectOption, SelectProps } from './types';
|
|
10
10
|
|
|
11
11
|
const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
12
12
|
options,
|
|
@@ -45,6 +45,13 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
45
45
|
})
|
|
46
46
|
: options;
|
|
47
47
|
|
|
48
|
+
// Get the index of the currently selected option
|
|
49
|
+
const getSelectedIndex = () => {
|
|
50
|
+
if (!value) return 0;
|
|
51
|
+
const index = filteredOptions.findIndex(option => option.value === value);
|
|
52
|
+
return index >= 0 ? index : 0;
|
|
53
|
+
};
|
|
54
|
+
|
|
48
55
|
// Apply styles with variants
|
|
49
56
|
selectStyles.useVariants({
|
|
50
57
|
type,
|
|
@@ -54,10 +61,17 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
54
61
|
focused: isOpen,
|
|
55
62
|
});
|
|
56
63
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!isOpen)
|
|
64
|
+
// Handle keyboard navigation on the trigger button
|
|
65
|
+
const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
66
|
+
// Handle opening with arrow keys when closed
|
|
67
|
+
if (!isOpen) {
|
|
68
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter' || event.key === ' ') {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
setIsOpen(true);
|
|
71
|
+
setFocusedIndex(getSelectedIndex());
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
61
75
|
|
|
62
76
|
switch (event.key) {
|
|
63
77
|
case 'ArrowDown':
|
|
@@ -72,7 +86,34 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
72
86
|
prev > 0 ? prev - 1 : filteredOptions.length - 1
|
|
73
87
|
);
|
|
74
88
|
break;
|
|
89
|
+
case 'Tab':
|
|
90
|
+
if (event.shiftKey) {
|
|
91
|
+
// Shift+Tab: go to previous option or exit
|
|
92
|
+
if (focusedIndex <= 0) {
|
|
93
|
+
setIsOpen(false);
|
|
94
|
+
} else {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
setFocusedIndex(prev => prev - 1);
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// Tab: go to next option or exit
|
|
100
|
+
if (focusedIndex >= filteredOptions.length - 1) {
|
|
101
|
+
setIsOpen(false);
|
|
102
|
+
} else {
|
|
103
|
+
event.preventDefault();
|
|
104
|
+
setFocusedIndex(prev => prev < 0 ? 0 : prev + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
75
108
|
case 'Enter':
|
|
109
|
+
event.preventDefault();
|
|
110
|
+
if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
|
|
111
|
+
const option = filteredOptions[focusedIndex];
|
|
112
|
+
if (!option.disabled) {
|
|
113
|
+
handleOptionSelect(option);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
break;
|
|
76
117
|
case ' ':
|
|
77
118
|
event.preventDefault();
|
|
78
119
|
if (focusedIndex >= 0 && focusedIndex < filteredOptions.length) {
|
|
@@ -82,44 +123,39 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
82
123
|
}
|
|
83
124
|
}
|
|
84
125
|
break;
|
|
126
|
+
case 'Escape':
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
setIsOpen(false);
|
|
129
|
+
break;
|
|
85
130
|
}
|
|
86
131
|
};
|
|
87
132
|
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
if (!isOpen) return;
|
|
90
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
91
|
-
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
92
|
-
}, [isOpen, focusedIndex, filteredOptions]);
|
|
93
|
-
|
|
94
133
|
// Focus search input when dropdown opens
|
|
95
134
|
useEffect(() => {
|
|
96
135
|
if (isOpen && searchable && searchInputRef.current) {
|
|
97
|
-
// Delay to ensure dropdown is positioned
|
|
98
136
|
setTimeout(() => {
|
|
99
137
|
searchInputRef.current?.focus();
|
|
100
138
|
}, 50);
|
|
101
139
|
}
|
|
102
140
|
}, [isOpen, searchable]);
|
|
103
141
|
|
|
104
|
-
const handleTriggerClick = () => {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
setSearchTerm('');
|
|
108
|
-
setFocusedIndex(-1);
|
|
109
|
-
}
|
|
142
|
+
const handleTriggerClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
e.stopPropagation();
|
|
110
145
|
};
|
|
111
146
|
|
|
112
147
|
const handleTriggerFocus = () => {
|
|
113
148
|
if (!disabled && !isOpen) {
|
|
114
149
|
setIsOpen(true);
|
|
115
150
|
setSearchTerm('');
|
|
116
|
-
|
|
151
|
+
// Focus on selected option, or first option if none selected
|
|
152
|
+
setFocusedIndex(getSelectedIndex());
|
|
117
153
|
}
|
|
118
154
|
};
|
|
119
155
|
|
|
120
156
|
const handleOptionSelect = (option: SelectOption) => {
|
|
121
157
|
if (!option.disabled) {
|
|
122
|
-
onValueChange(option.value);
|
|
158
|
+
onValueChange?.(option.value);
|
|
123
159
|
setIsOpen(false);
|
|
124
160
|
setSearchTerm('');
|
|
125
161
|
triggerRef.current?.focus();
|
|
@@ -160,6 +196,7 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
160
196
|
{...triggerWebProps}
|
|
161
197
|
onClick={handleTriggerClick}
|
|
162
198
|
onFocus={handleTriggerFocus}
|
|
199
|
+
onKeyDown={handleKeyDown}
|
|
163
200
|
disabled={disabled}
|
|
164
201
|
aria-label={accessibilityLabel || label}
|
|
165
202
|
aria-expanded={isOpen}
|
|
@@ -205,7 +242,6 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
205
242
|
{...getWebProps([selectStyles.dropdown])}
|
|
206
243
|
style={{
|
|
207
244
|
maxHeight: maxHeight,
|
|
208
|
-
// Override positioning since PositionedPortal handles it
|
|
209
245
|
position: 'relative',
|
|
210
246
|
top: 'auto',
|
|
211
247
|
left: 'auto',
|
|
@@ -228,16 +264,20 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
228
264
|
|
|
229
265
|
<div {...getWebProps([selectStyles.optionsList])}>
|
|
230
266
|
{filteredOptions.map((option, index) => {
|
|
231
|
-
const
|
|
267
|
+
const isFocused = index === focusedIndex;
|
|
232
268
|
|
|
233
269
|
return (
|
|
234
270
|
<div
|
|
235
271
|
key={option.value}
|
|
236
272
|
onClick={() => handleOptionSelect(option)}
|
|
237
273
|
role="option"
|
|
238
|
-
aria-selected={
|
|
274
|
+
aria-selected={option.value === value}
|
|
239
275
|
onMouseEnter={() => setFocusedIndex(index)}
|
|
240
|
-
{...getWebProps([
|
|
276
|
+
{...getWebProps([
|
|
277
|
+
selectStyles.option,
|
|
278
|
+
isFocused && selectStyles.optionFocused,
|
|
279
|
+
option.disabled && selectStyles.optionDisabled,
|
|
280
|
+
])}
|
|
241
281
|
>
|
|
242
282
|
<div {...getWebProps([selectStyles.optionContent])}>
|
|
243
283
|
{option.icon && (
|
|
@@ -282,4 +322,4 @@ const Select = forwardRef<HTMLDivElement, SelectProps>(({
|
|
|
282
322
|
|
|
283
323
|
Select.displayName = 'Select';
|
|
284
324
|
|
|
285
|
-
export default Select;
|
|
325
|
+
export default Select;
|
package/src/index.native.ts
CHANGED
|
@@ -134,4 +134,7 @@ export type { SkeletonProps, SkeletonGroupProps, SkeletonShape, SkeletonAnimatio
|
|
|
134
134
|
export type { ChipProps, ChipSize, ChipIntent } from './Chip/types';
|
|
135
135
|
export type { BreadcrumbProps, BreadcrumbItem } from './Breadcrumb/types';
|
|
136
136
|
|
|
137
|
+
// Event utilities
|
|
138
|
+
export * from './utils/events';
|
|
139
|
+
|
|
137
140
|
export type { AppTheme } from '@idealyst/theme';
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native-specific event wrappers
|
|
3
|
+
*
|
|
4
|
+
* Converts React Native events to standardized cross-platform events.
|
|
5
|
+
* Note: preventDefault() and stopPropagation() are no-ops on native since
|
|
6
|
+
* React Native's event system doesn't have these concepts in the same way.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
GestureResponderEvent,
|
|
11
|
+
NativeSyntheticEvent,
|
|
12
|
+
NativeScrollEvent,
|
|
13
|
+
TextInputFocusEventData,
|
|
14
|
+
TextInputChangeEventData,
|
|
15
|
+
} from 'react-native';
|
|
16
|
+
|
|
17
|
+
import type {
|
|
18
|
+
PressEvent,
|
|
19
|
+
FocusEvent,
|
|
20
|
+
ChangeEvent,
|
|
21
|
+
TextChangeEvent,
|
|
22
|
+
ToggleEvent,
|
|
23
|
+
KeyboardEvent,
|
|
24
|
+
ScrollEvent,
|
|
25
|
+
SubmitEvent,
|
|
26
|
+
} from './types';
|
|
27
|
+
|
|
28
|
+
// No-op functions for native (these concepts don't apply)
|
|
29
|
+
const noop = () => {};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Wraps a React Native GestureResponderEvent into a standardized PressEvent
|
|
33
|
+
*/
|
|
34
|
+
export function createPressEvent(
|
|
35
|
+
event: GestureResponderEvent,
|
|
36
|
+
type: PressEvent['type'] = 'press'
|
|
37
|
+
): PressEvent {
|
|
38
|
+
return {
|
|
39
|
+
nativeEvent: event.nativeEvent,
|
|
40
|
+
timestamp: event.nativeEvent.timestamp,
|
|
41
|
+
defaultPrevented: false,
|
|
42
|
+
propagationStopped: false,
|
|
43
|
+
type,
|
|
44
|
+
preventDefault: noop,
|
|
45
|
+
stopPropagation: noop,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Wraps a React Native focus event into a standardized FocusEvent
|
|
51
|
+
*/
|
|
52
|
+
export function createFocusEvent(
|
|
53
|
+
event: NativeSyntheticEvent<TextInputFocusEventData>,
|
|
54
|
+
type: FocusEvent['type']
|
|
55
|
+
): FocusEvent {
|
|
56
|
+
return {
|
|
57
|
+
nativeEvent: event.nativeEvent,
|
|
58
|
+
timestamp: Date.now(),
|
|
59
|
+
defaultPrevented: false,
|
|
60
|
+
propagationStopped: false,
|
|
61
|
+
type,
|
|
62
|
+
preventDefault: noop,
|
|
63
|
+
stopPropagation: noop,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a standardized FocusEvent without a native event (for simple focus tracking)
|
|
69
|
+
*/
|
|
70
|
+
export function createSimpleFocusEvent(type: FocusEvent['type']): FocusEvent {
|
|
71
|
+
return {
|
|
72
|
+
nativeEvent: null,
|
|
73
|
+
timestamp: Date.now(),
|
|
74
|
+
defaultPrevented: false,
|
|
75
|
+
propagationStopped: false,
|
|
76
|
+
type,
|
|
77
|
+
preventDefault: noop,
|
|
78
|
+
stopPropagation: noop,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Wraps a value change into a standardized ChangeEvent
|
|
84
|
+
*/
|
|
85
|
+
export function createChangeEvent<V = string>(value: V): ChangeEvent<V> {
|
|
86
|
+
return {
|
|
87
|
+
nativeEvent: null,
|
|
88
|
+
timestamp: Date.now(),
|
|
89
|
+
defaultPrevented: false,
|
|
90
|
+
propagationStopped: false,
|
|
91
|
+
type: 'change',
|
|
92
|
+
value,
|
|
93
|
+
preventDefault: noop,
|
|
94
|
+
stopPropagation: noop,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Wraps a React Native text change event into a standardized TextChangeEvent
|
|
100
|
+
*/
|
|
101
|
+
export function createTextChangeEvent(
|
|
102
|
+
event: NativeSyntheticEvent<TextInputChangeEventData>
|
|
103
|
+
): TextChangeEvent {
|
|
104
|
+
const text = event.nativeEvent.text;
|
|
105
|
+
return {
|
|
106
|
+
nativeEvent: event.nativeEvent,
|
|
107
|
+
timestamp: Date.now(),
|
|
108
|
+
defaultPrevented: false,
|
|
109
|
+
propagationStopped: false,
|
|
110
|
+
type: 'change',
|
|
111
|
+
value: text,
|
|
112
|
+
text,
|
|
113
|
+
preventDefault: noop,
|
|
114
|
+
stopPropagation: noop,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Creates a TextChangeEvent from a simple text value (for onChangeText)
|
|
120
|
+
*/
|
|
121
|
+
export function createSimpleTextChangeEvent(text: string): TextChangeEvent {
|
|
122
|
+
return {
|
|
123
|
+
nativeEvent: null,
|
|
124
|
+
timestamp: Date.now(),
|
|
125
|
+
defaultPrevented: false,
|
|
126
|
+
propagationStopped: false,
|
|
127
|
+
type: 'change',
|
|
128
|
+
value: text,
|
|
129
|
+
text,
|
|
130
|
+
preventDefault: noop,
|
|
131
|
+
stopPropagation: noop,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Creates a standardized ToggleEvent for switch/checkbox changes
|
|
137
|
+
*/
|
|
138
|
+
export function createToggleEvent(checked: boolean): ToggleEvent {
|
|
139
|
+
return {
|
|
140
|
+
nativeEvent: null,
|
|
141
|
+
timestamp: Date.now(),
|
|
142
|
+
defaultPrevented: false,
|
|
143
|
+
propagationStopped: false,
|
|
144
|
+
type: 'change',
|
|
145
|
+
value: checked,
|
|
146
|
+
checked,
|
|
147
|
+
preventDefault: noop,
|
|
148
|
+
stopPropagation: noop,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a standardized KeyboardEvent
|
|
154
|
+
* Note: React Native's keyboard events are more limited than web
|
|
155
|
+
*/
|
|
156
|
+
export function createKeyboardEvent(
|
|
157
|
+
key: string,
|
|
158
|
+
type: KeyboardEvent['type']
|
|
159
|
+
): KeyboardEvent {
|
|
160
|
+
return {
|
|
161
|
+
nativeEvent: null,
|
|
162
|
+
timestamp: Date.now(),
|
|
163
|
+
defaultPrevented: false,
|
|
164
|
+
propagationStopped: false,
|
|
165
|
+
type,
|
|
166
|
+
key,
|
|
167
|
+
keyCode: 0,
|
|
168
|
+
altKey: false,
|
|
169
|
+
ctrlKey: false,
|
|
170
|
+
metaKey: false,
|
|
171
|
+
shiftKey: false,
|
|
172
|
+
preventDefault: noop,
|
|
173
|
+
stopPropagation: noop,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Wraps a React Native scroll event into a standardized ScrollEvent
|
|
179
|
+
*/
|
|
180
|
+
export function createScrollEvent(
|
|
181
|
+
event: NativeSyntheticEvent<NativeScrollEvent>,
|
|
182
|
+
type: ScrollEvent['type'] = 'scroll'
|
|
183
|
+
): ScrollEvent {
|
|
184
|
+
const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
|
|
185
|
+
return {
|
|
186
|
+
nativeEvent: event.nativeEvent,
|
|
187
|
+
timestamp: Date.now(),
|
|
188
|
+
defaultPrevented: false,
|
|
189
|
+
propagationStopped: false,
|
|
190
|
+
type,
|
|
191
|
+
contentOffset,
|
|
192
|
+
contentSize,
|
|
193
|
+
layoutMeasurement,
|
|
194
|
+
preventDefault: noop,
|
|
195
|
+
stopPropagation: noop,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Creates a standardized SubmitEvent
|
|
201
|
+
*/
|
|
202
|
+
export function createSubmitEvent(): SubmitEvent {
|
|
203
|
+
return {
|
|
204
|
+
nativeEvent: null,
|
|
205
|
+
timestamp: Date.now(),
|
|
206
|
+
defaultPrevented: false,
|
|
207
|
+
propagationStopped: false,
|
|
208
|
+
type: 'submit',
|
|
209
|
+
preventDefault: noop,
|
|
210
|
+
stopPropagation: noop,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web-specific event wrappers
|
|
3
|
+
*
|
|
4
|
+
* Converts React web events to standardized cross-platform events
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
PressEvent,
|
|
9
|
+
FocusEvent,
|
|
10
|
+
ChangeEvent,
|
|
11
|
+
TextChangeEvent,
|
|
12
|
+
ToggleEvent,
|
|
13
|
+
KeyboardEvent,
|
|
14
|
+
ScrollEvent,
|
|
15
|
+
SubmitEvent,
|
|
16
|
+
} from './types';
|
|
17
|
+
|
|
18
|
+
type ReactMouseEvent = React.MouseEvent<HTMLElement>;
|
|
19
|
+
type ReactFocusEvent = React.FocusEvent<HTMLElement>;
|
|
20
|
+
type ReactChangeEvent = React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>;
|
|
21
|
+
type ReactKeyboardEvent = React.KeyboardEvent<HTMLElement>;
|
|
22
|
+
type ReactFormEvent = React.FormEvent<HTMLFormElement>;
|
|
23
|
+
type ReactUIEvent = React.UIEvent<HTMLElement>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wraps a React mouse/click event into a standardized PressEvent
|
|
27
|
+
*/
|
|
28
|
+
export function createPressEvent(
|
|
29
|
+
event: ReactMouseEvent,
|
|
30
|
+
type: PressEvent['type'] = 'press'
|
|
31
|
+
): PressEvent {
|
|
32
|
+
return {
|
|
33
|
+
nativeEvent: event.nativeEvent,
|
|
34
|
+
timestamp: event.timeStamp,
|
|
35
|
+
defaultPrevented: event.defaultPrevented,
|
|
36
|
+
propagationStopped: false,
|
|
37
|
+
type,
|
|
38
|
+
preventDefault: () => event.preventDefault(),
|
|
39
|
+
stopPropagation: () => event.stopPropagation(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Wraps a React focus event into a standardized FocusEvent
|
|
45
|
+
*/
|
|
46
|
+
export function createFocusEvent(
|
|
47
|
+
event: ReactFocusEvent,
|
|
48
|
+
type: FocusEvent['type']
|
|
49
|
+
): FocusEvent {
|
|
50
|
+
return {
|
|
51
|
+
nativeEvent: event.nativeEvent,
|
|
52
|
+
timestamp: event.timeStamp,
|
|
53
|
+
defaultPrevented: event.defaultPrevented,
|
|
54
|
+
propagationStopped: false,
|
|
55
|
+
type,
|
|
56
|
+
preventDefault: () => event.preventDefault(),
|
|
57
|
+
stopPropagation: () => event.stopPropagation(),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Wraps a React change event into a standardized ChangeEvent
|
|
63
|
+
*/
|
|
64
|
+
export function createChangeEvent<V = string>(
|
|
65
|
+
event: ReactChangeEvent,
|
|
66
|
+
value: V
|
|
67
|
+
): ChangeEvent<V> {
|
|
68
|
+
return {
|
|
69
|
+
nativeEvent: event.nativeEvent,
|
|
70
|
+
timestamp: event.timeStamp,
|
|
71
|
+
defaultPrevented: event.defaultPrevented,
|
|
72
|
+
propagationStopped: false,
|
|
73
|
+
type: 'change',
|
|
74
|
+
value,
|
|
75
|
+
preventDefault: () => event.preventDefault(),
|
|
76
|
+
stopPropagation: () => event.stopPropagation(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Wraps a React change event for text inputs into a standardized TextChangeEvent
|
|
82
|
+
*/
|
|
83
|
+
export function createTextChangeEvent(event: ReactChangeEvent): TextChangeEvent {
|
|
84
|
+
const value = event.target.value;
|
|
85
|
+
return {
|
|
86
|
+
nativeEvent: event.nativeEvent,
|
|
87
|
+
timestamp: event.timeStamp,
|
|
88
|
+
defaultPrevented: event.defaultPrevented,
|
|
89
|
+
propagationStopped: false,
|
|
90
|
+
type: 'change',
|
|
91
|
+
value,
|
|
92
|
+
text: value,
|
|
93
|
+
preventDefault: () => event.preventDefault(),
|
|
94
|
+
stopPropagation: () => event.stopPropagation(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Wraps a React change event for checkboxes/switches into a standardized ToggleEvent
|
|
100
|
+
*/
|
|
101
|
+
export function createToggleEvent(event: ReactChangeEvent): ToggleEvent {
|
|
102
|
+
const checked = (event.target as HTMLInputElement).checked;
|
|
103
|
+
return {
|
|
104
|
+
nativeEvent: event.nativeEvent,
|
|
105
|
+
timestamp: event.timeStamp,
|
|
106
|
+
defaultPrevented: event.defaultPrevented,
|
|
107
|
+
propagationStopped: false,
|
|
108
|
+
type: 'change',
|
|
109
|
+
value: checked,
|
|
110
|
+
checked,
|
|
111
|
+
preventDefault: () => event.preventDefault(),
|
|
112
|
+
stopPropagation: () => event.stopPropagation(),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Wraps a React keyboard event into a standardized KeyboardEvent
|
|
118
|
+
*/
|
|
119
|
+
export function createKeyboardEvent(
|
|
120
|
+
event: ReactKeyboardEvent,
|
|
121
|
+
type: KeyboardEvent['type']
|
|
122
|
+
): KeyboardEvent {
|
|
123
|
+
return {
|
|
124
|
+
nativeEvent: event.nativeEvent,
|
|
125
|
+
timestamp: event.timeStamp,
|
|
126
|
+
defaultPrevented: event.defaultPrevented,
|
|
127
|
+
propagationStopped: false,
|
|
128
|
+
type,
|
|
129
|
+
key: event.key,
|
|
130
|
+
keyCode: event.keyCode,
|
|
131
|
+
altKey: event.altKey,
|
|
132
|
+
ctrlKey: event.ctrlKey,
|
|
133
|
+
metaKey: event.metaKey,
|
|
134
|
+
shiftKey: event.shiftKey,
|
|
135
|
+
preventDefault: () => event.preventDefault(),
|
|
136
|
+
stopPropagation: () => event.stopPropagation(),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Wraps a React scroll event into a standardized ScrollEvent
|
|
142
|
+
*/
|
|
143
|
+
export function createScrollEvent(
|
|
144
|
+
event: ReactUIEvent,
|
|
145
|
+
type: ScrollEvent['type'] = 'scroll'
|
|
146
|
+
): ScrollEvent {
|
|
147
|
+
const target = event.target as HTMLElement;
|
|
148
|
+
return {
|
|
149
|
+
nativeEvent: event.nativeEvent,
|
|
150
|
+
timestamp: event.timeStamp,
|
|
151
|
+
defaultPrevented: event.defaultPrevented,
|
|
152
|
+
propagationStopped: false,
|
|
153
|
+
type,
|
|
154
|
+
contentOffset: {
|
|
155
|
+
x: target.scrollLeft,
|
|
156
|
+
y: target.scrollTop,
|
|
157
|
+
},
|
|
158
|
+
contentSize: {
|
|
159
|
+
width: target.scrollWidth,
|
|
160
|
+
height: target.scrollHeight,
|
|
161
|
+
},
|
|
162
|
+
layoutMeasurement: {
|
|
163
|
+
width: target.clientWidth,
|
|
164
|
+
height: target.clientHeight,
|
|
165
|
+
},
|
|
166
|
+
preventDefault: () => event.preventDefault(),
|
|
167
|
+
stopPropagation: () => event.stopPropagation(),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Wraps a React form submit event into a standardized SubmitEvent
|
|
173
|
+
*/
|
|
174
|
+
export function createSubmitEvent(event: ReactFormEvent): SubmitEvent {
|
|
175
|
+
return {
|
|
176
|
+
nativeEvent: event.nativeEvent,
|
|
177
|
+
timestamp: event.timeStamp,
|
|
178
|
+
defaultPrevented: event.defaultPrevented,
|
|
179
|
+
propagationStopped: false,
|
|
180
|
+
type: 'submit',
|
|
181
|
+
preventDefault: () => event.preventDefault(),
|
|
182
|
+
stopPropagation: () => event.stopPropagation(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform event utilities
|
|
3
|
+
*
|
|
4
|
+
* This module provides standardized event types and creation functions
|
|
5
|
+
* that work consistently across web and native platforms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-export all types
|
|
9
|
+
export type {
|
|
10
|
+
SyntheticEvent,
|
|
11
|
+
PressEvent,
|
|
12
|
+
FocusEvent,
|
|
13
|
+
ChangeEvent,
|
|
14
|
+
TextChangeEvent,
|
|
15
|
+
SelectChangeEvent,
|
|
16
|
+
ToggleEvent,
|
|
17
|
+
KeyboardEvent,
|
|
18
|
+
ScrollEvent,
|
|
19
|
+
SubmitEvent,
|
|
20
|
+
PressEventHandler,
|
|
21
|
+
FocusEventHandler,
|
|
22
|
+
ChangeEventHandler,
|
|
23
|
+
TextChangeEventHandler,
|
|
24
|
+
SelectChangeEventHandler,
|
|
25
|
+
ToggleEventHandler,
|
|
26
|
+
KeyboardEventHandler,
|
|
27
|
+
ScrollEventHandler,
|
|
28
|
+
SubmitEventHandler,
|
|
29
|
+
} from './types';
|
|
30
|
+
|
|
31
|
+
// Re-export base utility
|
|
32
|
+
export { createBaseSyntheticEvent } from './types';
|
|
33
|
+
|
|
34
|
+
// Re-export platform-specific functions (native by default)
|
|
35
|
+
export {
|
|
36
|
+
createPressEvent,
|
|
37
|
+
createFocusEvent,
|
|
38
|
+
createSimpleFocusEvent,
|
|
39
|
+
createChangeEvent,
|
|
40
|
+
createTextChangeEvent,
|
|
41
|
+
createSimpleTextChangeEvent,
|
|
42
|
+
createToggleEvent,
|
|
43
|
+
createKeyboardEvent,
|
|
44
|
+
createScrollEvent,
|
|
45
|
+
createSubmitEvent,
|
|
46
|
+
} from './events.native';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform event utilities (Web)
|
|
3
|
+
*
|
|
4
|
+
* This module provides standardized event types and creation functions
|
|
5
|
+
* that work consistently across web and native platforms.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Re-export all types
|
|
9
|
+
export type {
|
|
10
|
+
SyntheticEvent,
|
|
11
|
+
PressEvent,
|
|
12
|
+
FocusEvent,
|
|
13
|
+
ChangeEvent,
|
|
14
|
+
TextChangeEvent,
|
|
15
|
+
SelectChangeEvent,
|
|
16
|
+
ToggleEvent,
|
|
17
|
+
KeyboardEvent,
|
|
18
|
+
ScrollEvent,
|
|
19
|
+
SubmitEvent,
|
|
20
|
+
PressEventHandler,
|
|
21
|
+
FocusEventHandler,
|
|
22
|
+
ChangeEventHandler,
|
|
23
|
+
TextChangeEventHandler,
|
|
24
|
+
SelectChangeEventHandler,
|
|
25
|
+
ToggleEventHandler,
|
|
26
|
+
KeyboardEventHandler,
|
|
27
|
+
ScrollEventHandler,
|
|
28
|
+
SubmitEventHandler,
|
|
29
|
+
} from './types';
|
|
30
|
+
|
|
31
|
+
// Re-export base utility
|
|
32
|
+
export { createBaseSyntheticEvent } from './types';
|
|
33
|
+
|
|
34
|
+
// Re-export platform-specific functions (web)
|
|
35
|
+
export {
|
|
36
|
+
createPressEvent,
|
|
37
|
+
createFocusEvent,
|
|
38
|
+
createChangeEvent,
|
|
39
|
+
createTextChangeEvent,
|
|
40
|
+
createToggleEvent,
|
|
41
|
+
createKeyboardEvent,
|
|
42
|
+
createScrollEvent,
|
|
43
|
+
createSubmitEvent,
|
|
44
|
+
} from './events.web';
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standardized cross-platform event system
|
|
3
|
+
*
|
|
4
|
+
* Provides unified event interfaces that work consistently across web and native.
|
|
5
|
+
* On native, methods like preventDefault() and stopPropagation() are no-ops since
|
|
6
|
+
* React Native's event system doesn't have these concepts in the same way.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Base synthetic event interface that normalizes web and native events
|
|
11
|
+
*/
|
|
12
|
+
export interface SyntheticEvent<T = unknown> {
|
|
13
|
+
/**
|
|
14
|
+
* The native event (platform-specific)
|
|
15
|
+
* - Web: React.SyntheticEvent
|
|
16
|
+
* - Native: GestureResponderEvent or NativeSyntheticEvent
|
|
17
|
+
*/
|
|
18
|
+
nativeEvent: T;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Prevents the default behavior of the event.
|
|
22
|
+
* On native, this is a no-op as there's no default behavior concept.
|
|
23
|
+
*/
|
|
24
|
+
preventDefault: () => void;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Stops the event from propagating to parent elements.
|
|
28
|
+
* On native, this is a no-op.
|
|
29
|
+
*/
|
|
30
|
+
stopPropagation: () => void;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Whether preventDefault() has been called
|
|
34
|
+
*/
|
|
35
|
+
defaultPrevented: boolean;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Whether stopPropagation() has been called
|
|
39
|
+
*/
|
|
40
|
+
propagationStopped: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Timestamp of when the event occurred
|
|
44
|
+
*/
|
|
45
|
+
timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Press/Click event - for buttons, pressables, touchable elements
|
|
50
|
+
*/
|
|
51
|
+
export interface PressEvent extends SyntheticEvent {
|
|
52
|
+
/**
|
|
53
|
+
* The type of press event
|
|
54
|
+
*/
|
|
55
|
+
type: 'press' | 'pressIn' | 'pressOut' | 'longPress';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Focus event - for inputs and focusable elements
|
|
60
|
+
*/
|
|
61
|
+
export interface FocusEvent extends SyntheticEvent {
|
|
62
|
+
/**
|
|
63
|
+
* The type of focus event
|
|
64
|
+
*/
|
|
65
|
+
type: 'focus' | 'blur';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Change event - for inputs, selects, checkboxes, etc.
|
|
70
|
+
*/
|
|
71
|
+
export interface ChangeEvent<V = string> extends SyntheticEvent {
|
|
72
|
+
/**
|
|
73
|
+
* The new value after the change
|
|
74
|
+
*/
|
|
75
|
+
value: V;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The type of change event
|
|
79
|
+
*/
|
|
80
|
+
type: 'change';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Text change event - specifically for text inputs
|
|
85
|
+
*/
|
|
86
|
+
export interface TextChangeEvent extends ChangeEvent<string> {
|
|
87
|
+
/**
|
|
88
|
+
* The text value
|
|
89
|
+
*/
|
|
90
|
+
text: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Selection change event - for selects, dropdowns
|
|
95
|
+
*/
|
|
96
|
+
export interface SelectChangeEvent<V = string> extends ChangeEvent<V> {
|
|
97
|
+
/**
|
|
98
|
+
* The selected value(s)
|
|
99
|
+
*/
|
|
100
|
+
selected: V;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Toggle event - for switches, checkboxes
|
|
105
|
+
*/
|
|
106
|
+
export interface ToggleEvent extends ChangeEvent<boolean> {
|
|
107
|
+
/**
|
|
108
|
+
* Whether the toggle is now checked/on
|
|
109
|
+
*/
|
|
110
|
+
checked: boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Keyboard event - for keyboard interactions
|
|
115
|
+
*/
|
|
116
|
+
export interface KeyboardEvent extends SyntheticEvent {
|
|
117
|
+
/**
|
|
118
|
+
* The key that was pressed
|
|
119
|
+
*/
|
|
120
|
+
key: string;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* The key code
|
|
124
|
+
*/
|
|
125
|
+
keyCode: number;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether the alt/option key was pressed
|
|
129
|
+
*/
|
|
130
|
+
altKey: boolean;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether the control key was pressed
|
|
134
|
+
*/
|
|
135
|
+
ctrlKey: boolean;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Whether the meta/command key was pressed
|
|
139
|
+
*/
|
|
140
|
+
metaKey: boolean;
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whether the shift key was pressed
|
|
144
|
+
*/
|
|
145
|
+
shiftKey: boolean;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The type of keyboard event
|
|
149
|
+
*/
|
|
150
|
+
type: 'keyDown' | 'keyUp' | 'keyPress';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Scroll event - for scrollable containers
|
|
155
|
+
*/
|
|
156
|
+
export interface ScrollEvent extends SyntheticEvent {
|
|
157
|
+
/**
|
|
158
|
+
* Current scroll position
|
|
159
|
+
*/
|
|
160
|
+
contentOffset: {
|
|
161
|
+
x: number;
|
|
162
|
+
y: number;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Size of the scrollable content
|
|
167
|
+
*/
|
|
168
|
+
contentSize: {
|
|
169
|
+
width: number;
|
|
170
|
+
height: number;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Size of the visible container
|
|
175
|
+
*/
|
|
176
|
+
layoutMeasurement: {
|
|
177
|
+
width: number;
|
|
178
|
+
height: number;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The type of scroll event
|
|
183
|
+
*/
|
|
184
|
+
type: 'scroll' | 'scrollBegin' | 'scrollEnd';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Submit event - for forms
|
|
189
|
+
*/
|
|
190
|
+
export interface SubmitEvent extends SyntheticEvent {
|
|
191
|
+
/**
|
|
192
|
+
* The type of submit event
|
|
193
|
+
*/
|
|
194
|
+
type: 'submit';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Event handler type aliases for convenience
|
|
198
|
+
export type PressEventHandler = (event: PressEvent) => void;
|
|
199
|
+
export type FocusEventHandler = (event: FocusEvent) => void;
|
|
200
|
+
export type ChangeEventHandler<V = string> = (event: ChangeEvent<V>) => void;
|
|
201
|
+
export type TextChangeEventHandler = (event: TextChangeEvent) => void;
|
|
202
|
+
export type SelectChangeEventHandler<V = string> = (event: SelectChangeEvent<V>) => void;
|
|
203
|
+
export type ToggleEventHandler = (event: ToggleEvent) => void;
|
|
204
|
+
export type KeyboardEventHandler = (event: KeyboardEvent) => void;
|
|
205
|
+
export type ScrollEventHandler = (event: ScrollEvent) => void;
|
|
206
|
+
export type SubmitEventHandler = (event: SubmitEvent) => void;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Creates a base synthetic event object with default implementations
|
|
210
|
+
*/
|
|
211
|
+
export function createBaseSyntheticEvent<T>(nativeEvent: T): SyntheticEvent<T> {
|
|
212
|
+
let defaultPrevented = false;
|
|
213
|
+
let propagationStopped = false;
|
|
214
|
+
|
|
215
|
+
return {
|
|
216
|
+
nativeEvent,
|
|
217
|
+
timestamp: Date.now(),
|
|
218
|
+
get defaultPrevented() {
|
|
219
|
+
return defaultPrevented;
|
|
220
|
+
},
|
|
221
|
+
get propagationStopped() {
|
|
222
|
+
return propagationStopped;
|
|
223
|
+
},
|
|
224
|
+
preventDefault() {
|
|
225
|
+
defaultPrevented = true;
|
|
226
|
+
},
|
|
227
|
+
stopPropagation() {
|
|
228
|
+
propagationStopped = true;
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|