@react-navigation/core 7.17.2 → 7.17.4

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 (40) hide show
  1. package/lib/module/StaticNavigation.js +0 -19
  2. package/lib/module/StaticNavigation.js.map +1 -1
  3. package/lib/module/checkSerializable.js +11 -4
  4. package/lib/module/checkSerializable.js.map +1 -1
  5. package/lib/module/getActionFromState.js +15 -2
  6. package/lib/module/getActionFromState.js.map +1 -1
  7. package/lib/module/getStateFromPath.js +98 -74
  8. package/lib/module/getStateFromPath.js.map +1 -1
  9. package/lib/module/index.js.map +1 -1
  10. package/lib/module/types.js +55 -0
  11. package/lib/module/types.js.map +1 -1
  12. package/lib/module/useEventEmitter.js +28 -29
  13. package/lib/module/useEventEmitter.js.map +1 -1
  14. package/lib/module/useNavigationBuilder.js +48 -47
  15. package/lib/module/useNavigationBuilder.js.map +1 -1
  16. package/lib/module/useRouteCache.js +2 -1
  17. package/lib/module/useRouteCache.js.map +1 -1
  18. package/lib/typescript/src/StaticNavigation.d.ts +2 -63
  19. package/lib/typescript/src/StaticNavigation.d.ts.map +1 -1
  20. package/lib/typescript/src/checkSerializable.d.ts +5 -3
  21. package/lib/typescript/src/checkSerializable.d.ts.map +1 -1
  22. package/lib/typescript/src/getActionFromState.d.ts.map +1 -1
  23. package/lib/typescript/src/getStateFromPath.d.ts.map +1 -1
  24. package/lib/typescript/src/index.d.ts +1 -1
  25. package/lib/typescript/src/index.d.ts.map +1 -1
  26. package/lib/typescript/src/types.d.ts +102 -2
  27. package/lib/typescript/src/types.d.ts.map +1 -1
  28. package/lib/typescript/src/useEventEmitter.d.ts.map +1 -1
  29. package/lib/typescript/src/useNavigationBuilder.d.ts.map +1 -1
  30. package/lib/typescript/src/useRouteCache.d.ts.map +1 -1
  31. package/package.json +3 -3
  32. package/src/StaticNavigation.tsx +1 -91
  33. package/src/checkSerializable.tsx +22 -12
  34. package/src/getActionFromState.tsx +20 -4
  35. package/src/getStateFromPath.tsx +147 -100
  36. package/src/index.tsx +0 -1
  37. package/src/types.tsx +138 -4
  38. package/src/useEventEmitter.tsx +33 -38
  39. package/src/useNavigationBuilder.tsx +77 -76
  40. package/src/useRouteCache.tsx +2 -1
@@ -29,6 +29,8 @@ type RouteConfig = {
29
29
  params: { screen: string; name?: string; index: number }[];
30
30
  routeNames: string[];
31
31
  parse?: ParseConfig;
32
+ explicitParamNames?: Set<string>;
33
+ hasNestedScreens: boolean;
32
34
  };
33
35
 
34
36
  type InitialRouteConfig = {
@@ -49,6 +51,22 @@ type ParsedRoute = {
49
51
  type ConfigResources = {
50
52
  initialRoutes: InitialRouteConfig[];
51
53
  configs: RouteConfig[];
54
+ configsByScreen: Record<string, RouteConfig[]>;
55
+ };
56
+
57
+ const NESTED_SCREEN_PARAM_NAMES = [
58
+ 'screen',
59
+ 'params',
60
+ 'initial',
61
+ 'path',
62
+ 'merge',
63
+ 'pop',
64
+ ];
65
+
66
+ const getExplicitParamNames = (parse?: ParseConfig) => {
67
+ const names = Object.entries(parse ?? {}).map(([name]) => name);
68
+
69
+ return names.length ? new Set(names) : undefined;
52
70
  };
53
71
 
54
72
  /**
@@ -76,7 +94,8 @@ export function getStateFromPath<ParamList extends {}>(
76
94
  path: string,
77
95
  options?: Options<ParamList>
78
96
  ): ResultState | undefined {
79
- const { initialRoutes, configs } = getConfigResources(options);
97
+ const { initialRoutes, configs, configsByScreen } =
98
+ getConfigResources(options);
80
99
 
81
100
  const screens = options?.screens;
82
101
 
@@ -103,15 +122,23 @@ export function getStateFromPath<ParamList extends {}>(
103
122
  remaining = remaining.replace(normalizedPrefix, '');
104
123
  }
105
124
 
125
+ const decodedSegments: string[] = [];
126
+
127
+ for (const segment of remaining.split('/')) {
128
+ if (!segment) {
129
+ continue;
130
+ }
131
+
132
+ try {
133
+ decodedSegments.push(decodeURIComponent(segment));
134
+ } catch {
135
+ return undefined;
136
+ }
137
+ }
138
+
106
139
  if (screens === undefined) {
107
140
  // When no config is specified, use the path segments as route names
108
- const routes = remaining
109
- .split('/')
110
- .filter(Boolean)
111
- .map((segment) => {
112
- const name = decodeURIComponent(segment);
113
- return { name };
114
- });
141
+ const routes = decodedSegments.map((name) => ({ name }));
115
142
 
116
143
  if (routes.length) {
117
144
  return createNestedStateObject(path, routes, initialRoutes);
@@ -130,32 +157,26 @@ export function getStateFromPath<ParamList extends {}>(
130
157
  path,
131
158
  match.routeNames.map((name) => ({ name })),
132
159
  initialRoutes,
133
- configs
160
+ match
134
161
  );
135
162
  }
136
163
 
137
164
  return undefined;
138
165
  }
139
166
 
140
- let result: PartialState<NavigationState> | undefined;
141
- let current: PartialState<NavigationState> | undefined;
142
-
143
167
  // We match the whole path against the regex instead of segments
144
168
  // This makes sure matches such as wildcard will catch any unmatched routes, even if nested
145
- const { routes, remainingPath } = matchAgainstConfigs(remaining, configs);
146
-
147
- if (routes !== undefined) {
148
- // This will always be empty if full path matched
149
- current = createNestedStateObject(path, routes, initialRoutes, configs);
150
- remaining = remainingPath;
151
- result = current;
152
- }
169
+ const { routes, config } = matchAgainstConfigs(
170
+ remaining,
171
+ configs,
172
+ configsByScreen
173
+ );
153
174
 
154
- if (current == null || result == null) {
175
+ if (routes === undefined || config === undefined) {
155
176
  return undefined;
156
177
  }
157
178
 
158
- return result;
179
+ return createNestedStateObject(path, routes, initialRoutes, config);
159
180
  }
160
181
 
161
182
  /**
@@ -189,12 +210,16 @@ function prepareConfigResources(options?: Options<{}>) {
189
210
 
190
211
  checkForDuplicatedConfigs(configs);
191
212
 
192
- const configWithRegexes = getConfigsWithRegexes(configs);
213
+ const configsByScreen: Record<string, RouteConfig[]> = {};
214
+
215
+ for (const c of configs) {
216
+ (configsByScreen[c.screen] ??= []).push(c);
217
+ }
193
218
 
194
219
  return {
195
220
  initialRoutes,
196
221
  configs,
197
- configWithRegexes,
222
+ configsByScreen,
198
223
  };
199
224
  }
200
225
 
@@ -337,17 +362,14 @@ function checkForDuplicatedConfigs(configs: RouteConfig[]) {
337
362
  }, {});
338
363
  }
339
364
 
340
- function getConfigsWithRegexes(configs: RouteConfig[]) {
341
- return configs.map((c) => ({
342
- ...c,
343
- // Add `$` to the regex to make sure it matches till end of the path and not just beginning
344
- regex: c.regex ? new RegExp(c.regex.source + '$') : undefined,
345
- }));
346
- }
347
-
348
- const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
365
+ const matchAgainstConfigs = (
366
+ remaining: string,
367
+ configs: RouteConfig[],
368
+ configsByScreen: Record<string, RouteConfig[]>
369
+ ) => {
349
370
  let routes: ParsedRoute[] | undefined;
350
371
  let remainingPath = remaining;
372
+ let matchingConfig: RouteConfig | undefined;
351
373
 
352
374
  // Go through all configs, and see if the next path segment matches our regex
353
375
  for (const config of configs) {
@@ -359,53 +381,57 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
359
381
 
360
382
  // If our regex matches, we need to extract params from the path
361
383
  if (match) {
362
- routes = config.routeNames.map((routeName) => {
363
- const routeConfig = configs.find((c) => {
364
- // Check matching name AND pattern in case same screen is used at different levels in config
365
- return (
366
- c.screen === routeName &&
367
- arrayStartsWith(config.segments, c.segments)
368
- );
369
- });
384
+ routes = [];
385
+ matchingConfig = config;
370
386
 
371
- const params =
372
- routeConfig && match.groups
373
- ? Object.fromEntries(
374
- Object.entries(match.groups)
375
- .map(([key, value]) => {
376
- const index = Number(key.replace('param_', ''));
377
- const param = routeConfig.params.find(
378
- (it) => it.index === index
379
- );
380
-
381
- if (param?.screen === routeName && param?.name) {
382
- return [param.name, value];
383
- }
384
-
385
- return null;
386
- })
387
- .filter((it) => it != null)
388
- .map(([key, value]) => {
389
- if (value == null) {
390
- return [key, undefined];
391
- }
392
-
393
- const decoded = decodeURIComponent(value);
394
- const parsed = routeConfig.parse?.[key]
395
- ? routeConfig.parse[key](decoded)
396
- : decoded;
397
-
398
- return [key, parsed];
399
- })
400
- )
401
- : undefined;
387
+ for (const routeName of config.routeNames) {
388
+ // Check matching name AND pattern in case same screen is used at different levels in config
389
+ const routeConfig = configsByScreen[routeName]?.find((c) =>
390
+ arrayStartsWith(config.segments, c.segments)
391
+ );
402
392
 
403
- if (params && Object.keys(params).length) {
404
- return { name: routeName, params };
393
+ let params: Record<string, unknown> | undefined;
394
+
395
+ if (routeConfig && match.groups) {
396
+ const paramEntries: [string, unknown][] = [];
397
+
398
+ for (const [key, value] of Object.entries(match.groups)) {
399
+ const index = Number(key.replace('param_', ''));
400
+ const param = routeConfig.params.find((it) => it.index === index);
401
+
402
+ if (param?.screen !== routeName || !param.name) {
403
+ continue;
404
+ }
405
+
406
+ if (value == null) {
407
+ paramEntries.push([param.name, undefined]);
408
+ continue;
409
+ }
410
+
411
+ let decoded: string;
412
+
413
+ try {
414
+ decoded = decodeURIComponent(value);
415
+ } catch {
416
+ return { routes: undefined, remainingPath };
417
+ }
418
+
419
+ const parser = routeConfig.parse?.[param.name];
420
+
421
+ paramEntries.push([param.name, parser ? parser(decoded) : decoded]);
422
+ }
423
+
424
+ if (paramEntries.length) {
425
+ params = Object.fromEntries(paramEntries);
426
+ }
405
427
  }
406
428
 
407
- return { name: routeName };
408
- });
429
+ if (params && Object.keys(params).length) {
430
+ routes.push({ name: routeName, params });
431
+ } else {
432
+ routes.push({ name: routeName });
433
+ }
434
+ }
409
435
 
410
436
  remainingPath = remainingPath.replace(match[0], '');
411
437
 
@@ -413,7 +439,7 @@ const matchAgainstConfigs = (remaining: string, configs: RouteConfig[]) => {
413
439
  }
414
440
  }
415
441
 
416
- return { routes, remainingPath };
442
+ return { routes, remainingPath, config: matchingConfig };
417
443
  };
418
444
 
419
445
  const createNormalizedConfigs = (
@@ -439,6 +465,9 @@ const createNormalizedConfigs = (
439
465
  // if an object is specified as the value (e.g. Foo: { ... }),
440
466
  // it can have `path` property and
441
467
  // it could have `screens` prop which has nested configs
468
+ const nestedScreens = config.screens;
469
+ const hasNestedScreens = !!nestedScreens;
470
+
442
471
  if (typeof config.path === 'string') {
443
472
  if (config.exact && config.path == null) {
444
473
  throw new Error(
@@ -458,7 +487,8 @@ const createNormalizedConfigs = (
458
487
  screen,
459
488
  [...routeNames],
460
489
  [...paths, { screen, path: alias }],
461
- config.parse
490
+ config.parse,
491
+ hasNestedScreens
462
492
  )
463
493
  );
464
494
  } else if (typeof alias === 'object') {
@@ -469,7 +499,8 @@ const createNormalizedConfigs = (
469
499
  alias.exact
470
500
  ? [{ screen, path: alias.path }]
471
501
  : [...paths, { screen, path: alias.path }],
472
- alias.parse
502
+ alias.parse,
503
+ hasNestedScreens
473
504
  )
474
505
  );
475
506
  }
@@ -484,7 +515,13 @@ const createNormalizedConfigs = (
484
515
 
485
516
  paths.push({ screen, path: config.path });
486
517
  configs.push(
487
- createConfigItem(screen, [...routeNames], [...paths], config.parse)
518
+ createConfigItem(
519
+ screen,
520
+ [...routeNames],
521
+ [...paths],
522
+ config.parse,
523
+ hasNestedScreens
524
+ )
488
525
  );
489
526
 
490
527
  configs.push(...aliasConfigs);
@@ -500,7 +537,7 @@ const createNormalizedConfigs = (
500
537
  );
501
538
  }
502
539
 
503
- if (config.screens) {
540
+ if (nestedScreens) {
504
541
  // property `initialRouteName` without `screens` has no purpose
505
542
  if (config.initialRouteName) {
506
543
  initials.push({
@@ -509,10 +546,10 @@ const createNormalizedConfigs = (
509
546
  });
510
547
  }
511
548
 
512
- Object.keys(config.screens).forEach((nestedConfig) => {
549
+ Object.keys(nestedScreens).forEach((nestedConfig) => {
513
550
  const result = createNormalizedConfigs(
514
551
  nestedConfig,
515
- config.screens as Record<string, string | PathConfig<ParamListBase>>,
552
+ nestedScreens as Record<string, string | PathConfig<ParamListBase>>,
516
553
  initials,
517
554
  [...paths],
518
555
  [...parentScreens],
@@ -533,7 +570,8 @@ const createConfigItem = (
533
570
  screen: string,
534
571
  routeNames: string[],
535
572
  paths: { screen: string; path: string }[],
536
- parse?: ParseConfig
573
+ parse?: ParseConfig,
574
+ hasNestedScreens = false
537
575
  ): RouteConfig => {
538
576
  const parts: (PatternPart & { screen: string })[] = [];
539
577
 
@@ -578,22 +616,11 @@ const createConfigItem = (
578
616
  params,
579
617
  routeNames,
580
618
  parse,
619
+ explicitParamNames: getExplicitParamNames(parse),
620
+ hasNestedScreens,
581
621
  };
582
622
  };
583
623
 
584
- const findParseConfigForRoute = (
585
- routeName: string,
586
- flatConfig: RouteConfig[]
587
- ): ParseConfig | undefined => {
588
- for (const config of flatConfig) {
589
- if (routeName === config.routeNames[config.routeNames.length - 1]) {
590
- return config.parse;
591
- }
592
- }
593
-
594
- return undefined;
595
- };
596
-
597
624
  // Try to find an initial route connected with the one passed
598
625
  const findInitialRoute = (
599
626
  routeName: string,
@@ -655,7 +682,7 @@ const createNestedStateObject = (
655
682
  path: string,
656
683
  routes: ParsedRoute[],
657
684
  initialRoutes: InitialRouteConfig[],
658
- flatConfig?: RouteConfig[]
685
+ routeConfig?: RouteConfig
659
686
  ) => {
660
687
  let route = routes.shift() as ParsedRoute;
661
688
  const parentScreens: string[] = [];
@@ -699,7 +726,10 @@ const createNestedStateObject = (
699
726
 
700
727
  const params = parseQueryParams(
701
728
  path,
702
- flatConfig ? findParseConfigForRoute(route.name, flatConfig) : undefined
729
+ routeConfig?.parse,
730
+ routeConfig?.explicitParamNames,
731
+ routeConfig?.hasNestedScreens,
732
+ route.params
703
733
  );
704
734
 
705
735
  if (params) {
@@ -711,7 +741,10 @@ const createNestedStateObject = (
711
741
 
712
742
  const parseQueryParams = (
713
743
  path: string,
714
- parseConfig?: Record<string, (value: string) => unknown>
744
+ parseConfig?: ParseConfig,
745
+ explicitParamNames?: Set<string>,
746
+ hasNestedScreens = false,
747
+ routeParams?: Record<string, unknown>
715
748
  ) => {
716
749
  const query = path.split('?')[1];
717
750
  const params: Record<string, unknown> = queryString.parse(query);
@@ -727,5 +760,19 @@ const parseQueryParams = (
727
760
  });
728
761
  }
729
762
 
763
+ if (
764
+ hasNestedScreens &&
765
+ !explicitParamNames?.has('screen') &&
766
+ (typeof params.screen === 'string' ||
767
+ typeof routeParams?.screen === 'string')
768
+ ) {
769
+ for (const name of NESTED_SCREEN_PARAM_NAMES) {
770
+ if (!explicitParamNames?.has(name)) {
771
+ // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
772
+ delete params[name];
773
+ }
774
+ }
775
+ }
776
+
730
777
  return Object.keys(params).length ? params : undefined;
731
778
  };
package/src/index.tsx CHANGED
@@ -25,7 +25,6 @@ export {
25
25
  type StaticConfigGroup,
26
26
  type StaticConfigScreens,
27
27
  type StaticNavigation,
28
- type StaticParamList,
29
28
  type StaticScreenProps,
30
29
  } from './StaticNavigation';
31
30
  export { ThemeContext } from './theming/ThemeContext';
package/src/types.tsx CHANGED
@@ -10,14 +10,148 @@ import type {
10
10
  } from '@react-navigation/routers';
11
11
  import type * as React from 'react';
12
12
 
13
+ /**
14
+ * Flatten a type to remove all type alias names, unions etc.
15
+ * This will show a plain object when hovering over the type.
16
+ */
17
+ type FlatType<T> = { [K in keyof T]: T[K] } & {};
18
+
19
+ /**
20
+ * keyof T doesn't work for union types. We can use distributive conditional types instead.
21
+ * https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types
22
+ */
23
+ type KeysOf<T> = T extends {} ? keyof T : never;
24
+
25
+ /**
26
+ * We get a union type when using keyof, but we want an intersection instead.
27
+ * https://stackoverflow.com/a/50375286/1665026
28
+ */
29
+ type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
30
+ k: infer I
31
+ ) => void
32
+ ? I
33
+ : never;
34
+
35
+ type UnknownToUndefined<T> = unknown extends T ? undefined : T;
36
+
37
+ type ParamsForScreenComponent<T> = T extends {
38
+ screen: React.ComponentType<{ route: { params: infer P } }>;
39
+ }
40
+ ? P
41
+ : T extends React.ComponentType<{ route: { params: infer P } }>
42
+ ? P
43
+ : undefined;
44
+
45
+ type StaticNavigationConfig = {
46
+ readonly config: {
47
+ readonly screens?: Record<string, any>;
48
+ readonly groups?: {
49
+ [key: string]: {
50
+ screens: Record<string, any>;
51
+ };
52
+ };
53
+ };
54
+ };
55
+
56
+ type ParamsForScreen<T> = T extends {
57
+ screen: infer Screen;
58
+ }
59
+ ? Screen extends StaticNavigationConfig
60
+ ? NavigatorScreenParams<StaticParamList<Screen>> | undefined
61
+ : UnknownToUndefined<ParamsForScreenComponent<T>>
62
+ : T extends StaticNavigationConfig
63
+ ? NavigatorScreenParams<StaticParamList<T>> | undefined
64
+ : UnknownToUndefined<ParamsForScreenComponent<T>>;
65
+
66
+ type ParamListForScreens<Screens> = {
67
+ [Key in KeysOf<Screens>]: ParamsForScreen<Screens[Key]>;
68
+ };
69
+
70
+ type ParamListForGroups<
71
+ Groups extends
72
+ | Readonly<{
73
+ [key: string]: {
74
+ screens: Record<string, any>;
75
+ };
76
+ }>
77
+ | undefined,
78
+ > = Groups extends {
79
+ [key: string]: {
80
+ screens: Record<string, any>;
81
+ };
82
+ }
83
+ ? ParamListForScreens<UnionToIntersection<Groups[keyof Groups]['screens']>>
84
+ : {};
85
+
86
+ /**
87
+ * Infer the param list from the static navigation config.
88
+ */
89
+ export type StaticParamList<T extends StaticNavigationConfig> = FlatType<
90
+ ParamListForScreens<T['config']['screens']> &
91
+ ParamListForGroups<T['config']['groups']>
92
+ >;
93
+
94
+ type ParamListForStaticNavigator<T> = T extends StaticNavigationConfig
95
+ ? StaticParamList<T>
96
+ : {};
97
+
98
+ type ParamListForTypedNavigator<T> = T extends {
99
+ Screen: any;
100
+ } & PrivateValueStore<infer Value>
101
+ ? Value[0]
102
+ : {};
103
+
104
+ type ParamListForRootNavigator<T> =
105
+ string extends keyof ParamListForTypedNavigator<T>
106
+ ? ParamListForStaticNavigator<T>
107
+ : ParamListForTypedNavigator<T>;
108
+
109
+ /**
110
+ * Root navigator used in the app.
111
+ * It's used for the global types in the app.
112
+ *
113
+ * Users need to use module augmentation to add their navigator type:
114
+ *
115
+ * ```ts
116
+ * // Navigator created with static or dynamic API
117
+ * const RootStack = createStackNavigator({
118
+ * // ...
119
+ * });
120
+ *
121
+ * type RootStackType = typeof RootStack;
122
+ *
123
+ * declare module '@react-navigation/core' {
124
+ * interface RootNavigator extends RootStackType {}
125
+ * }
126
+ * ```
127
+ */
128
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
129
+ export interface RootNavigator {}
130
+
131
+ /**
132
+ * Theme object for the navigation components.
133
+ *
134
+ * Custom properties can be added using declaration merging:
135
+ *
136
+ * ```ts
137
+ * declare module '@react-navigation/core' {
138
+ * interface Theme extends NativeTheme {
139
+ * myCustomProperty: string;
140
+ * }
141
+ * }
142
+ * ```
143
+ */
144
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
145
+ export interface Theme {}
146
+
147
+ type RootTheme = Theme;
148
+
13
149
  declare global {
14
150
  // eslint-disable-next-line @typescript-eslint/no-namespace
15
151
  namespace ReactNavigation {
16
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
17
- interface RootParamList {}
152
+ interface RootParamList extends ParamListForRootNavigator<RootNavigator> {}
18
153
 
19
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
20
- interface Theme {}
154
+ interface Theme extends RootTheme {}
21
155
  }
22
156
  }
23
157
 
@@ -75,59 +75,54 @@ export function useEventEmitter<T extends Record<string, any>>(
75
75
  target?: string;
76
76
  canPreventDefault?: boolean;
77
77
  }) => {
78
- const items = listeners.current[type] || {};
78
+ const items = listeners.current[type];
79
79
 
80
80
  // Copy the current list of callbacks in case they are mutated during execution
81
- const callbacks =
82
- target !== undefined
83
- ? items[target]?.slice()
84
- : ([] as Listeners)
85
- .concat(...Object.keys(items).map((t) => items[t]))
86
- .filter((cb, i, self) => self.lastIndexOf(cb) === i);
87
-
88
- const event: EventArg<any, any, any> = {
89
- get type() {
90
- return type;
91
- },
81
+ let callbacks: Listeners | undefined;
82
+
83
+ if (items !== undefined) {
84
+ callbacks =
85
+ target !== undefined
86
+ ? items[target]?.slice()
87
+ : ([] as Listeners)
88
+ .concat(...Object.keys(items).map((t) => items[t]))
89
+ .filter((cb, i, self) => self.lastIndexOf(cb) === i);
90
+ }
91
+
92
+ const descriptors: PropertyDescriptorMap = {
93
+ type: { enumerable: true, value: type },
92
94
  };
93
95
 
94
96
  if (target !== undefined) {
95
- Object.defineProperty(event, 'target', {
96
- enumerable: true,
97
- get() {
98
- return target;
99
- },
100
- });
97
+ descriptors.target = { enumerable: true, value: target };
101
98
  }
102
99
 
103
100
  if (data !== undefined) {
104
- Object.defineProperty(event, 'data', {
105
- enumerable: true,
106
- get() {
107
- return data;
108
- },
109
- });
101
+ descriptors.data = { enumerable: true, value: data };
110
102
  }
111
103
 
104
+ let defaultPrevented = false;
105
+
112
106
  if (canPreventDefault) {
113
- let defaultPrevented = false;
114
-
115
- Object.defineProperties(event, {
116
- defaultPrevented: {
117
- enumerable: true,
118
- get() {
119
- return defaultPrevented;
120
- },
107
+ descriptors.defaultPrevented = {
108
+ enumerable: true,
109
+ get() {
110
+ return defaultPrevented;
121
111
  },
122
- preventDefault: {
123
- enumerable: true,
124
- value() {
125
- defaultPrevented = true;
126
- },
112
+ };
113
+ descriptors.preventDefault = {
114
+ enumerable: true,
115
+ value() {
116
+ defaultPrevented = true;
127
117
  },
128
- });
118
+ };
129
119
  }
130
120
 
121
+ const event: EventArg<any, any, any> = Object.defineProperties(
122
+ {} as EventArg<any, any, any>,
123
+ descriptors
124
+ );
125
+
131
126
  listenRef.current?.(event);
132
127
 
133
128
  callbacks?.forEach((cb) => cb(event));