@xhub-short/ui 0.1.0-beta.8 → 1.0.0-beta.18

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 (75) hide show
  1. package/dist/CommentSheet.css-DuBy01rU.d.ts +219 -0
  2. package/dist/VideoSlotPlayIndicator-DPs8Xt5C.d.ts +51 -0
  3. package/dist/chunk-3OB3OVYR.js +349 -0
  4. package/dist/chunk-4RIMQOBR.js +58 -0
  5. package/dist/chunk-5MKYDI4X.js +1 -0
  6. package/dist/chunk-5Y43HNRG.js +296 -0
  7. package/dist/chunk-7WXAQHJI.js +350 -0
  8. package/dist/chunk-BADA7OLG.js +564 -0
  9. package/dist/chunk-BEJAJFV6.js +191 -0
  10. package/dist/chunk-BNI7CYRI.js +334 -0
  11. package/dist/{chunk-UPCVSGWH.js → chunk-CAWE42LH.js} +5 -2
  12. package/dist/chunk-DGKMO3AE.js +717 -0
  13. package/dist/chunk-DR7KR7OT.js +103 -0
  14. package/dist/chunk-GSNIZ6DF.js +605 -0
  15. package/dist/chunk-HXQPEZRG.js +105 -0
  16. package/dist/chunk-IC2KUU4V.js +1264 -0
  17. package/dist/chunk-IWSBYOSS.js +91 -0
  18. package/dist/chunk-MFJS65C5.js +368 -0
  19. package/dist/{chunk-CXPNPSF7.js → chunk-NJXIYSDZ.js} +12 -1
  20. package/dist/{chunk-NRQXKZO3.js → chunk-OM4L7RE5.js} +12 -2
  21. package/dist/chunk-QCRRF76W.js +75 -0
  22. package/dist/chunk-QUEJHA24.js +508 -0
  23. package/dist/chunk-UYBQTE4M.js +337 -0
  24. package/dist/chunk-VJ744W5N.js +603 -0
  25. package/dist/chunk-VXW7AOGM.js +285 -0
  26. package/dist/{chunk-OQ7P5XC7.js → chunk-YB7AXTX7.js} +1 -1
  27. package/dist/components/ActionBar/index.d.ts +7 -2
  28. package/dist/components/ActionBar/index.js +1 -1
  29. package/dist/components/AdvanceMenu/index.d.ts +80 -0
  30. package/dist/components/AdvanceMenu/index.js +1 -0
  31. package/dist/components/ArticleSlot/index.d.ts +213 -0
  32. package/dist/components/ArticleSlot/index.js +1 -0
  33. package/dist/components/AuthorInfo/index.js +1 -1
  34. package/dist/components/BottomSheet/index.d.ts +87 -0
  35. package/dist/components/BottomSheet/index.js +1 -0
  36. package/dist/components/CleanModeOverlay/index.d.ts +60 -0
  37. package/dist/components/CleanModeOverlay/index.js +1 -0
  38. package/dist/components/CommentSheet/index.d.ts +155 -3
  39. package/dist/components/CommentSheet/index.js +1 -1
  40. package/dist/components/DetailView/index.d.ts +314 -0
  41. package/dist/components/DetailView/index.js +1 -0
  42. package/dist/components/ErrorBoundary/index.js +1 -1
  43. package/dist/components/{VideoFeed → Feed}/index.d.ts +64 -45
  44. package/dist/components/Feed/index.js +1 -0
  45. package/dist/components/ImageCarousel/index.d.ts +50 -0
  46. package/dist/components/ImageCarousel/index.js +1 -0
  47. package/dist/components/Playlist/index.d.ts +117 -0
  48. package/dist/components/Playlist/index.js +1 -0
  49. package/dist/components/ProgressBar/index.js +1 -1
  50. package/dist/components/QualityPicker/index.d.ts +35 -0
  51. package/dist/components/QualityPicker/index.js +1 -0
  52. package/dist/components/ReportSheet/index.d.ts +74 -0
  53. package/dist/components/ReportSheet/index.js +1 -0
  54. package/dist/components/Skeleton/index.js +1 -1
  55. package/dist/components/SpeedPicker/index.d.ts +32 -0
  56. package/dist/components/SpeedPicker/index.js +1 -0
  57. package/dist/components/VideoInfo/index.d.ts +7 -3
  58. package/dist/components/VideoInfo/index.js +1 -1
  59. package/dist/components/VideoPlayer/index.js +1 -1
  60. package/dist/components/VideoSlot/index.d.ts +31 -68
  61. package/dist/components/VideoSlot/index.js +2 -1
  62. package/dist/components/VirtualSlider/index.js +1 -1
  63. package/dist/index.d.ts +74 -11
  64. package/dist/index.js +39 -15
  65. package/package.json +8 -4
  66. package/dist/CommentSheet.css-BD6XbpU2.d.ts +0 -207
  67. package/dist/chunk-EBAMBI3O.js +0 -571
  68. package/dist/chunk-K3CETRCY.js +0 -737
  69. package/dist/chunk-PGHLVKXS.js +0 -148
  70. package/dist/chunk-QF7O26KZ.js +0 -357
  71. package/dist/chunk-RKS7YA7Z.js +0 -562
  72. package/dist/chunk-T4RQWGRY.js +0 -1519
  73. package/dist/chunk-UXNIXHII.js +0 -2040
  74. package/dist/chunk-YWAPI7JO.js +0 -204
  75. package/dist/components/VideoFeed/index.js +0 -1
@@ -0,0 +1,191 @@
1
+ import { VirtualSlider } from './chunk-YB7AXTX7.js';
2
+ import { clsx2 } from './chunk-EDWS2IPH.js';
3
+ import { injectComponentCSS } from './chunk-CAWE42LH.js';
4
+ import { createContext, useContext, useInsertionEffect, useRef, useMemo, useCallback } from 'react';
5
+ import { jsx, jsxs } from 'react/jsx-runtime';
6
+
7
+ // src/components/Feed/Feed.css.ts
8
+ var FEED_CSS = `.sv-feed{position:relative;width:100%;height:100%;overflow:hidden;background:var(--sv-bg-primary,#000);touch-action:pan-y;user-select:none;-webkit-user-select:none}.sv-feed__container{position:relative;width:100%;height:100%;will-change:transform}.sv-feed__container--snapping{transition:transform .3s cubic-bezier(.25,.46,.45,.94)}.sv-feed__container--swiping{transition:none}.sv-feed__slot{position:absolute;top:0;left:0;width:100%;height:100%;will-change:transform,opacity;backface-visibility:hidden;-webkit-backface-visibility:hidden}.sv-feed__slot--active{z-index:2}.sv-feed__slot--adjacent{z-index:1;pointer-events:none}.sv-feed__slot--transitioning{transition:transform .175s cubic-bezier(.25,.46,.45,.94),opacity .2s ease}.sv-feed__loading{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:var(--sv-spacing-md,16px);color:var(--sv-text-secondary,#999)}.sv-feed__loading-spinner{width:32px;height:32px;border:3px solid rgba(255,255,255,.2);border-top-color:var(--sv-color-primary,#fe2c55);border-radius:50%;animation:sv-feed-spinner .8s linear infinite}@keyframes sv-feed-spinner{to{transform:rotate(360deg)}}.sv-feed__loading-text{font-size:var(--sv-font-size-sm,13px);font-family:var(--sv-font-family,'Urbanist',sans-serif)}.sv-feed__empty{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;color:var(--sv-text-secondary,#999);font-family:var(--sv-font-family,'Urbanist',sans-serif)}.sv-feed__empty-icon{font-size:48px;margin-bottom:var(--sv-spacing-md,16px)}.sv-feed__empty-text{font-size:var(--sv-font-size-md,14px)}.sv-feed__end{position:absolute;bottom:calc(var(--sv-spacing-xl,32px)+env(safe-area-inset-bottom,0));left:50%;transform:translateX(-50%);z-index:10;padding:var(--sv-spacing-sm,8px)var(--sv-spacing-md,16px);background:rgba(0,0,0,.7);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-radius:var(--sv-border-radius-md,8px);color:var(--sv-text-secondary,#999);font-size:var(--sv-font-size-sm,13px);font-family:var(--sv-font-family,'Urbanist',sans-serif);pointer-events:none;animation:sv-feed-end-fade-in .3s ease-out}@keyframes sv-feed-end-fade-in{from{opacity:0;transform:translateX(-50%)translateY(10px)}to{opacity:1;transform:translateX(-50%)translateY(0)}}@media(prefers-reduced-motion:reduce){.sv-feed__container--snapping{transition:none}.sv-feed__loading-spinner{animation:none}}`;
9
+ var FeedContext = createContext(null);
10
+ function useFeedContext() {
11
+ const context = useContext(FeedContext);
12
+ if (!context) {
13
+ throw new Error("useFeedContext must be used within a Feed");
14
+ }
15
+ return context;
16
+ }
17
+ function useOptionalFeedContext() {
18
+ return useContext(FeedContext);
19
+ }
20
+ function FeedLoading({ text = "Loading..." }) {
21
+ return /* @__PURE__ */ jsxs("div", { className: "sv-feed__loading", children: [
22
+ /* @__PURE__ */ jsx("div", { className: "sv-feed__loading-spinner" }),
23
+ /* @__PURE__ */ jsx("div", { className: "sv-feed__loading-text", children: text })
24
+ ] });
25
+ }
26
+ function FeedEmpty({ text = "No content" }) {
27
+ return /* @__PURE__ */ jsxs("div", { className: "sv-feed__empty", children: [
28
+ /* @__PURE__ */ jsx("div", { className: "sv-feed__empty-icon", children: "\u{1F4ED}" }),
29
+ /* @__PURE__ */ jsx("div", { className: "sv-feed__empty-text", children: text })
30
+ ] });
31
+ }
32
+ function FeedEnd({ text = "You've reached the end" }) {
33
+ return /* @__PURE__ */ jsx("div", { className: "sv-feed__end", "aria-live": "polite", children: text });
34
+ }
35
+ function FeedHeadless({
36
+ feedState,
37
+ swipeState,
38
+ height,
39
+ className,
40
+ renderSlot,
41
+ onIndexChange,
42
+ onEndReached,
43
+ endReachedThreshold = 2,
44
+ bufferSize = 1,
45
+ renderEmpty,
46
+ renderLoading,
47
+ renderEnd,
48
+ loadingText = "Loading...",
49
+ emptyText = "No content",
50
+ endText = "You've reached the end",
51
+ disableInlineTransforms = false
52
+ }) {
53
+ useInsertionEffect(() => {
54
+ return injectComponentCSS("sv-feed", FEED_CSS);
55
+ }, []);
56
+ const { items, isLoading, hasMore } = feedState;
57
+ const { activeIndex, isSwiping, dragOffset, goToIndex } = swipeState;
58
+ const containerHeightRef = useRef(
59
+ typeof window !== "undefined" ? window.innerHeight : 800
60
+ );
61
+ const contextValue = useMemo(
62
+ () => ({
63
+ items,
64
+ videos: items,
65
+ // Backward compatibility
66
+ activeIndex: Math.max(0, Math.min(items.length - 1, activeIndex)),
67
+ isSwiping,
68
+ dragOffset,
69
+ containerHeight: containerHeightRef.current,
70
+ goToIndex,
71
+ totalCount: items.length
72
+ }),
73
+ [items, activeIndex, isSwiping, dragOffset, goToIndex]
74
+ );
75
+ const keyExtractor = useCallback((item) => item.id, []);
76
+ const renderItem = useCallback(
77
+ (item, index, isActive) => {
78
+ return renderSlot(item, index, isActive);
79
+ },
80
+ [renderSlot]
81
+ );
82
+ const loadingComponent = useMemo(
83
+ () => renderLoading?.(loadingText) || /* @__PURE__ */ jsx(FeedLoading, { text: loadingText }),
84
+ [loadingText]
85
+ );
86
+ const emptyComponent = useMemo(
87
+ () => renderEmpty?.(emptyText) || /* @__PURE__ */ jsx(FeedEmpty, { text: emptyText }),
88
+ [emptyText]
89
+ );
90
+ const endComponent = useMemo(() => renderEnd?.(endText) || /* @__PURE__ */ jsx(FeedEnd, { text: endText }), [endText]);
91
+ const sliderProps = {
92
+ // Required
93
+ items,
94
+ keyExtractor,
95
+ renderItem,
96
+ // Swipe state
97
+ activeIndex,
98
+ isSwiping,
99
+ dragOffset,
100
+ goToIndex,
101
+ // Callbacks
102
+ onIndexChange,
103
+ onEndReached,
104
+ endReachedThreshold,
105
+ bufferSize,
106
+ // Loading states
107
+ isLoading,
108
+ hasMore,
109
+ // Custom UI (video-specific styling)
110
+ loadingComponent,
111
+ emptyComponent,
112
+ endComponent,
113
+ // Styling - use sv-feed classes instead of sv-slider
114
+ height,
115
+ className: clsx2("sv-feed", className),
116
+ disableInlineTransforms
117
+ };
118
+ return /* @__PURE__ */ jsx(FeedContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(VirtualSlider, { ...sliderProps }) });
119
+ }
120
+ FeedHeadless.displayName = "FeedHeadless";
121
+ function useFeedPosition(options) {
122
+ const {
123
+ totalCount,
124
+ activeIndex,
125
+ containerHeight,
126
+ dragOffset,
127
+ isSwiping,
128
+ isResizing = false,
129
+ bufferSize = 1
130
+ } = options;
131
+ return useMemo(() => {
132
+ if (totalCount === 0) {
133
+ return {
134
+ slots: [],
135
+ scrollOffset: 0,
136
+ isAtStart: true,
137
+ isAtLastIndex: true,
138
+ clampedActiveIndex: 0
139
+ };
140
+ }
141
+ const clampedActiveIndex = Math.max(0, Math.min(totalCount - 1, activeIndex));
142
+ const slots = [];
143
+ const startIndex = Math.max(0, clampedActiveIndex - bufferSize);
144
+ const endIndex = Math.min(totalCount - 1, clampedActiveIndex + bufferSize);
145
+ const baseOffset = -clampedActiveIndex * containerHeight;
146
+ const scrollOffset = baseOffset + dragOffset;
147
+ for (let i = startIndex; i <= endIndex; i++) {
148
+ const signedDistance = i - clampedActiveIndex;
149
+ const distance = Math.abs(signedDistance);
150
+ const isActive = i === clampedActiveIndex;
151
+ const top = i * containerHeight;
152
+ const transform = `translateY(${top + scrollOffset}px)`;
153
+ let opacity = 1;
154
+ if (!isActive && !isResizing) {
155
+ opacity = isSwiping ? 0.7 : 0.3;
156
+ }
157
+ slots.push({
158
+ index: i,
159
+ top,
160
+ isActive,
161
+ distance,
162
+ signedDistance,
163
+ transform,
164
+ opacity
165
+ });
166
+ }
167
+ return {
168
+ slots,
169
+ scrollOffset,
170
+ isAtStart: clampedActiveIndex === 0,
171
+ isAtLastIndex: clampedActiveIndex >= totalCount - 1,
172
+ clampedActiveIndex
173
+ };
174
+ }, [totalCount, activeIndex, containerHeight, dragOffset, isSwiping, isResizing, bufferSize]);
175
+ }
176
+ function getSlotIndexFromPosition(yPosition, containerHeight, totalCount) {
177
+ if (totalCount === 0 || containerHeight === 0) {
178
+ return 0;
179
+ }
180
+ const rawIndex = Math.round(-yPosition / containerHeight);
181
+ return Math.max(0, Math.min(totalCount - 1, rawIndex));
182
+ }
183
+
184
+ // src/components/Feed/constants.ts
185
+ var SLOT_INDEX_ATTR = "data-slot-index";
186
+ var SLOT_ACTIVE_ATTR = "data-slot-active";
187
+ var SLOT_INDEX_DATASET_KEY = "slotIndex";
188
+ var SLOT_ACTIVE_DATASET_KEY = "slotActive";
189
+ var FEED_CLASS_PREFIX = "sv-feed";
190
+
191
+ export { FEED_CLASS_PREFIX, FEED_CSS, FeedContext, FeedHeadless, SLOT_ACTIVE_ATTR, SLOT_ACTIVE_DATASET_KEY, SLOT_INDEX_ATTR, SLOT_INDEX_DATASET_KEY, getSlotIndexFromPosition, useFeedContext, useFeedPosition, useOptionalFeedContext };
@@ -0,0 +1,334 @@
1
+ import { useDoubleTap, VideoSlotPlayIndicatorInner } from './chunk-MFJS65C5.js';
2
+ import { ImageCarouselHeadless } from './chunk-GSNIZ6DF.js';
3
+ import { clsx2 } from './chunk-EDWS2IPH.js';
4
+ import { injectComponentCSS } from './chunk-CAWE42LH.js';
5
+ import { createContext, memo, useInsertionEffect, useState, useRef, useCallback, useMemo, useContext } from 'react';
6
+ import { jsx, jsxs } from 'react/jsx-runtime';
7
+
8
+ // src/components/ArticleSlot/ArticleSlot.css.ts
9
+ var ARTICLE_SLOT_CSS = `.sv-article-slot{position:relative;width:100%;height:100%;overflow:hidden;background:var(--sv-article-bg,#000);touch-action:pan-y;user-select:none;-webkit-user-select:none}.sv-article-slot--active{z-index:1}.sv-article-slot__images{position:absolute;inset:0;display:flex;align-items:center;justify-content:center}.sv-article-slot__overlay{position:absolute;inset:0;pointer-events:none;display:flex;flex-direction:column;z-index:2}.sv-article-slot__overlay>*{pointer-events:auto}.sv-article-slot__gradient{position:absolute;left:0;right:0;pointer-events:none}.sv-article-slot__gradient--top{top:0;height:120px;background:linear-gradient(to bottom,rgba(0,0,0,.5)0%,rgba(0,0,0,.3)50%,transparent 100%)}.sv-article-slot__gradient--bottom{bottom:0;height:200px;background:linear-gradient(to top,rgba(0,0,0,.7)0%,rgba(0,0,0,.4)50%,transparent 100%)}.sv-article-slot__top-bar{position:absolute;top:0;left:0;right:0;padding:var(--sv-article-top-padding,16px);display:flex;justify-content:space-between;align-items:flex-start;z-index:10}.sv-article-slot__counter{padding:4px 10px;background:var(--sv-article-counter-bg,rgba(0,0,0,.6));border-radius:12px;font-size:var(--sv-article-counter-font-size,12px);font-weight:500;color:var(--sv-article-counter-color,#fff)}.sv-article-slot__badge{padding:4px 8px;background:var(--sv-article-badge-bg,rgba(255,255,255,.2));border-radius:4px;font-size:var(--sv-article-badge-font-size,11px);font-weight:500;color:var(--sv-article-badge-color,#fff);text-transform:uppercase;letter-spacing:.5px}.sv-article-slot__bottom{position:absolute;bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-playlist-bar-offset,0));left:0;right:var(--sv-article-action-bar-width,72px);padding:var(--sv-article-bottom-padding,16px);display:flex;flex-direction:column;gap:12px;z-index:5;transition:bottom .3s ease-in-out}.sv-article-slot__author{display:flex;align-items:center;gap:10px}.sv-article-slot__author-avatar{width:var(--sv-article-avatar-size,40px);height:var(--sv-article-avatar-size,40px);border-radius:50%;object-fit:cover;border:2px solid var(--sv-article-avatar-border,rgba(255,255,255,.3))}.sv-article-slot__author-name{font-size:var(--sv-article-author-font-size,15px);font-weight:600;color:var(--sv-article-author-color,#fff);text-shadow:0 1px 2px rgba(0,0,0,.5)}.sv-article-slot__caption{font-size:var(--sv-article-caption-font-size,14px);line-height:1.4;color:var(--sv-article-caption-color,#fff);text-shadow:0 1px 2px rgba(0,0,0,.5);display:-webkit-box;-webkit-line-clamp:var(--sv-article-caption-lines,2);-webkit-box-orient:vertical;overflow:hidden}.sv-article-slot__caption--expanded{-webkit-line-clamp:unset;overflow:visible}.sv-article-slot__read-more{display:inline-flex;align-items:center;gap:4px;padding:8px 16px;background:var(--sv-article-read-more-bg,rgba(255,255,255,.2));border:0;border-radius:20px;font-size:var(--sv-article-read-more-font-size,13px);font-weight:500;color:var(--sv-article-read-more-color,#fff);cursor:pointer;transition:background .2s ease;width:fit-content}.sv-article-slot__read-more:hover{background:var(--sv-article-read-more-hover-bg,rgba(255,255,255,.3))}.sv-article-slot__read-more svg{width:16px;height:16px}.sv-article-slot__actions{position:absolute;right:0;bottom:calc(var(--sv-safe-area-bottom,0)+var(--sv-playlist-bar-offset,0));width:var(--sv-article-action-bar-width,72px);padding:var(--sv-article-actions-padding,16px 8px);display:flex;flex-direction:column;align-items:center;gap:20px;z-index:5;transition:bottom .3s ease-in-out}.sv-article-slot__music{overflow:hidden;aspect-ratio:1/1;background:transparent;width:fit-content;height:fit-content;border:0}.sv-article-slot__music-cover{width:44px;height:44px;object-fit:cover;border-radius:999px;border:2px solid var(--sv-article-music-border,rgba(255,255,255,.3))}.sv-article-slot__music-cover--playing{animation:sv-music-rotate 4s linear infinite}@keyframes sv-music-rotate{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}.sv-article-slot__like-animation{position:absolute;inset:0;pointer-events:none;z-index:20}.sv-article-slot--clean-mode .sv-article-slot__overlay,.sv-article-slot--clean-mode .sv-article-slot__top-bar,.sv-article-slot--clean-mode .sv-article-slot__bottom,.sv-article-slot--clean-mode .sv-article-slot__actions,.sv-article-slot--clean-mode .sv-article-slot__gradient{opacity:0;transition:opacity .3s ease}.sv-article-slot--loading .sv-article-slot__skeleton{position:absolute;inset:0;background:linear-gradient(90deg,rgba(255,255,255,.05)25%,rgba(255,255,255,.1)50%,rgba(255,255,255,.05)75%);background-size:200% 100%;animation:sv-article-shimmer 1.5s infinite}@keyframes sv-article-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}.sv-article-slot:focus-visible{outline:2px solid var(--sv-article-focus-color,#4dabff);outline-offset:-2px}.sv-article-slot__sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}`;
10
+ var cssInjected = false;
11
+ function injectArticleSlotCSS() {
12
+ if (cssInjected) return void 0;
13
+ cssInjected = true;
14
+ return injectComponentCSS("article-slot", ARTICLE_SLOT_CSS);
15
+ }
16
+ var ARTICLE_SLOT_CLASS = "sv-article-slot";
17
+ var ARTICLE_SLOT_ACTIVE_CLASS = "sv-article-slot--active";
18
+ var ARTICLE_SLOT_CLEAN_MODE_CLASS = "sv-article-slot--clean-mode";
19
+ var ARTICLE_ID_ATTR = "data-article-id";
20
+ var ARTICLE_ACTIVE_ATTR = "data-active";
21
+ var ArticleSlotContext = createContext(null);
22
+ function useArticleSlotContext() {
23
+ const context = useContext(ArticleSlotContext);
24
+ if (!context) {
25
+ throw new Error("useArticleSlotContext must be used within ArticleSlotHeadless");
26
+ }
27
+ return context;
28
+ }
29
+ function useOptionalArticleSlotContext() {
30
+ return useContext(ArticleSlotContext);
31
+ }
32
+ var ANIMATION_SELECTOR = '[data-like-animation="true"]';
33
+ function ArticleSlotHeadlessBase({
34
+ article,
35
+ isActive = false,
36
+ currentImageIndex: controlledIndex,
37
+ onImageIndexChange,
38
+ music,
39
+ isMusicPlaying = false,
40
+ onToggleMusic,
41
+ lastMusicToggleTime: controlledToggleTime,
42
+ isCleanMode = false,
43
+ onTap,
44
+ onDoubleTap,
45
+ onLongPress,
46
+ onReadMoreClick,
47
+ disableTap = false,
48
+ enableZoom = false,
49
+ renderPlaceholder,
50
+ showCounter = true,
51
+ showIndicators = true,
52
+ className,
53
+ onSwipeDirectionChange,
54
+ children,
55
+ style
56
+ }) {
57
+ useInsertionEffect(() => {
58
+ return injectArticleSlotCSS();
59
+ }, []);
60
+ const [internalIndex, setInternalIndex] = useState(0);
61
+ const currentIndex = controlledIndex ?? internalIndex;
62
+ const [internalToggleTime, setInternalToggleTime] = useState(0);
63
+ const lastMusicToggleTime = controlledToggleTime ?? internalToggleTime;
64
+ const containerRef = useRef(null);
65
+ const totalImages = article.images.length;
66
+ const handleImageIndexChange = useCallback(
67
+ (index) => {
68
+ setInternalIndex(index);
69
+ onImageIndexChange?.(index);
70
+ },
71
+ [onImageIndexChange]
72
+ );
73
+ const goToImage = useCallback(
74
+ (index) => {
75
+ const clampedIndex = Math.max(0, Math.min(totalImages - 1, index));
76
+ handleImageIndexChange(clampedIndex);
77
+ },
78
+ [totalImages, handleImageIndexChange]
79
+ );
80
+ const nextImage = useCallback(() => {
81
+ if (currentIndex < totalImages - 1) {
82
+ goToImage(currentIndex + 1);
83
+ }
84
+ }, [currentIndex, totalImages, goToImage]);
85
+ const prevImage = useCallback(() => {
86
+ if (currentIndex > 0) {
87
+ goToImage(currentIndex - 1);
88
+ }
89
+ }, [currentIndex, goToImage]);
90
+ const toggleMusic = useCallback(() => {
91
+ if (controlledToggleTime === void 0) {
92
+ setInternalToggleTime(Date.now());
93
+ }
94
+ onToggleMusic?.();
95
+ }, [onToggleMusic, controlledToggleTime]);
96
+ const expandCaption = useCallback(() => {
97
+ onReadMoreClick?.();
98
+ }, [onReadMoreClick]);
99
+ const triggerLikeAnimation = useCallback((position) => {
100
+ const container = containerRef.current;
101
+ if (!container) return;
102
+ const animationEl = container.querySelector(ANIMATION_SELECTOR);
103
+ if (!animationEl) return;
104
+ const ref = animationEl.__likeAnimation;
105
+ if (ref?.triggerHeart) {
106
+ ref.triggerHeart(position.x, position.y);
107
+ }
108
+ }, []);
109
+ const { handlers: doubleTapHandlers } = useDoubleTap({
110
+ containerRef,
111
+ disabled: disableTap,
112
+ onSingleTap: onTap,
113
+ onDoubleTap: (position) => {
114
+ triggerLikeAnimation(position);
115
+ onDoubleTap?.();
116
+ },
117
+ onLongPress: onLongPress ? () => onLongPress() : void 0,
118
+ longPressDelay: 500
119
+ });
120
+ const contextValue = useMemo(
121
+ () => ({
122
+ article,
123
+ currentImageIndex: currentIndex,
124
+ totalImages,
125
+ isActive,
126
+ isCleanMode,
127
+ lastMusicToggleTime,
128
+ music,
129
+ isMusicPlaying,
130
+ toggleMusic,
131
+ goToImage,
132
+ nextImage,
133
+ prevImage,
134
+ expandCaption
135
+ }),
136
+ [
137
+ article,
138
+ currentIndex,
139
+ totalImages,
140
+ isActive,
141
+ isCleanMode,
142
+ lastMusicToggleTime,
143
+ music,
144
+ isMusicPlaying,
145
+ toggleMusic,
146
+ goToImage,
147
+ nextImage,
148
+ prevImage,
149
+ expandCaption
150
+ ]
151
+ );
152
+ const containerClassName = clsx2(
153
+ ARTICLE_SLOT_CLASS,
154
+ isActive && ARTICLE_SLOT_ACTIVE_CLASS,
155
+ isCleanMode && ARTICLE_SLOT_CLEAN_MODE_CLASS,
156
+ className
157
+ );
158
+ return /* @__PURE__ */ jsx(ArticleSlotContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
159
+ "div",
160
+ {
161
+ ref: containerRef,
162
+ className: containerClassName,
163
+ ...{ [ARTICLE_ID_ATTR]: article.id },
164
+ ...{ [ARTICLE_ACTIVE_ATTR]: isActive ? "true" : "false" },
165
+ ...doubleTapHandlers,
166
+ "aria-label": `Article by ${article.author?.name ?? "Unknown"}, ${totalImages} images`,
167
+ style,
168
+ children: [
169
+ /* @__PURE__ */ jsx("div", { className: "sv-article-slot__images", children: /* @__PURE__ */ jsx(
170
+ ImageCarouselHeadless,
171
+ {
172
+ images: article.images,
173
+ currentIndex,
174
+ onIndexChange: handleImageIndexChange,
175
+ showIndicators: showIndicators && totalImages > 1,
176
+ indicatorStyle: "dots",
177
+ enableZoom,
178
+ lazyLoad: true,
179
+ renderPlaceholder,
180
+ onSwipeDirectionChange
181
+ }
182
+ ) }),
183
+ /* @__PURE__ */ jsx("div", { className: "sv-article-slot__gradient sv-article-slot__gradient--top" }),
184
+ /* @__PURE__ */ jsx("div", { className: "sv-article-slot__gradient sv-article-slot__gradient--bottom" }),
185
+ showCounter && totalImages > 1 && /* @__PURE__ */ jsx("div", { className: "sv-article-slot__top-bar", children: /* @__PURE__ */ jsxs("div", { className: "sv-article-slot__counter", children: [
186
+ currentIndex + 1,
187
+ "/",
188
+ totalImages
189
+ ] }) }),
190
+ typeof children === "function" ? children(contextValue) : children
191
+ ]
192
+ }
193
+ ) });
194
+ }
195
+ var ArticleSlotHeadless = memo(ArticleSlotHeadlessBase);
196
+ ArticleSlotHeadless.displayName = "ArticleSlotHeadless";
197
+ function ArticleSlotOverlay({ children, className }) {
198
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-article-slot__overlay", className), children });
199
+ }
200
+ function ArticleSlotBottom({
201
+ children,
202
+ className,
203
+ onClick
204
+ }) {
205
+ return (
206
+ // biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
207
+ /* @__PURE__ */ jsx("div", { className: clsx2("sv-article-slot__bottom", className), onClick, children })
208
+ );
209
+ }
210
+ function ArticleSlotActions({ children, className }) {
211
+ return /* @__PURE__ */ jsx("div", { className: clsx2("sv-article-slot__actions", className), children });
212
+ }
213
+ function ArticleSlotCaption({
214
+ maxLines = 2,
215
+ expanded = false,
216
+ className
217
+ }) {
218
+ const { article } = useArticleSlotContext();
219
+ return /* @__PURE__ */ jsx(
220
+ "p",
221
+ {
222
+ className: clsx2(
223
+ "sv-article-slot__caption",
224
+ expanded && "sv-article-slot__caption--expanded",
225
+ className
226
+ ),
227
+ style: { WebkitLineClamp: expanded ? "unset" : maxLines },
228
+ children: article.caption
229
+ }
230
+ );
231
+ }
232
+ function ArticleSlotReadMore({
233
+ label = "Read More",
234
+ className,
235
+ onClick
236
+ }) {
237
+ const { expandCaption } = useArticleSlotContext();
238
+ const handleClick = useCallback(() => {
239
+ onClick?.() ?? expandCaption();
240
+ }, [onClick, expandCaption]);
241
+ return /* @__PURE__ */ jsxs(
242
+ "button",
243
+ {
244
+ type: "button",
245
+ className: clsx2("sv-article-slot__read-more", className),
246
+ onClick: handleClick,
247
+ children: [
248
+ label,
249
+ /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: /* @__PURE__ */ jsx("polyline", { points: "9 6 15 12 9 18" }) })
250
+ ]
251
+ }
252
+ );
253
+ }
254
+ function MusicCoverFallback({ className }) {
255
+ return /* @__PURE__ */ jsxs(
256
+ "svg",
257
+ {
258
+ viewBox: "0 0 48 48",
259
+ fill: "none",
260
+ xmlns: "http://www.w3.org/2000/svg",
261
+ className,
262
+ "aria-hidden": "true",
263
+ children: [
264
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "22", fill: "#1a1a1a", stroke: "#333", strokeWidth: "2" }),
265
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "18", fill: "none", stroke: "#2a2a2a", strokeWidth: "1" }),
266
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "14", fill: "none", stroke: "#2a2a2a", strokeWidth: "1" }),
267
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "10", fill: "none", stroke: "#2a2a2a", strokeWidth: "1" }),
268
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "6", fill: "#ff4757" }),
269
+ /* @__PURE__ */ jsx("circle", { cx: "24", cy: "24", r: "2", fill: "#1a1a1a" }),
270
+ /* @__PURE__ */ jsx(
271
+ "path",
272
+ {
273
+ d: "M26 21v5.5c0 1.38-1.12 2.5-2.5 2.5S21 27.88 21 26.5s1.12-2.5 2.5-2.5c.41 0 .79.1 1.13.27V21h1.37z",
274
+ fill: "white"
275
+ }
276
+ )
277
+ ]
278
+ }
279
+ );
280
+ }
281
+ function ArticleSlotMusic({
282
+ className,
283
+ onClick,
284
+ fallbackCover
285
+ }) {
286
+ const { music, isMusicPlaying } = useArticleSlotContext();
287
+ const [hasError, setHasError] = useState(false);
288
+ if (!music) return null;
289
+ const coverClassName = clsx2(
290
+ "sv-article-slot__music-cover",
291
+ isMusicPlaying && "sv-article-slot__music-cover--playing"
292
+ );
293
+ const renderFallback = () => {
294
+ if (fallbackCover) {
295
+ return /* @__PURE__ */ jsx("span", { className: coverClassName, children: fallbackCover });
296
+ }
297
+ return /* @__PURE__ */ jsx(MusicCoverFallback, { className: coverClassName });
298
+ };
299
+ return /* @__PURE__ */ jsx(
300
+ "button",
301
+ {
302
+ type: "button",
303
+ className: clsx2("sv-article-slot__music", className),
304
+ onClick: (e) => {
305
+ e.stopPropagation();
306
+ onClick?.();
307
+ },
308
+ children: music.cover && !hasError ? /* @__PURE__ */ jsx(
309
+ "img",
310
+ {
311
+ src: music.cover,
312
+ alt: "",
313
+ className: coverClassName,
314
+ onError: () => setHasError(true)
315
+ }
316
+ ) : renderFallback()
317
+ }
318
+ );
319
+ }
320
+ function ArticleSlotPlayIndicator(props) {
321
+ const context = useOptionalArticleSlotContext();
322
+ if (!context || !context.music) return null;
323
+ return /* @__PURE__ */ jsx(
324
+ VideoSlotPlayIndicatorInner,
325
+ {
326
+ ...props,
327
+ lastUserToggleTime: context.lastMusicToggleTime,
328
+ isPlaying: context.isMusicPlaying
329
+ }
330
+ );
331
+ }
332
+ ArticleSlotPlayIndicator.displayName = "ArticleSlotPlayIndicator";
333
+
334
+ export { ARTICLE_ACTIVE_ATTR, ARTICLE_ID_ATTR, ARTICLE_SLOT_ACTIVE_CLASS, ARTICLE_SLOT_CLASS, ARTICLE_SLOT_CLEAN_MODE_CLASS, ARTICLE_SLOT_CSS, ArticleSlotActions, ArticleSlotBottom, ArticleSlotCaption, ArticleSlotHeadless, ArticleSlotMusic, ArticleSlotOverlay, ArticleSlotPlayIndicator, ArticleSlotReadMore, useArticleSlotContext, useOptionalArticleSlotContext };
@@ -1,7 +1,8 @@
1
1
  // src/utils/injectComponentCSS.ts
2
2
  var injectedStyles = /* @__PURE__ */ new Set();
3
3
  var STYLE_ID_PREFIX = "sv-styles-";
4
- function injectComponentCSS(componentId, css) {
4
+ function injectComponentCSS(componentId, css, options = {}) {
5
+ const { layered = true } = options;
5
6
  if (typeof document === "undefined") {
6
7
  return () => {
7
8
  };
@@ -20,7 +21,9 @@ function injectComponentCSS(componentId, css) {
20
21
  const style = document.createElement("style");
21
22
  style.id = styleId;
22
23
  style.setAttribute("data-sv-component", componentId);
23
- style.textContent = css;
24
+ style.textContent = layered ? `@layer sv {
25
+ ${css}
26
+ }` : css;
24
27
  document.head.appendChild(style);
25
28
  injectedStyles.add(componentId);
26
29
  return () => {