unified-video-framework 1.4.343 → 1.4.344

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.
@@ -1,197 +0,0 @@
1
- import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
2
- import type {
3
- FlashNewsTickerConfig,
4
- FlashNewsTickerItem,
5
- FlashNewsTickerAPI,
6
- } from '../types/FlashNewsTickerTypes';
7
-
8
- interface Props {
9
- config: FlashNewsTickerConfig;
10
- onAPIReady?: (api: FlashNewsTickerAPI) => void;
11
- }
12
-
13
- export const FlashNewsTicker: React.FC<Props> = ({ config, onAPIReady }) => {
14
- // State
15
- const [visible, setVisible] = useState(config.enabled ?? true);
16
- const [items, setItems] = useState<FlashNewsTickerItem[]>(config.items || []);
17
- const [isPaused, setIsPaused] = useState(false);
18
- const [contentWidth, setContentWidth] = useState(0);
19
-
20
- // Refs
21
- const contentRef = useRef<HTMLDivElement>(null);
22
- const apiRef = useRef<FlashNewsTickerAPI | null>(null);
23
- const resizeObserverRef = useRef<ResizeObserver | null>(null);
24
-
25
- // Merge config with defaults
26
- const mergedConfig = useMemo(
27
- () => ({
28
- position: 'bottom' as 'top' | 'bottom' | 'both',
29
- height: 40,
30
- backgroundColor: 'rgba(0, 0, 0, 0.7)',
31
- textColor: '#ffffff',
32
- fontSize: 14,
33
- fontWeight: 600,
34
- speed: 50,
35
- gap: 100,
36
- loop: true,
37
- separator: ' • ',
38
- topOffset: 10,
39
- bottomOffset: 10,
40
- ...config,
41
- }),
42
- [config]
43
- );
44
-
45
- // Calculate animation duration based on content width
46
- const animationDuration = useMemo(() => {
47
- return contentWidth > 0 ? contentWidth / mergedConfig.speed : 20;
48
- }, [contentWidth, mergedConfig.speed]);
49
-
50
- // Filter active items by time range and priority
51
- const activeItems = useMemo(() => {
52
- const now = Date.now();
53
- return items
54
- .filter((item) => {
55
- if (item.startTime && now < item.startTime) return false;
56
- if (item.endTime && now > item.endTime) return false;
57
- return true;
58
- })
59
- .sort((a, b) => (b.priority || 0) - (a.priority || 0));
60
- }, [items]);
61
-
62
- // Measure content width when items change
63
- useEffect(() => {
64
- if (!contentRef.current) return;
65
-
66
- const measureWidth = () => {
67
- if (contentRef.current) {
68
- setContentWidth(contentRef.current.scrollWidth);
69
- }
70
- };
71
-
72
- // Initial measurement
73
- measureWidth();
74
-
75
- // Set up ResizeObserver for dynamic resizing
76
- if (typeof ResizeObserver !== 'undefined') {
77
- resizeObserverRef.current = new ResizeObserver(() => {
78
- measureWidth();
79
- });
80
- resizeObserverRef.current.observe(contentRef.current);
81
- }
82
-
83
- return () => {
84
- if (resizeObserverRef.current) {
85
- resizeObserverRef.current.disconnect();
86
- }
87
- };
88
- }, [activeItems]);
89
-
90
- // Update items when config changes
91
- useEffect(() => {
92
- setItems(config.items || []);
93
- }, [config.items]);
94
-
95
- // Create and expose API
96
- useEffect(() => {
97
- const api: FlashNewsTickerAPI = {
98
- show: () => setVisible(true),
99
- hide: () => setVisible(false),
100
- isVisible: () => visible,
101
- updateItems: (newItems) => setItems(newItems),
102
- addItem: (item) => setItems((prev) => [...prev, item]),
103
- removeItem: (id) => setItems((prev) => prev.filter((i) => i.id !== id)),
104
- clearItems: () => setItems([]),
105
- updateConfig: () => {
106
- // Config is controlled via props, no local update needed
107
- },
108
- pause: () => setIsPaused(true),
109
- resume: () => setIsPaused(false),
110
- isPaused: () => isPaused,
111
- };
112
-
113
- apiRef.current = api;
114
- onAPIReady?.(api);
115
- }, [visible, isPaused, onAPIReady]);
116
-
117
- // Don't render if hidden or no items
118
- if (!visible || activeItems.length === 0) return null;
119
-
120
- // Responsive adjustments
121
- const isMobile = typeof window !== 'undefined' && window.innerWidth <= 768;
122
- const height = isMobile ? 32 : mergedConfig.height;
123
- const fontSize = isMobile ? 12 : mergedConfig.fontSize;
124
-
125
- // Position styles - overlay on video, just above where controls appear
126
- const positionStyles =
127
- mergedConfig.position === 'top'
128
- ? { top: mergedConfig.topOffset }
129
- : { bottom: mergedConfig.bottomOffset || 10 };
130
-
131
- return (
132
- <>
133
- <div
134
- className={`uvf-flash-news-ticker ticker-${mergedConfig.position}`}
135
- style={{
136
- position: 'fixed',
137
- left: 0,
138
- right: 0,
139
- height: `${height}px`,
140
- backgroundColor: mergedConfig.backgroundColor,
141
- zIndex: 90,
142
- overflow: 'hidden',
143
- pointerEvents: 'none',
144
- display: 'flex',
145
- alignItems: 'center',
146
- ...positionStyles,
147
- }}
148
- >
149
- <div
150
- ref={contentRef}
151
- style={{
152
- display: 'flex',
153
- whiteSpace: 'nowrap',
154
- animation:
155
- isPaused || !mergedConfig.loop
156
- ? 'none'
157
- : `ticker-scroll ${animationDuration}s linear infinite`,
158
- willChange: 'transform',
159
- }}
160
- >
161
- {/* Render items twice for seamless loop */}
162
- {[...activeItems, ...activeItems].map((item, idx) => (
163
- <span
164
- key={`${item.id}-${idx}`}
165
- style={{
166
- color: mergedConfig.textColor,
167
- fontSize: `${fontSize}px`,
168
- fontWeight: mergedConfig.fontWeight,
169
- marginRight: `${mergedConfig.gap}px`,
170
- }}
171
- >
172
- {item.text}
173
- {mergedConfig.separator && idx < activeItems.length * 2 - 1 && (
174
- <span style={{ opacity: 0.5, margin: '0 8px' }}>
175
- {mergedConfig.separator}
176
- </span>
177
- )}
178
- </span>
179
- ))}
180
- </div>
181
- </div>
182
-
183
- <style>{`
184
- @keyframes ticker-scroll {
185
- 0% {
186
- transform: translateX(0);
187
- }
188
- 100% {
189
- transform: translateX(-50%);
190
- }
191
- }
192
- `}</style>
193
- </>
194
- );
195
- };
196
-
197
- export default FlashNewsTicker;