@unif/react-native-design 0.31.3 → 0.32.1

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 (165) hide show
  1. package/README.md +23 -171
  2. package/lib/module/components/ui/Avatar/Avatar.js +2 -1
  3. package/lib/module/components/ui/Avatar/Avatar.js.map +1 -1
  4. package/lib/module/components/ui/Avatar/geometry.js +43 -0
  5. package/lib/module/components/ui/Avatar/geometry.js.map +1 -0
  6. package/lib/module/components/ui/Avatar/styles.js +1 -41
  7. package/lib/module/components/ui/Avatar/styles.js.map +1 -1
  8. package/lib/module/components/ui/AvatarGroup/AvatarGroup.js +1 -1
  9. package/lib/module/components/ui/AvatarGroup/AvatarGroup.js.map +1 -1
  10. package/lib/module/components/ui/CircularProgress/CircularProgress.js +14 -25
  11. package/lib/module/components/ui/CircularProgress/CircularProgress.js.map +1 -1
  12. package/lib/module/components/ui/CircularProgress/normalizeCircularProgress.js.map +1 -1
  13. package/lib/module/components/ui/CircularProgress/styles.js +24 -0
  14. package/lib/module/components/ui/CircularProgress/styles.js.map +1 -0
  15. package/lib/module/components/ui/Pulse/PulseDot.js +5 -5
  16. package/lib/module/components/ui/Pulse/PulseDot.js.map +1 -1
  17. package/lib/module/components/ui/Pulse/constants.js +15 -0
  18. package/lib/module/components/ui/Pulse/constants.js.map +1 -0
  19. package/lib/module/components/ui/Pulse/usePulse.js +4 -62
  20. package/lib/module/components/ui/Pulse/usePulse.js.map +1 -1
  21. package/lib/module/components/ui/Skeleton/Skeleton.js +6 -2
  22. package/lib/module/components/ui/Skeleton/Skeleton.js.map +1 -1
  23. package/lib/module/components/ui/Skeleton/constants.js +9 -0
  24. package/lib/module/components/ui/Skeleton/constants.js.map +1 -0
  25. package/lib/module/components/ui/TextField/TextFieldBase.js +21 -4
  26. package/lib/module/components/ui/TextField/TextFieldBase.js.map +1 -1
  27. package/lib/module/components/ui/TextField/constants.js +7 -0
  28. package/lib/module/components/ui/TextField/constants.js.map +1 -0
  29. package/lib/module/components/ui/TextField/styles.js +10 -2
  30. package/lib/module/components/ui/TextField/styles.js.map +1 -1
  31. package/lib/module/components/ui/TextField/useMultilineLayout.js +18 -0
  32. package/lib/module/components/ui/TextField/useMultilineLayout.js.map +1 -0
  33. package/lib/module/components/ui/TextField/useMultilineLayout.web.js +44 -0
  34. package/lib/module/components/ui/TextField/useMultilineLayout.web.js.map +1 -0
  35. package/lib/module/components/ui/Thumbnail/Thumbnail.js +13 -6
  36. package/lib/module/components/ui/Thumbnail/Thumbnail.js.map +1 -1
  37. package/lib/module/components/ui/Thumbnail/constants.js +22 -0
  38. package/lib/module/components/ui/Thumbnail/constants.js.map +1 -0
  39. package/lib/module/components/ui/Thumbnail/normalize.js +30 -1
  40. package/lib/module/components/ui/Thumbnail/normalize.js.map +1 -1
  41. package/lib/module/components/ui/Thumbnail/styles.js +5 -28
  42. package/lib/module/components/ui/Thumbnail/styles.js.map +1 -1
  43. package/lib/module/components/ui/index.js.map +1 -1
  44. package/lib/module/components/ui/shared/pulse/constants.js +5 -0
  45. package/lib/module/components/ui/shared/pulse/constants.js.map +1 -0
  46. package/lib/module/components/ui/{Pulse → shared/pulse}/normalizePulseOptions.js +2 -12
  47. package/lib/module/components/ui/shared/pulse/normalizePulseOptions.js.map +1 -0
  48. package/lib/module/components/ui/shared/pulse/types.js +2 -0
  49. package/lib/module/components/ui/shared/pulse/types.js.map +1 -0
  50. package/lib/module/components/ui/shared/pulse/usePulseAnimation.js +30 -0
  51. package/lib/module/components/ui/shared/pulse/usePulseAnimation.js.map +1 -0
  52. package/lib/module/components/ui/{Pulse → shared/pulse}/usePulseDriver.js +1 -1
  53. package/lib/module/components/ui/shared/pulse/usePulseDriver.js.map +1 -0
  54. package/lib/module/components/ui/{Pulse → shared/pulse}/usePulseDriver.web.js +2 -17
  55. package/lib/module/components/ui/shared/pulse/usePulseDriver.web.js.map +1 -0
  56. package/lib/typescript/src/components/ui/Avatar/Avatar.d.ts.map +1 -1
  57. package/lib/typescript/src/components/ui/Avatar/geometry.d.ts +7 -0
  58. package/lib/typescript/src/components/ui/Avatar/geometry.d.ts.map +1 -0
  59. package/lib/typescript/src/components/ui/Avatar/styles.d.ts +1 -9
  60. package/lib/typescript/src/components/ui/Avatar/styles.d.ts.map +1 -1
  61. package/lib/typescript/src/components/ui/Avatar/types.d.ts +7 -2
  62. package/lib/typescript/src/components/ui/Avatar/types.d.ts.map +1 -1
  63. package/lib/typescript/src/components/ui/CircularProgress/CircularProgress.d.ts.map +1 -1
  64. package/lib/typescript/src/components/ui/CircularProgress/normalizeCircularProgress.d.ts +2 -14
  65. package/lib/typescript/src/components/ui/CircularProgress/normalizeCircularProgress.d.ts.map +1 -1
  66. package/lib/typescript/src/components/ui/CircularProgress/styles.d.ts +19 -0
  67. package/lib/typescript/src/components/ui/CircularProgress/styles.d.ts.map +1 -0
  68. package/lib/typescript/src/components/ui/CircularProgress/types.d.ts +17 -2
  69. package/lib/typescript/src/components/ui/CircularProgress/types.d.ts.map +1 -1
  70. package/lib/typescript/src/components/ui/Pulse/PulseDot.d.ts.map +1 -1
  71. package/lib/typescript/src/components/ui/Pulse/constants.d.ts +4 -0
  72. package/lib/typescript/src/components/ui/Pulse/constants.d.ts.map +1 -0
  73. package/lib/typescript/src/components/ui/Pulse/types.d.ts +6 -6
  74. package/lib/typescript/src/components/ui/Pulse/types.d.ts.map +1 -1
  75. package/lib/typescript/src/components/ui/Pulse/usePulse.d.ts +0 -9
  76. package/lib/typescript/src/components/ui/Pulse/usePulse.d.ts.map +1 -1
  77. package/lib/typescript/src/components/ui/Skeleton/Skeleton.d.ts.map +1 -1
  78. package/lib/typescript/src/components/ui/Skeleton/constants.d.ts +3 -0
  79. package/lib/typescript/src/components/ui/Skeleton/constants.d.ts.map +1 -0
  80. package/lib/typescript/src/components/ui/Skeleton/types.d.ts +2 -2
  81. package/lib/typescript/src/components/ui/Skeleton/types.d.ts.map +1 -1
  82. package/lib/typescript/src/components/ui/TextField/TextFieldBase.d.ts.map +1 -1
  83. package/lib/typescript/src/components/ui/TextField/constants.d.ts +4 -0
  84. package/lib/typescript/src/components/ui/TextField/constants.d.ts.map +1 -0
  85. package/lib/typescript/src/components/ui/TextField/styles.d.ts +6 -0
  86. package/lib/typescript/src/components/ui/TextField/styles.d.ts.map +1 -1
  87. package/lib/typescript/src/components/ui/TextField/types.d.ts +36 -11
  88. package/lib/typescript/src/components/ui/TextField/types.d.ts.map +1 -1
  89. package/lib/typescript/src/components/ui/TextField/useMultilineLayout.d.ts +4 -0
  90. package/lib/typescript/src/components/ui/TextField/useMultilineLayout.d.ts.map +1 -0
  91. package/lib/typescript/src/components/ui/TextField/useMultilineLayout.web.d.ts +4 -0
  92. package/lib/typescript/src/components/ui/TextField/useMultilineLayout.web.d.ts.map +1 -0
  93. package/lib/typescript/src/components/ui/Textarea/types.d.ts +4 -2
  94. package/lib/typescript/src/components/ui/Textarea/types.d.ts.map +1 -1
  95. package/lib/typescript/src/components/ui/Thumbnail/Thumbnail.d.ts.map +1 -1
  96. package/lib/typescript/src/components/ui/Thumbnail/constants.d.ts +4 -0
  97. package/lib/typescript/src/components/ui/Thumbnail/constants.d.ts.map +1 -0
  98. package/lib/typescript/src/components/ui/Thumbnail/index.d.ts +1 -1
  99. package/lib/typescript/src/components/ui/Thumbnail/index.d.ts.map +1 -1
  100. package/lib/typescript/src/components/ui/Thumbnail/normalize.d.ts +2 -6
  101. package/lib/typescript/src/components/ui/Thumbnail/normalize.d.ts.map +1 -1
  102. package/lib/typescript/src/components/ui/Thumbnail/styles.d.ts +9 -8
  103. package/lib/typescript/src/components/ui/Thumbnail/styles.d.ts.map +1 -1
  104. package/lib/typescript/src/components/ui/Thumbnail/types.d.ts +23 -5
  105. package/lib/typescript/src/components/ui/Thumbnail/types.d.ts.map +1 -1
  106. package/lib/typescript/src/components/ui/index.d.ts +1 -1
  107. package/lib/typescript/src/components/ui/index.d.ts.map +1 -1
  108. package/lib/typescript/src/components/ui/shared/pulse/constants.d.ts +2 -0
  109. package/lib/typescript/src/components/ui/shared/pulse/constants.d.ts.map +1 -0
  110. package/lib/typescript/src/components/ui/shared/pulse/normalizePulseOptions.d.ts +7 -0
  111. package/lib/typescript/src/components/ui/shared/pulse/normalizePulseOptions.d.ts.map +1 -0
  112. package/lib/typescript/src/components/ui/shared/pulse/types.d.ts +17 -0
  113. package/lib/typescript/src/components/ui/shared/pulse/types.d.ts.map +1 -0
  114. package/lib/typescript/src/components/ui/shared/pulse/usePulseAnimation.d.ts +8 -0
  115. package/lib/typescript/src/components/ui/shared/pulse/usePulseAnimation.d.ts.map +1 -0
  116. package/lib/typescript/src/components/ui/{Pulse → shared/pulse}/usePulseDriver.d.ts +1 -1
  117. package/lib/typescript/src/components/ui/shared/pulse/usePulseDriver.d.ts.map +1 -0
  118. package/lib/typescript/src/components/ui/shared/pulse/usePulseDriver.web.d.ts +7 -0
  119. package/lib/typescript/src/components/ui/shared/pulse/usePulseDriver.web.d.ts.map +1 -0
  120. package/package.json +2 -2
  121. package/src/components/ui/Avatar/Avatar.tsx +2 -6
  122. package/src/components/ui/Avatar/geometry.ts +30 -0
  123. package/src/components/ui/Avatar/styles.ts +2 -30
  124. package/src/components/ui/Avatar/types.ts +8 -2
  125. package/src/components/ui/AvatarGroup/AvatarGroup.tsx +1 -1
  126. package/src/components/ui/CircularProgress/CircularProgress.tsx +20 -26
  127. package/src/components/ui/CircularProgress/normalizeCircularProgress.ts +7 -15
  128. package/src/components/ui/CircularProgress/styles.ts +13 -0
  129. package/src/components/ui/CircularProgress/types.ts +19 -2
  130. package/src/components/ui/Pulse/PulseDot.tsx +6 -5
  131. package/src/components/ui/Pulse/constants.ts +14 -0
  132. package/src/components/ui/Pulse/types.ts +6 -6
  133. package/src/components/ui/Pulse/usePulse.ts +4 -81
  134. package/src/components/ui/Skeleton/Skeleton.tsx +6 -6
  135. package/src/components/ui/Skeleton/constants.ts +8 -0
  136. package/src/components/ui/Skeleton/types.ts +2 -2
  137. package/src/components/ui/TextField/TextFieldBase.tsx +28 -4
  138. package/src/components/ui/TextField/constants.ts +6 -0
  139. package/src/components/ui/TextField/styles.ts +8 -2
  140. package/src/components/ui/TextField/types.ts +41 -11
  141. package/src/components/ui/TextField/useMultilineLayout.ts +20 -0
  142. package/src/components/ui/TextField/useMultilineLayout.web.ts +59 -0
  143. package/src/components/ui/Textarea/types.ts +9 -6
  144. package/src/components/ui/Thumbnail/Thumbnail.tsx +19 -5
  145. package/src/components/ui/Thumbnail/constants.ts +24 -0
  146. package/src/components/ui/Thumbnail/index.ts +1 -0
  147. package/src/components/ui/Thumbnail/normalize.ts +38 -19
  148. package/src/components/ui/Thumbnail/styles.ts +6 -17
  149. package/src/components/ui/Thumbnail/types.ts +27 -5
  150. package/src/components/ui/index.ts +1 -0
  151. package/src/components/ui/shared/pulse/constants.ts +2 -0
  152. package/src/components/ui/shared/pulse/normalizePulseOptions.ts +48 -0
  153. package/src/components/ui/shared/pulse/types.ts +18 -0
  154. package/src/components/ui/shared/pulse/usePulseAnimation.ts +29 -0
  155. package/src/components/ui/{Pulse → shared/pulse}/usePulseDriver.ts +2 -2
  156. package/src/components/ui/{Pulse → shared/pulse}/usePulseDriver.web.ts +4 -19
  157. package/lib/module/components/ui/Pulse/normalizePulseOptions.js.map +0 -1
  158. package/lib/module/components/ui/Pulse/usePulseDriver.js.map +0 -1
  159. package/lib/module/components/ui/Pulse/usePulseDriver.web.js.map +0 -1
  160. package/lib/typescript/src/components/ui/Pulse/normalizePulseOptions.d.ts +0 -32
  161. package/lib/typescript/src/components/ui/Pulse/normalizePulseOptions.d.ts.map +0 -1
  162. package/lib/typescript/src/components/ui/Pulse/usePulseDriver.d.ts.map +0 -1
  163. package/lib/typescript/src/components/ui/Pulse/usePulseDriver.web.d.ts +0 -18
  164. package/lib/typescript/src/components/ui/Pulse/usePulseDriver.web.d.ts.map +0 -1
  165. package/src/components/ui/Pulse/normalizePulseOptions.ts +0 -76
@@ -1,6 +1,5 @@
1
1
  import { StyleSheet } from 'react-native';
2
- import { r, type ColorTokens } from '../../../theme';
3
- import type { ThumbnailSize } from './types';
2
+ import { type ColorTokens } from '../../../theme';
4
3
 
5
4
  /** Thumbnail 静态样式 ——
6
5
  * - `visualFrame`:固定尺寸、裁切、加载失败 placeholder
@@ -14,19 +13,9 @@ export const makeStyles = (c: ColorTokens) =>
14
13
  ring: {
15
14
  borderWidth: 2,
16
15
  },
16
+ fallback: {
17
+ ...StyleSheet.absoluteFill,
18
+ alignItems: 'center',
19
+ justifyContent: 'center',
20
+ },
17
21
  });
18
-
19
- /** Thumbnail size 阶梯 —— width / height / borderRadius 全 r() 缩放。
20
- * 新增 size 在 types.ts 加 union + 这里加 case,两处同步。 */
21
- export const sizingFor = (size: ThumbnailSize) => {
22
- switch (size) {
23
- case 'sm':
24
- return { width: r(64), height: r(40), borderRadius: r(6) };
25
- case 'lg':
26
- return { width: r(160), height: r(96), borderRadius: r(10) };
27
- case 'md':
28
- default:
29
- // 113×67 与 NewsList / NewsArea 历史一致,16:9.5 接近视频宽高比
30
- return { width: r(113), height: r(67), borderRadius: r(8) };
31
- }
32
- };
@@ -1,3 +1,4 @@
1
+ import type { ReactNode } from 'react';
1
2
  import type {
2
3
  ImageProps,
3
4
  ImageSourcePropType,
@@ -6,12 +7,29 @@ import type {
6
7
  ViewStyle,
7
8
  } from 'react-native';
8
9
 
9
- /** 缩略图尺寸阶梯 —— 跨场景统一,新增 size 在这里 + sizingFor.ts 同步。
10
+ /** 缩略图尺寸阶梯 —— 跨场景统一,新增阶梯在这里与 constants.ts 同步。
10
11
  * - sm:64×40 chat 行内、列表二级图缩略
11
12
  * - md:113×67 NewsList / NewsArea 公告右侧缩略(默认)
12
13
  * - lg:160×96 详情顶部 hero 小图、卡片头图缩略 */
13
14
  export type ThumbnailSize = 'sm' | 'md' | 'lg';
14
15
 
16
+ /** 已计算好的 RN 布局尺寸;组件不再调用 r() 缩放。 */
17
+ export interface ThumbnailDimensions {
18
+ width: number;
19
+ height: number;
20
+ borderRadius?: number;
21
+ }
22
+
23
+ export interface NormalizedThumbnailDimensions {
24
+ dimensions: Required<ThumbnailDimensions>;
25
+ diagnostics: readonly string[];
26
+ }
27
+
28
+ export interface SanitizedThumbnailImageStyle {
29
+ style: ThumbnailImageStyle;
30
+ diagnostics: readonly string[];
31
+ }
32
+
15
33
  export type ThumbnailImageStyle = Omit<
16
34
  ImageStyle,
17
35
  | 'position'
@@ -39,9 +57,11 @@ type ThumbnailSource =
39
57
  uri?: never;
40
58
  };
41
59
 
42
- export type ThumbnailProps = ThumbnailSource & {
43
- /** 视觉尺寸阶梯,默认 'md'。 */
44
- size?: ThumbnailSize;
60
+ interface ThumbnailCommonProps {
61
+ /** 原三档或实际图像框尺寸,默认 md;非法对象整体回退 md。 */
62
+ size?: ThumbnailSize | Readonly<ThumbnailDimensions>;
63
+ /** 无有效 source 或当前图片失败时居中显示;加载中不显示。 */
64
+ fallback?: ReactNode;
45
65
  /** 选中态:在固定 visual frame 内显示 2pt 品牌色 ring。 */
46
66
  selected?: boolean;
47
67
  /** 完整外部布局样式,只落到稳定根 View。 */
@@ -54,4 +74,6 @@ export type ThumbnailProps = ThumbnailSource & {
54
74
  accessibilityLabel?: string;
55
75
  /** E2E / 测试定位,只落到外层 layout View。 */
56
76
  testID?: string;
57
- };
77
+ }
78
+
79
+ export type ThumbnailProps = ThumbnailSource & ThumbnailCommonProps;
@@ -97,6 +97,7 @@ export type { TextareaProps } from './Textarea';
97
97
  export { Thumbnail } from './Thumbnail';
98
98
  export type {
99
99
  ThumbnailImageStyle,
100
+ ThumbnailDimensions,
100
101
  ThumbnailProps,
101
102
  ThumbnailSize,
102
103
  } from './Thumbnail';
@@ -0,0 +1,2 @@
1
+ // 平台计时器使用 int32,达到 2^31 会溢出。
2
+ export const MAX_TIMER = 2 ** 31;
@@ -0,0 +1,48 @@
1
+ import type { PulseOptions } from '../../Pulse/types';
2
+ import type {
3
+ NormalizedPulseOptions,
4
+ PulseDefaults,
5
+ PulseDiagnostic,
6
+ } from './types';
7
+ import { MAX_TIMER } from './constants';
8
+
9
+ /** 共用的参数校验:非法字段采用所属组件默认值并报告诊断,不夹值或取整。 */
10
+ export function normalizePulseOptions(
11
+ input: PulseOptions = {},
12
+ defaults: Readonly<PulseDefaults>
13
+ ): NormalizedPulseOptions {
14
+ const diagnostics: PulseDiagnostic[] = [];
15
+ const pick = (
16
+ field: keyof PulseDefaults,
17
+ valid: (value: number) => boolean
18
+ ): number => {
19
+ const received = input[field];
20
+ if (received === undefined) return defaults[field];
21
+ if (typeof received === 'number' && valid(received)) return received;
22
+ diagnostics.push({ field, received, fallback: defaults[field] });
23
+ return defaults[field];
24
+ };
25
+ const duration = pick(
26
+ 'duration',
27
+ (value) => Number.isFinite(value) && value >= 1 && value < MAX_TIMER
28
+ );
29
+ const delay = pick(
30
+ 'delay',
31
+ (value) => Number.isFinite(value) && value >= 0 && value < MAX_TIMER
32
+ );
33
+ const from = pick(
34
+ 'from',
35
+ (value) => Number.isFinite(value) && value >= 0 && value <= 1
36
+ );
37
+ const to = pick(
38
+ 'to',
39
+ (value) => Number.isFinite(value) && value >= 0 && value <= 1
40
+ );
41
+ return { duration, delay, from, to, isStatic: from === to, diagnostics };
42
+ }
43
+
44
+ /** driver 是否应当启动循环。静态或系统开启减弱动效时都不启动。 */
45
+ export const shouldAnimatePulse = (
46
+ options: NormalizedPulseOptions,
47
+ reducedMotion: boolean
48
+ ): boolean => !reducedMotion && !options.isStatic;
@@ -0,0 +1,18 @@
1
+ export interface PulseDefaults {
2
+ duration: number;
3
+ delay: number;
4
+ from: number;
5
+ to: number;
6
+ }
7
+
8
+ export interface PulseDiagnostic {
9
+ field: keyof PulseDefaults;
10
+ received: unknown;
11
+ fallback: number;
12
+ }
13
+
14
+ export interface NormalizedPulseOptions extends PulseDefaults {
15
+ /** 两端相等 —— 没有可播的动画,driver 直接停在 `to`。 */
16
+ isStatic: boolean;
17
+ diagnostics: readonly PulseDiagnostic[];
18
+ }
@@ -0,0 +1,29 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import type { createLogger } from '../../../../utils/logger';
3
+ import type { PulseOptions } from '../../Pulse/types';
4
+ import type { PulseDefaults } from './types';
5
+ import { normalizePulseOptions } from './normalizePulseOptions';
6
+ import { usePulseDriver } from './usePulseDriver';
7
+
8
+ /** 共享参数解释与平台驱动;默认值和诊断 scope 由所属组件交付。 */
9
+ export function usePulseAnimation(
10
+ { duration, delay, from, to }: PulseOptions = {},
11
+ defaults: Readonly<PulseDefaults>,
12
+ log: ReturnType<typeof createLogger>
13
+ ) {
14
+ // 各组件交付自己的固定默认值;内联 options 的新引用不重启动效。
15
+ const normalized = useMemo(
16
+ () => normalizePulseOptions({ duration, delay, from, to }, defaults),
17
+ [duration, delay, from, to, defaults]
18
+ );
19
+ useEffect(() => {
20
+ if (__DEV__) {
21
+ for (const item of normalized.diagnostics) {
22
+ log.warn(
23
+ `${item.field}=${String(item.received)} 无效,已回退为 ${item.fallback}`
24
+ );
25
+ }
26
+ }
27
+ }, [normalized.diagnostics, log]);
28
+ return usePulseDriver(normalized);
29
+ }
@@ -8,9 +8,9 @@ import {
8
8
  withSequence,
9
9
  withTiming,
10
10
  } from 'react-native-reanimated';
11
- import { usePrefersReducedMotion } from '../../../theme';
11
+ import { usePrefersReducedMotion } from '../../../../theme';
12
12
  import { shouldAnimatePulse } from './normalizePulseOptions';
13
- import type { NormalizedPulseOptions } from './normalizePulseOptions';
13
+ import type { NormalizedPulseOptions } from './types';
14
14
 
15
15
  /**
16
16
  * native Pulse driver —— reanimated 4 worklet,整个动画在 UI 线程跑、不占 JS 桥。
@@ -1,21 +1,9 @@
1
1
  import { useEffect, useState } from 'react';
2
- import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
3
- import { usePrefersReducedMotion } from '../../../theme';
2
+ import { usePrefersReducedMotion } from '../../../../theme';
4
3
  import { shouldAnimatePulse } from './normalizePulseOptions';
5
- import type { NormalizedPulseOptions } from './normalizePulseOptions';
4
+ import type { NormalizedPulseOptions } from './types';
6
5
 
7
- /**
8
- * Web Pulse driver —— RN-Web 上 reanimated 的 `useAnimatedStyle` 链路会在
9
- * `_updatePropsJS` 里对 undefined 取 `Object.keys` 而抛 TypeError,pulse 卡死在初始值。
10
- * 故 web 走 **CSS transition 降级**(零 rAF JS 帧):
11
- *
12
- * - `setInterval` 每隔 `duration` ms 只在 from / to 两档翻转 opacity
13
- * —— setState 频率从 ~60fps 降到 1 次/duration ms(参 ToastHost.web.tsx)
14
- * - 补间交给浏览器 CSS transition,JS 侧零插值
15
- *
16
- * **只接受归一化产物**:duration 已被验证在 `[1, 2^31)`,不会出现 `setInterval(0)`
17
- * 那种「每帧触发」的退化。web 端**不跑 worklet** —— 别在文档里宣称它跑。
18
- */
6
+ /** Web 由 CSS 补间,timer 只按归一化后的半周期切换透明度;每个实例独立释放。 */
19
7
  export function usePulseDriver(options: NormalizedPulseOptions) {
20
8
  const reduced = usePrefersReducedMotion();
21
9
  const animate = shouldAnimatePulse(options, reduced);
@@ -54,13 +42,10 @@ export function usePulseDriver(options: NormalizedPulseOptions) {
54
42
  };
55
43
  }, [animate, from, to, duration, delay]);
56
44
 
57
- // RN-Web 把 transition 字段透传到 DOM style,实现 CSS 级平滑过渡。
58
- // RN 的 style typedef 没有 transition 字段,故用交叉类型补一个窄声明 ——
59
- // 比整块 `as any` 精确:仍然保留 opacity 的类型检查。
60
45
  return {
61
46
  opacity,
62
47
  ...(animate
63
48
  ? { transition: `opacity ${duration}ms ease-in-out` }
64
49
  : undefined),
65
- } as (ViewStyle | TextStyle | ImageStyle) & { transition?: string };
50
+ };
66
51
  }
@@ -1 +0,0 @@
1
- {"version":3,"names":["MAX_TIMER","normalizePulseOptions","input","defaults","diagnostics","pick","field","valid","received","undefined","push","fallback","duration","value","Number","isFinite","delay","from","to","isStatic","shouldAnimatePulse","options","reducedMotion"],"sourceRoot":"../../../../../src","sources":["components/ui/Pulse/normalizePulseOptions.ts"],"mappings":";;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAqBA;AACA,MAAMA,SAAS,GAAG,CAAC,IAAI,EAAE;AAEzB,OAAO,SAASC,qBAAqBA,CACnCC,KAAmB,GAAG,CAAC,CAAC,EACxBC,QAAuB,EACC;EACxB,MAAMC,WAA8B,GAAG,EAAE;EACzC,MAAMC,IAAI,GAAGA,CACXC,KAA0B,EAC1BC,KAAiC,KACtB;IACX,MAAMC,QAAQ,GAAGN,KAAK,CAACI,KAAK,CAAC;IAC7B,IAAIE,QAAQ,KAAKC,SAAS,EAAE,OAAON,QAAQ,CAACG,KAAK,CAAC;IAClD,IAAI,OAAOE,QAAQ,KAAK,QAAQ,IAAID,KAAK,CAACC,QAAQ,CAAC,EAAE,OAAOA,QAAQ;IACpEJ,WAAW,CAACM,IAAI,CAAC;MAAEJ,KAAK;MAAEE,QAAQ;MAAEG,QAAQ,EAAER,QAAQ,CAACG,KAAK;IAAE,CAAC,CAAC;IAChE,OAAOH,QAAQ,CAACG,KAAK,CAAC;EACxB,CAAC;EACD,MAAMM,QAAQ,GAAGP,IAAI,CACnB,UAAU,EACTQ,KAAK,IAAKC,MAAM,CAACC,QAAQ,CAACF,KAAK,CAAC,IAAIA,KAAK,IAAI,CAAC,IAAIA,KAAK,GAAGb,SAC7D,CAAC;EACD,MAAMgB,KAAK,GAAGX,IAAI,CAChB,OAAO,EACNQ,KAAK,IAAKC,MAAM,CAACC,QAAQ,CAACF,KAAK,CAAC,IAAIA,KAAK,IAAI,CAAC,IAAIA,KAAK,GAAGb,SAC7D,CAAC;EACD,MAAMiB,IAAI,GAAGZ,IAAI,CACf,MAAM,EACLQ,KAAK,IAAKC,MAAM,CAACC,QAAQ,CAACF,KAAK,CAAC,IAAIA,KAAK,IAAI,CAAC,IAAIA,KAAK,IAAI,CAC9D,CAAC;EACD,MAAMK,EAAE,GAAGb,IAAI,CACb,IAAI,EACHQ,KAAK,IAAKC,MAAM,CAACC,QAAQ,CAACF,KAAK,CAAC,IAAIA,KAAK,IAAI,CAAC,IAAIA,KAAK,IAAI,CAC9D,CAAC;EACD,OAAO;IAAED,QAAQ;IAAEI,KAAK;IAAEC,IAAI;IAAEC,EAAE;IAAEC,QAAQ,EAAEF,IAAI,KAAKC,EAAE;IAAEd;EAAY,CAAC;AAC1E;;AAEA;AACA,OAAO,MAAMgB,kBAAkB,GAAGA,CAChCC,OAA+B,EAC/BC,aAAsB,KACV,CAACA,aAAa,IAAI,CAACD,OAAO,CAACF,QAAQ","ignoreList":[]}
@@ -1 +0,0 @@
1
- {"version":3,"names":["useEffect","cancelAnimation","useAnimatedStyle","useSharedValue","withDelay","withRepeat","withSequence","withTiming","usePrefersReducedMotion","shouldAnimatePulse","usePulseDriver","options","reduced","animate","from","to","duration","delay","op","value","cycle","opacity"],"sourceRoot":"../../../../../src","sources":["components/ui/Pulse/usePulseDriver.ts"],"mappings":";;AAAA,SAASA,SAAS,QAAQ,OAAO;AACjC,SACEC,eAAe,EACfC,gBAAgB,EAChBC,cAAc,EACdC,SAAS,EACTC,UAAU,EACVC,YAAY,EACZC,UAAU,QACL,yBAAyB;AAChC,SAASC,uBAAuB,QAAQ,yBAAgB;AACxD,SAASC,kBAAkB,QAAQ,4BAAyB;AAG5D;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,OAA+B,EAAE;EAC9D,MAAMC,OAAO,GAAGJ,uBAAuB,CAAC,CAAC;EACzC,MAAMK,OAAO,GAAGJ,kBAAkB,CAACE,OAAO,EAAEC,OAAO,CAAC;EACpD,MAAM;IAAEE,IAAI;IAAEC,EAAE;IAAEC,QAAQ;IAAEC;EAAM,CAAC,GAAGN,OAAO;EAC7C,MAAMO,EAAE,GAAGf,cAAc,CAACU,OAAO,GAAGC,IAAI,GAAGC,EAAE,CAAC;EAE9Cf,SAAS,CAAC,MAAM;IACd,IAAI,CAACa,OAAO,EAAE;MACZ;MACAZ,eAAe,CAACiB,EAAE,CAAC;MACnBA,EAAE,CAACC,KAAK,GAAGJ,EAAE;MACb;IACF;IACA;IACAG,EAAE,CAACC,KAAK,GAAGL,IAAI;IACf,MAAMM,KAAK,GAAGf,UAAU,CACtBC,YAAY,CACVC,UAAU,CAACQ,EAAE,EAAE;MAAEC;IAAS,CAAC,CAAC,EAC5BT,UAAU,CAACO,IAAI,EAAE;MAAEE;IAAS,CAAC,CAC/B,CAAC,EACD,CAAC,CAAC,EACF,KACF,CAAC;IACDE,EAAE,CAACC,KAAK,GAAGF,KAAK,GAAG,CAAC,GAAGb,SAAS,CAACa,KAAK,EAAEG,KAAK,CAAC,GAAGA,KAAK;IACtD,OAAO,MAAMnB,eAAe,CAACiB,EAAE,CAAC;EAClC,CAAC,EAAE,CAACA,EAAE,EAAEL,OAAO,EAAEC,IAAI,EAAEC,EAAE,EAAEC,QAAQ,EAAEC,KAAK,CAAC,CAAC;EAE5C,OAAOf,gBAAgB,CAAC,OAAO;IAAEmB,OAAO,EAAEH,EAAE,CAACC;EAAM,CAAC,CAAC,CAAC;AACxD","ignoreList":[]}
@@ -1 +0,0 @@
1
- {"version":3,"names":["useEffect","useState","usePrefersReducedMotion","shouldAnimatePulse","usePulseDriver","options","reduced","animate","from","to","duration","delay","opacity","setOpacity","timer","delayTimer","current","start","setInterval","setTimeout","clearTimeout","clearInterval","transition","undefined"],"sourceRoot":"../../../../../src","sources":["components/ui/Pulse/usePulseDriver.web.ts"],"mappings":";;AAAA,SAASA,SAAS,EAAEC,QAAQ,QAAQ,OAAO;AAE3C,SAASC,uBAAuB,QAAQ,yBAAgB;AACxD,SAASC,kBAAkB,QAAQ,4BAAyB;AAG5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,cAAcA,CAACC,OAA+B,EAAE;EAC9D,MAAMC,OAAO,GAAGJ,uBAAuB,CAAC,CAAC;EACzC,MAAMK,OAAO,GAAGJ,kBAAkB,CAACE,OAAO,EAAEC,OAAO,CAAC;EACpD,MAAM;IAAEE,IAAI;IAAEC,EAAE;IAAEC,QAAQ;IAAEC;EAAM,CAAC,GAAGN,OAAO;EAC7C,MAAM,CAACO,OAAO,EAAEC,UAAU,CAAC,GAAGZ,QAAQ,CAACM,OAAO,GAAGC,IAAI,GAAGC,EAAE,CAAC;EAE3DT,SAAS,CAAC,MAAM;IACd,IAAI,CAACO,OAAO,EAAE;MACZ;MACAM,UAAU,CAACJ,EAAE,CAAC;MACd;IACF;IAEAI,UAAU,CAACL,IAAI,CAAC;IAChB,IAAIM,KAA4C,GAAG,IAAI;IACvD,IAAIC,UAAgD,GAAG,IAAI;IAC3D,IAAIC,OAAO,GAAGR,IAAI;IAElB,MAAMS,KAAK,GAAGA,CAAA,KAAM;MAClBH,KAAK,GAAGI,WAAW,CAAC,MAAM;QACxBF,OAAO,GAAGA,OAAO,KAAKR,IAAI,GAAGC,EAAE,GAAGD,IAAI;QACtCK,UAAU,CAACG,OAAO,CAAC;MACrB,CAAC,EAAEN,QAAQ,CAAC;IACd,CAAC;IAED,IAAIC,KAAK,GAAG,CAAC,EAAE;MACbI,UAAU,GAAGI,UAAU,CAACF,KAAK,EAAEN,KAAK,CAAC;IACvC,CAAC,MAAM;MACLM,KAAK,CAAC,CAAC;IACT;;IAEA;IACA,OAAO,MAAM;MACX,IAAIF,UAAU,IAAI,IAAI,EAAEK,YAAY,CAACL,UAAU,CAAC;MAChD,IAAID,KAAK,IAAI,IAAI,EAAEO,aAAa,CAACP,KAAK,CAAC;IACzC,CAAC;EACH,CAAC,EAAE,CAACP,OAAO,EAAEC,IAAI,EAAEC,EAAE,EAAEC,QAAQ,EAAEC,KAAK,CAAC,CAAC;;EAExC;EACA;EACA;EACA,OAAO;IACLC,OAAO;IACP,IAAIL,OAAO,GACP;MAAEe,UAAU,EAAE,WAAWZ,QAAQ;IAAiB,CAAC,GACnDa,SAAS;EACf,CAAC;AACH","ignoreList":[]}
@@ -1,32 +0,0 @@
1
- import type { PulseOptions } from './types';
2
- export type { PulseOptions };
3
- /**
4
- * Pulse 参数的**唯一**归一化入口 —— 公共 hook 先跑完这里,平台 driver 只消费产物。
5
- *
6
- * 为什么要单独一层:此前 native / web 两份 `usePulse` 各自读原始 props 并各自解释
7
- * 默认值,web 侧甚至把未校验的 `duration` 直接喂给 `setInterval`(0 或 2^31 会退化成
8
- * 「每帧触发」)。归一化收口后,两端拿到的一定是同一组已验证数值。
9
- *
10
- * 纪律:**不做 clamp、不做取整**。非法值一律整体回退到 defaults 并记一条诊断 ——
11
- * 悄悄把 `duration: 0` 夹成 `1` 会让调用方永远发现不了自己传错了。
12
- */
13
- export type PulseDefaults = {
14
- duration: number;
15
- delay: number;
16
- from: number;
17
- to: number;
18
- };
19
- export type PulseDiagnostic = {
20
- field: keyof PulseDefaults;
21
- received: unknown;
22
- fallback: number;
23
- };
24
- export type NormalizedPulseOptions = PulseDefaults & {
25
- /** 两端相等 —— 没有可播的动画,driver 直接停在 `to`。 */
26
- isStatic: boolean;
27
- diagnostics: readonly PulseDiagnostic[];
28
- };
29
- export declare function normalizePulseOptions(input: PulseOptions | undefined, defaults: PulseDefaults): NormalizedPulseOptions;
30
- /** driver 是否应当启动循环。静态或系统开启减弱动效时都不启动。 */
31
- export declare const shouldAnimatePulse: (options: NormalizedPulseOptions, reducedMotion: boolean) => boolean;
32
- //# sourceMappingURL=normalizePulseOptions.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"normalizePulseOptions.d.ts","sourceRoot":"","sources":["../../../../../../src/components/ui/Pulse/normalizePulseOptions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B;;;;;;;;;GASG;AAEH,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,aAAa,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG;IACnD,wCAAwC;IACxC,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,SAAS,eAAe,EAAE,CAAC;CACzC,CAAC;AAKF,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,YAAY,YAAK,EACxB,QAAQ,EAAE,aAAa,GACtB,sBAAsB,CA6BxB;AAED,wCAAwC;AACxC,eAAO,MAAM,kBAAkB,GAC7B,SAAS,sBAAsB,EAC/B,eAAe,OAAO,KACrB,OAA8C,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"usePulseDriver.d.ts","sourceRoot":"","sources":["../../../../../../src/components/ui/Pulse/usePulseDriver.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,sBAAsB;;GA4B7D"}
@@ -1,18 +0,0 @@
1
- import type { ImageStyle, TextStyle, ViewStyle } from 'react-native';
2
- import type { NormalizedPulseOptions } from './normalizePulseOptions';
3
- /**
4
- * Web Pulse driver —— RN-Web 上 reanimated 的 `useAnimatedStyle` 链路会在
5
- * `_updatePropsJS` 里对 undefined 取 `Object.keys` 而抛 TypeError,pulse 卡死在初始值。
6
- * 故 web 走 **CSS transition 降级**(零 rAF JS 帧):
7
- *
8
- * - `setInterval` 每隔 `duration` ms 只在 from / to 两档翻转 opacity
9
- * —— setState 频率从 ~60fps 降到 1 次/duration ms(参 ToastHost.web.tsx)
10
- * - 补间交给浏览器 CSS transition,JS 侧零插值
11
- *
12
- * **只接受归一化产物**:duration 已被验证在 `[1, 2^31)`,不会出现 `setInterval(0)`
13
- * 那种「每帧触发」的退化。web 端**不跑 worklet** —— 别在文档里宣称它跑。
14
- */
15
- export declare function usePulseDriver(options: NormalizedPulseOptions): (ViewStyle | TextStyle | ImageStyle) & {
16
- transition?: string;
17
- };
18
- //# sourceMappingURL=usePulseDriver.web.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"usePulseDriver.web.d.ts","sourceRoot":"","sources":["../../../../../../src/components/ui/Pulse/usePulseDriver.web.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,sBAAsB,GA8CvD,CAAC,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CACpE"}
@@ -1,76 +0,0 @@
1
- import type { PulseOptions } from './types';
2
-
3
- export type { PulseOptions };
4
-
5
- /**
6
- * Pulse 参数的**唯一**归一化入口 —— 公共 hook 先跑完这里,平台 driver 只消费产物。
7
- *
8
- * 为什么要单独一层:此前 native / web 两份 `usePulse` 各自读原始 props 并各自解释
9
- * 默认值,web 侧甚至把未校验的 `duration` 直接喂给 `setInterval`(0 或 2^31 会退化成
10
- * 「每帧触发」)。归一化收口后,两端拿到的一定是同一组已验证数值。
11
- *
12
- * 纪律:**不做 clamp、不做取整**。非法值一律整体回退到 defaults 并记一条诊断 ——
13
- * 悄悄把 `duration: 0` 夹成 `1` 会让调用方永远发现不了自己传错了。
14
- */
15
-
16
- export type PulseDefaults = {
17
- duration: number;
18
- delay: number;
19
- from: number;
20
- to: number;
21
- };
22
-
23
- export type PulseDiagnostic = {
24
- field: keyof PulseDefaults;
25
- received: unknown;
26
- fallback: number;
27
- };
28
-
29
- export type NormalizedPulseOptions = PulseDefaults & {
30
- /** 两端相等 —— 没有可播的动画,driver 直接停在 `to`。 */
31
- isStatic: boolean;
32
- diagnostics: readonly PulseDiagnostic[];
33
- };
34
-
35
- // setTimeout / setInterval 内部用 int32 存时长,>= 2^31 会溢出成 0 变「每帧触发」。
36
- const MAX_TIMER = 2 ** 31;
37
-
38
- export function normalizePulseOptions(
39
- input: PulseOptions = {},
40
- defaults: PulseDefaults
41
- ): NormalizedPulseOptions {
42
- const diagnostics: PulseDiagnostic[] = [];
43
- const pick = (
44
- field: keyof PulseDefaults,
45
- valid: (value: number) => boolean
46
- ): number => {
47
- const received = input[field];
48
- if (received === undefined) return defaults[field];
49
- if (typeof received === 'number' && valid(received)) return received;
50
- diagnostics.push({ field, received, fallback: defaults[field] });
51
- return defaults[field];
52
- };
53
- const duration = pick(
54
- 'duration',
55
- (value) => Number.isFinite(value) && value >= 1 && value < MAX_TIMER
56
- );
57
- const delay = pick(
58
- 'delay',
59
- (value) => Number.isFinite(value) && value >= 0 && value < MAX_TIMER
60
- );
61
- const from = pick(
62
- 'from',
63
- (value) => Number.isFinite(value) && value >= 0 && value <= 1
64
- );
65
- const to = pick(
66
- 'to',
67
- (value) => Number.isFinite(value) && value >= 0 && value <= 1
68
- );
69
- return { duration, delay, from, to, isStatic: from === to, diagnostics };
70
- }
71
-
72
- /** driver 是否应当启动循环。静态或系统开启减弱动效时都不启动。 */
73
- export const shouldAnimatePulse = (
74
- options: NormalizedPulseOptions,
75
- reducedMotion: boolean
76
- ): boolean => !reducedMotion && !options.isStatic;