@xhub-short/ui 0.1.0-beta.1 → 0.1.0-beta.11

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 (76) hide show
  1. package/dist/CommentSheet.css-DyEc3Sro.d.ts +217 -0
  2. package/dist/VideoSlotPlayIndicator-DPs8Xt5C.d.ts +51 -0
  3. package/dist/chunk-3OB3OVYR.js +349 -0
  4. package/dist/{chunk-WKX2WBVO.js → chunk-3XPJHUYL.js} +1 -39
  5. package/dist/chunk-4RIMQOBR.js +58 -0
  6. package/dist/chunk-4TUBNA2X.js +180 -0
  7. package/dist/{chunk-4YDIRPIN.js → chunk-ANCP53F3.js} +3 -3
  8. package/dist/{chunk-UXMA4KJZ.js → chunk-CAWE42LH.js} +5 -3
  9. package/dist/{chunk-ANGBSV7L.js → chunk-CIIZ3IHV.js} +10 -5
  10. package/dist/chunk-DR7KR7OT.js +103 -0
  11. package/dist/chunk-DXLCQ4FH.js +102 -0
  12. package/dist/chunk-EDWS2IPH.js +1 -0
  13. package/dist/chunk-FR7UQSZP.js +570 -0
  14. package/dist/chunk-IWSBYOSS.js +91 -0
  15. package/dist/chunk-JEY6R4KJ.js +334 -0
  16. package/dist/chunk-KMJ3PQ7M.js +1262 -0
  17. package/dist/chunk-MFJS65C5.js +368 -0
  18. package/dist/{chunk-HW4LXTFT.js → chunk-OM4L7RE5.js} +18 -6
  19. package/dist/chunk-PBIH2F2Q.js +344 -0
  20. package/dist/chunk-PJ4NMVMY.js +326 -0
  21. package/dist/chunk-Q6MG7AVG.js +531 -0
  22. package/dist/chunk-QCKVF2DR.js +713 -0
  23. package/dist/chunk-QCRRF76W.js +75 -0
  24. package/dist/chunk-QUEJHA24.js +508 -0
  25. package/dist/chunk-VXW7AOGM.js +285 -0
  26. package/dist/chunk-YB7AXTX7.js +430 -0
  27. package/dist/chunk-ZGWSJ6Z5.js +601 -0
  28. package/dist/components/ActionBar/index.js +1 -1
  29. package/dist/components/AdvanceMenu/index.d.ts +78 -0
  30. package/dist/components/AdvanceMenu/index.js +1 -0
  31. package/dist/components/AuthorInfo/index.d.ts +5 -1
  32. package/dist/components/AuthorInfo/index.js +1 -1
  33. package/dist/components/BottomSheet/index.d.ts +82 -0
  34. package/dist/components/BottomSheet/index.js +1 -0
  35. package/dist/components/CleanModeOverlay/index.d.ts +60 -0
  36. package/dist/components/CleanModeOverlay/index.js +1 -0
  37. package/dist/components/CommentSheet/index.d.ts +164 -0
  38. package/dist/components/CommentSheet/index.js +1 -0
  39. package/dist/components/DetailView/index.d.ts +311 -0
  40. package/dist/components/DetailView/index.js +1 -0
  41. package/dist/components/ErrorBoundary/index.js +1 -1
  42. package/dist/components/ImageCarousel/index.d.ts +50 -0
  43. package/dist/components/ImageCarousel/index.js +1 -0
  44. package/dist/components/ImagePostSlot/index.d.ts +207 -0
  45. package/dist/components/ImagePostSlot/index.js +1 -0
  46. package/dist/components/ProgressBar/index.d.ts +30 -2
  47. package/dist/components/ProgressBar/index.js +1 -1
  48. package/dist/components/QualityPicker/index.d.ts +35 -0
  49. package/dist/components/QualityPicker/index.js +1 -0
  50. package/dist/components/ReportSheet/index.d.ts +68 -0
  51. package/dist/components/ReportSheet/index.js +1 -0
  52. package/dist/components/Skeleton/index.js +1 -1
  53. package/dist/components/SpeedPicker/index.d.ts +32 -0
  54. package/dist/components/SpeedPicker/index.js +1 -0
  55. package/dist/components/VideoFeed/index.d.ts +12 -1
  56. package/dist/components/VideoFeed/index.js +1 -1
  57. package/dist/components/VideoInfo/index.d.ts +4 -2
  58. package/dist/components/VideoInfo/index.js +1 -1
  59. package/dist/components/VideoPlayer/index.d.ts +14 -41
  60. package/dist/components/VideoPlayer/index.js +1 -1
  61. package/dist/components/VideoSlot/index.d.ts +84 -65
  62. package/dist/components/VideoSlot/index.js +2 -1
  63. package/dist/components/VirtualSlider/index.d.ts +339 -0
  64. package/dist/components/VirtualSlider/index.js +1 -0
  65. package/dist/components/icons/index.js +1 -1
  66. package/dist/index.d.ts +107 -95
  67. package/dist/index.js +84 -27
  68. package/package.json +51 -7
  69. package/dist/chunk-2PTMP65P.js +0 -738
  70. package/dist/chunk-4MN72OZH.js +0 -148
  71. package/dist/chunk-DHQJBXQW.js +0 -562
  72. package/dist/chunk-SSJDO24Q.js +0 -204
  73. package/dist/chunk-XAOEHLOX.js +0 -1326
  74. package/dist/chunk-YW23IBKF.js +0 -530
  75. package/dist/chunk-ZZDQKP4R.js +0 -418
  76. package/dist/use-gesture-react.esm-3SV4QLEJ.js +0 -1893
@@ -1,418 +0,0 @@
1
- import { cn } from './chunk-WKX2WBVO.js';
2
- import { injectComponentCSS } from './chunk-UXMA4KJZ.js';
3
- import { createContext, useContext, useMemo, useInsertionEffect, useRef, useState, useEffect, useCallback } from 'react';
4
- import { jsx, jsxs } from 'react/jsx-runtime';
5
-
6
- // src/components/VideoFeed/VideoFeed.css.ts
7
- var VIDEO_FEED_CSS = `
8
- /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
9
- * VideoFeed Styles
10
- * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
11
-
12
- .sv-video-feed {
13
- position: relative;
14
- width: 100%;
15
- height: 100%;
16
- overflow: hidden;
17
- background: var(--sv-bg-primary, #000);
18
- touch-action: pan-y;
19
- user-select: none;
20
- -webkit-user-select: none;
21
- }
22
-
23
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
24
- * Feed Container (holds all slots)
25
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
26
-
27
- .sv-video-feed__container {
28
- position: relative;
29
- width: 100%;
30
- height: 100%;
31
- will-change: transform;
32
- }
33
-
34
- /* Smooth transition when not swiping */
35
- .sv-video-feed__container--snapping {
36
- transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
37
- }
38
-
39
- /* No transition during swipe for immediate feedback */
40
- .sv-video-feed__container--swiping {
41
- transition: none;
42
- }
43
-
44
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
45
- * Video Slot Wrapper
46
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
47
-
48
- .sv-video-feed__slot {
49
- position: absolute;
50
- top: 0;
51
- left: 0;
52
- width: 100%;
53
- height: 100%;
54
- will-change: transform, opacity;
55
- backface-visibility: hidden;
56
- -webkit-backface-visibility: hidden;
57
- }
58
-
59
- /* Active slot */
60
- .sv-video-feed__slot--active {
61
- z-index: 2;
62
- }
63
-
64
- /* Adjacent slots (for preview) */
65
- .sv-video-feed__slot--adjacent {
66
- z-index: 1;
67
- pointer-events: none;
68
- }
69
-
70
- /* Transition for smooth snap animations (when not swiping) */
71
- .sv-video-feed__slot--transitioning {
72
- transition: transform 0.175s cubic-bezier(0.25, 0.46, 0.45, 0.94),
73
- opacity 0.2s ease;
74
- }
75
-
76
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
77
- * Loading State
78
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
79
-
80
- .sv-video-feed__loading {
81
- position: absolute;
82
- top: 50%;
83
- left: 50%;
84
- transform: translate(-50%, -50%);
85
- display: flex;
86
- flex-direction: column;
87
- align-items: center;
88
- gap: var(--sv-spacing-md, 16px);
89
- color: var(--sv-text-secondary, #999);
90
- }
91
-
92
- .sv-video-feed__loading-spinner {
93
- width: 32px;
94
- height: 32px;
95
- border: 3px solid rgba(255, 255, 255, 0.2);
96
- border-top-color: var(--sv-color-primary, #fe2c55);
97
- border-radius: 50%;
98
- animation: sv-feed-spinner 0.8s linear infinite;
99
- }
100
-
101
- @keyframes sv-feed-spinner {
102
- to {
103
- transform: rotate(360deg);
104
- }
105
- }
106
-
107
- .sv-video-feed__loading-text {
108
- font-size: var(--sv-font-size-sm, 13px);
109
- font-family: var(--sv-font-family, 'Urbanist', sans-serif);
110
- }
111
-
112
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
113
- * Empty State
114
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
115
-
116
- .sv-video-feed__empty {
117
- position: absolute;
118
- top: 50%;
119
- left: 50%;
120
- transform: translate(-50%, -50%);
121
- text-align: center;
122
- color: var(--sv-text-secondary, #999);
123
- font-family: var(--sv-font-family, 'Urbanist', sans-serif);
124
- }
125
-
126
- .sv-video-feed__empty-icon {
127
- font-size: 48px;
128
- margin-bottom: var(--sv-spacing-md, 16px);
129
- }
130
-
131
- .sv-video-feed__empty-text {
132
- font-size: var(--sv-font-size-md, 14px);
133
- }
134
-
135
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
136
- * End of Feed
137
- * Note: Positioned outside container but above slots. Uses safe-area-inset
138
- * to avoid being covered by device notch/home indicator.
139
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
140
-
141
- .sv-video-feed__end {
142
- position: absolute;
143
- /* Safe bottom positioning - accounts for device UI */
144
- bottom: calc(var(--sv-spacing-xl, 32px) + env(safe-area-inset-bottom, 0px));
145
- left: 50%;
146
- transform: translateX(-50%);
147
- z-index: 10; /* Above slots (z-index 1-2) */
148
- padding: var(--sv-spacing-sm, 8px) var(--sv-spacing-md, 16px);
149
- background: rgba(0, 0, 0, 0.7);
150
- backdrop-filter: blur(8px);
151
- -webkit-backdrop-filter: blur(8px);
152
- border-radius: var(--sv-border-radius-md, 8px);
153
- color: var(--sv-text-secondary, #999);
154
- font-size: var(--sv-font-size-sm, 13px);
155
- font-family: var(--sv-font-family, 'Urbanist', sans-serif);
156
- pointer-events: none; /* Don't block interactions */
157
- animation: sv-feed-end-fade-in 0.3s ease-out;
158
- }
159
-
160
- @keyframes sv-feed-end-fade-in {
161
- from {
162
- opacity: 0;
163
- transform: translateX(-50%) translateY(10px);
164
- }
165
- to {
166
- opacity: 1;
167
- transform: translateX(-50%) translateY(0);
168
- }
169
- }
170
-
171
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
172
- * Reduced Motion
173
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
174
-
175
- @media (prefers-reduced-motion: reduce) {
176
- .sv-video-feed__container--snapping {
177
- transition: none;
178
- }
179
-
180
- .sv-video-feed__loading-spinner {
181
- animation: none;
182
- }
183
- }
184
- `;
185
- var VideoFeedContext = createContext(null);
186
- function useVideoFeedContext() {
187
- const context = useContext(VideoFeedContext);
188
- if (!context) {
189
- throw new Error("useVideoFeedContext must be used within a VideoFeed");
190
- }
191
- return context;
192
- }
193
- function useOptionalVideoFeedContext() {
194
- return useContext(VideoFeedContext);
195
- }
196
- function useVideoFeedPosition(options) {
197
- const {
198
- totalCount,
199
- activeIndex,
200
- containerHeight,
201
- dragOffset,
202
- isSwiping,
203
- isResizing = false,
204
- bufferSize = 1
205
- } = options;
206
- return useMemo(() => {
207
- if (totalCount === 0) {
208
- return {
209
- slots: [],
210
- scrollOffset: 0,
211
- isAtStart: true,
212
- isAtLastIndex: true,
213
- clampedActiveIndex: 0
214
- };
215
- }
216
- const clampedActiveIndex = Math.max(0, Math.min(totalCount - 1, activeIndex));
217
- const slots = [];
218
- const startIndex = Math.max(0, clampedActiveIndex - bufferSize);
219
- const endIndex = Math.min(totalCount - 1, clampedActiveIndex + bufferSize);
220
- const baseOffset = -clampedActiveIndex * containerHeight;
221
- const scrollOffset = baseOffset + dragOffset;
222
- for (let i = startIndex; i <= endIndex; i++) {
223
- const signedDistance = i - clampedActiveIndex;
224
- const distance = Math.abs(signedDistance);
225
- const isActive = i === clampedActiveIndex;
226
- const top = i * containerHeight;
227
- const transform = `translateY(${top + scrollOffset}px)`;
228
- let opacity = 1;
229
- if (!isActive && !isResizing) {
230
- opacity = isSwiping ? 0.7 : 0.3;
231
- }
232
- slots.push({
233
- index: i,
234
- top,
235
- isActive,
236
- distance,
237
- signedDistance,
238
- transform,
239
- opacity
240
- });
241
- }
242
- return {
243
- slots,
244
- scrollOffset,
245
- isAtStart: clampedActiveIndex === 0,
246
- isAtLastIndex: clampedActiveIndex >= totalCount - 1,
247
- clampedActiveIndex
248
- };
249
- }, [totalCount, activeIndex, containerHeight, dragOffset, isSwiping, isResizing, bufferSize]);
250
- }
251
- function getSlotIndexFromPosition(yPosition, containerHeight, totalCount) {
252
- if (totalCount === 0 || containerHeight === 0) {
253
- return 0;
254
- }
255
- const rawIndex = Math.round(-yPosition / containerHeight);
256
- return Math.max(0, Math.min(totalCount - 1, rawIndex));
257
- }
258
- function VideoFeedHeadless({
259
- feedState,
260
- swipeState,
261
- height,
262
- className,
263
- renderSlot,
264
- onIndexChange,
265
- onEndReached,
266
- endReachedThreshold = 2,
267
- bufferSize = 1,
268
- loadingText = "Loading...",
269
- emptyText = "No videos",
270
- endText = "You've reached the end",
271
- disableInlineTransforms = false
272
- }) {
273
- useInsertionEffect(() => {
274
- return injectComponentCSS("sv-video-feed", VIDEO_FEED_CSS);
275
- }, []);
276
- const containerRef = useRef(null);
277
- const [containerHeight, setContainerHeight] = useState(0);
278
- const [isResizing, setIsResizing] = useState(false);
279
- const resizeTimeoutRef = useRef(null);
280
- const prevHeightRef = useRef(0);
281
- useEffect(() => {
282
- const container = containerRef.current;
283
- if (!container) return;
284
- const updateHeight = () => {
285
- const newHeight = container.clientHeight;
286
- const heightChanged = newHeight !== prevHeightRef.current && prevHeightRef.current !== 0;
287
- if (heightChanged) {
288
- setIsResizing(true);
289
- if (resizeTimeoutRef.current) {
290
- clearTimeout(resizeTimeoutRef.current);
291
- }
292
- resizeTimeoutRef.current = setTimeout(() => {
293
- setIsResizing(false);
294
- }, 150);
295
- }
296
- prevHeightRef.current = newHeight;
297
- setContainerHeight(newHeight);
298
- };
299
- updateHeight();
300
- let cleanup;
301
- if (typeof ResizeObserver !== "undefined") {
302
- const observer = new ResizeObserver(updateHeight);
303
- observer.observe(container);
304
- cleanup = () => observer.disconnect();
305
- } else {
306
- window.addEventListener("resize", updateHeight);
307
- cleanup = () => window.removeEventListener("resize", updateHeight);
308
- }
309
- return () => {
310
- cleanup();
311
- if (resizeTimeoutRef.current) {
312
- clearTimeout(resizeTimeoutRef.current);
313
- }
314
- };
315
- }, []);
316
- const { videos, isLoading, hasMore } = feedState;
317
- const { activeIndex, isSwiping, dragOffset, goToIndex } = swipeState;
318
- const prevActiveIndexRef = useRef(activeIndex);
319
- useEffect(() => {
320
- if (prevActiveIndexRef.current !== activeIndex) {
321
- onIndexChange?.(activeIndex);
322
- prevActiveIndexRef.current = activeIndex;
323
- }
324
- }, [activeIndex, onIndexChange]);
325
- useEffect(() => {
326
- if (videos.length === 0) return;
327
- if (!hasMore || isLoading) return;
328
- const distanceToEnd = videos.length - 1 - activeIndex;
329
- if (distanceToEnd <= endReachedThreshold) {
330
- onEndReached?.();
331
- }
332
- }, [activeIndex, videos.length, hasMore, isLoading, endReachedThreshold, onEndReached]);
333
- const { slots, isAtLastIndex, clampedActiveIndex } = useVideoFeedPosition({
334
- totalCount: videos.length,
335
- activeIndex,
336
- containerHeight,
337
- dragOffset,
338
- isSwiping,
339
- isResizing,
340
- bufferSize
341
- });
342
- const contextValue = useMemo(
343
- () => ({
344
- videos,
345
- activeIndex: clampedActiveIndex,
346
- isSwiping,
347
- dragOffset,
348
- containerHeight,
349
- goToIndex,
350
- totalCount: videos.length
351
- }),
352
- [videos, clampedActiveIndex, isSwiping, dragOffset, containerHeight, goToIndex]
353
- );
354
- const renderSlots = useCallback(() => {
355
- return slots.map((slot) => {
356
- const video = videos[slot.index];
357
- if (!video) return null;
358
- const slotStyle = disableInlineTransforms ? { opacity: slot.opacity } : { transform: slot.transform, opacity: slot.opacity };
359
- return /* @__PURE__ */ jsx(
360
- "div",
361
- {
362
- className: cn(
363
- "sv-video-feed__slot",
364
- slot.isActive && "sv-video-feed__slot--active",
365
- !slot.isActive && "sv-video-feed__slot--adjacent",
366
- !isSwiping && "sv-video-feed__slot--transitioning"
367
- ),
368
- style: slotStyle,
369
- "data-slot-index": slot.index,
370
- "data-slot-active": slot.isActive,
371
- children: renderSlot(video, slot.index)
372
- },
373
- video.id
374
- );
375
- });
376
- }, [slots, videos, isSwiping, renderSlot, disableInlineTransforms]);
377
- const containerStyle = useMemo(() => {
378
- const style = {};
379
- if (height !== void 0) {
380
- style.height = typeof height === "number" ? `${height}px` : height;
381
- }
382
- return style;
383
- }, [height]);
384
- if (isLoading && videos.length === 0) {
385
- return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cn("sv-video-feed", className), style: containerStyle, children: /* @__PURE__ */ jsxs("div", { className: "sv-video-feed__loading", children: [
386
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__loading-spinner" }),
387
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__loading-text", children: loadingText })
388
- ] }) });
389
- }
390
- if (!isLoading && videos.length === 0) {
391
- return /* @__PURE__ */ jsx("div", { ref: containerRef, className: cn("sv-video-feed", className), style: containerStyle, children: /* @__PURE__ */ jsxs("div", { className: "sv-video-feed__empty", children: [
392
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__empty-icon", children: "\u{1F4ED}" }),
393
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__empty-text", children: emptyText })
394
- ] }) });
395
- }
396
- return /* @__PURE__ */ jsx(VideoFeedContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs("div", { ref: containerRef, className: cn("sv-video-feed", className), style: containerStyle, children: [
397
- /* @__PURE__ */ jsx(
398
- "div",
399
- {
400
- className: cn(
401
- "sv-video-feed__container",
402
- isSwiping ? "sv-video-feed__container--swiping" : "sv-video-feed__container--snapping"
403
- ),
404
- children: renderSlots()
405
- }
406
- ),
407
- isAtLastIndex && !hasMore && /* @__PURE__ */ jsx("div", { className: "sv-video-feed__end", "aria-live": "polite", children: endText })
408
- ] }) });
409
- }
410
-
411
- // src/components/VideoFeed/constants.ts
412
- var SLOT_INDEX_ATTR = "data-slot-index";
413
- var SLOT_ACTIVE_ATTR = "data-slot-active";
414
- var SLOT_INDEX_DATASET_KEY = "slotIndex";
415
- var SLOT_ACTIVE_DATASET_KEY = "slotActive";
416
- var VIDEO_FEED_CLASS_PREFIX = "sv-video-feed";
417
-
418
- export { SLOT_ACTIVE_ATTR, SLOT_ACTIVE_DATASET_KEY, SLOT_INDEX_ATTR, SLOT_INDEX_DATASET_KEY, VIDEO_FEED_CLASS_PREFIX, VIDEO_FEED_CSS, VideoFeedContext, VideoFeedHeadless, getSlotIndexFromPosition, useOptionalVideoFeedContext, useVideoFeedContext, useVideoFeedPosition };