1- import React , { memo , useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
1+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
22import {
33 ActivityIndicator ,
44 Animated ,
55 Dimensions ,
66 NativeScrollEvent ,
77 NativeSyntheticEvent ,
8- ScrollView ,
98 StyleSheet ,
109 Text ,
11- TouchableOpacity ,
1210 View ,
1311} from 'react-native' ;
14- import { GlassView } from 'expo-glass-effect' ;
1512
16- // Background colors for glass effect based on theme
17- const GLASS_BG_DARK = 'rgba(255, 255, 255, 0.01)' ;
18- const GLASS_BG_LIGHT = 'rgba(0, 0, 0, 0.01)' ;
13+ // Background colors for glass effect - exported for parent to use
14+ export const GLASS_BG_DARK = 'rgba(255, 255, 255, 0.01)' ;
15+ export const GLASS_BG_LIGHT = 'rgba(0, 0, 0, 0.01)' ;
1916
2017// Excel date serial number range (1900-2099)
2118const MIN_DATE_SERIAL = 1 ;
@@ -49,32 +46,6 @@ const getTextAlign = (ht?: number): 'left' | 'center' | 'right' => {
4946 }
5047} ;
5148
52- /**
53- * Memoized glass button for sheet tabs.
54- * Prevents re-render on parent state changes to preserve iOS liquid glass effect.
55- */
56- const GlassTabButton = memo ( function GlassTabButton ( {
57- name,
58- isDark,
59- textColor,
60- } : {
61- name : string ;
62- isDark : boolean ;
63- textColor : string ;
64- } ) {
65- return (
66- < GlassView
67- glassEffectStyle = "regular"
68- style = { [ styles . glassTab , { backgroundColor : isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT } ] }
69- pointerEvents = "none"
70- >
71- < View style = { styles . tabInner } >
72- < Text style = { [ styles . tabText , { color : textColor } ] } > { name } </ Text >
73- </ View >
74- </ GlassView >
75- ) ;
76- } ) ;
77-
7849// Types based on Univer's data structure
7950interface ICellData {
8051 v ?: string | number | boolean | null ;
@@ -136,6 +107,19 @@ interface IWorkbookData {
136107 styles ?: { [ styleId : string ] : IStyleData | null } ;
137108}
138109
110+ export interface SheetControlsData {
111+ zoom : number ;
112+ minZoom : number ;
113+ maxZoom : number ;
114+ sheetNames : string [ ] ;
115+ activeSheetIndex : number ;
116+ hasMultipleSheets : boolean ;
117+ tabBarHeight : number ;
118+ onZoomIn : ( ) => void ;
119+ onZoomOut : ( ) => void ;
120+ onSelectSheet : ( index : number ) => void ;
121+ }
122+
139123interface NativeSheetsViewerProps {
140124 content : string ;
141125 theme : {
@@ -151,6 +135,8 @@ interface NativeSheetsViewerProps {
151135 onLoaded ?: ( ) => void ;
152136 hideLoadingOverlay ?: boolean ;
153137 bottomInset ?: number ;
138+ /** Callback to provide sheet controls data to parent for rendering outside ScrollView */
139+ onControlsReady ?: ( controls : SheetControlsData ) => void ;
154140}
155141
156142// Constants - match typical Excel/Sheets defaults (these are only used as last resort)
@@ -280,6 +266,7 @@ export function NativeSheetsViewer({
280266 onLoaded,
281267 hideLoadingOverlay,
282268 bottomInset = 0 ,
269+ onControlsReady,
283270} : NativeSheetsViewerProps ) {
284271 const [ loading , setLoading ] = useState ( true ) ;
285272 const [ activeSheetIndex , setActiveSheetIndex ] = useState ( 0 ) ;
@@ -453,6 +440,19 @@ export function NativeSheetsViewer({
453440 return label ;
454441 } ;
455442
443+ // Zoom control handlers
444+ const handleZoomIn = useCallback ( ( ) => {
445+ setZoom ( z => Math . min ( MAX_ZOOM , z + 0.25 ) ) ;
446+ } , [ ] ) ;
447+
448+ const handleZoomOut = useCallback ( ( ) => {
449+ setZoom ( z => Math . max ( MIN_ZOOM , z - 0.25 ) ) ;
450+ } , [ ] ) ;
451+
452+ const handleSelectSheet = useCallback ( ( index : number ) => {
453+ setActiveSheetIndex ( index ) ;
454+ } , [ ] ) ;
455+
456456 useEffect ( ( ) => {
457457 if ( currentSheet ) {
458458 setLoading ( false ) ;
@@ -466,6 +466,24 @@ export function NativeSheetsViewer({
466466 const cellBg = theme . colors . background ;
467467 const tabBarHeight = hasMultipleSheets ? 48 + bottomInset : 0 ;
468468
469+ // Notify parent about controls data so it can render glass controls outside ScrollView
470+ useEffect ( ( ) => {
471+ if ( currentSheet && onControlsReady ) {
472+ onControlsReady ( {
473+ zoom,
474+ minZoom : MIN_ZOOM ,
475+ maxZoom : MAX_ZOOM ,
476+ sheetNames : sheetInfo . names ,
477+ activeSheetIndex,
478+ hasMultipleSheets,
479+ tabBarHeight,
480+ onZoomIn : handleZoomIn ,
481+ onZoomOut : handleZoomOut ,
482+ onSelectSheet : handleSelectSheet ,
483+ } ) ;
484+ }
485+ } , [ currentSheet , onControlsReady , zoom , sheetInfo . names , activeSheetIndex , hasMultipleSheets , tabBarHeight , handleZoomIn , handleZoomOut , handleSelectSheet ] ) ;
486+
469487 if ( ! workbook || ! currentSheet ) {
470488 return (
471489 < View style = { [ styles . container , { backgroundColor : cellBg } ] } >
@@ -833,72 +851,7 @@ export function NativeSheetsViewer({
833851 </ Animated . ScrollView >
834852 </ View >
835853
836- { /* Zoom controls */ }
837- < View style = { [ styles . zoomControls , { bottom : tabBarHeight + 16 } ] } >
838- < TouchableOpacity onPress = { ( ) => setZoom ( z => Math . max ( MIN_ZOOM , z - 0.25 ) ) } >
839- < GlassView
840- glassEffectStyle = "regular"
841- style = { [ styles . glassButton , { backgroundColor : theme . isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT } ] }
842- pointerEvents = "none"
843- >
844- < View style = { styles . zoomButtonInner } >
845- < Text style = { [ styles . zoomButtonText , { color : theme . colors . foreground } ] } > −</ Text >
846- </ View >
847- </ GlassView >
848- </ TouchableOpacity >
849- < GlassView
850- glassEffectStyle = "regular"
851- style = { [ styles . glassLabelContainer , { backgroundColor : theme . isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT } ] }
852- >
853- < View style = { styles . zoomLabelInner } >
854- < Text style = { [ styles . zoomLabel , { color : theme . colors . foreground } ] } >
855- { Math . round ( zoom * 100 ) } %
856- </ Text >
857- </ View >
858- </ GlassView >
859- < TouchableOpacity onPress = { ( ) => setZoom ( z => Math . min ( MAX_ZOOM , z + 0.25 ) ) } >
860- < GlassView
861- glassEffectStyle = "regular"
862- style = { [ styles . glassButton , { backgroundColor : theme . isDark ? GLASS_BG_DARK : GLASS_BG_LIGHT } ] }
863- pointerEvents = "none"
864- >
865- < View style = { styles . zoomButtonInner } >
866- < Text style = { [ styles . zoomButtonText , { color : theme . colors . foreground } ] } > +</ Text >
867- </ View >
868- </ GlassView >
869- </ TouchableOpacity >
870- </ View >
871-
872-
873- { /* Sheet tabs */ }
874- { hasMultipleSheets && (
875- < View
876- style = { [
877- styles . tabBar ,
878- {
879- backgroundColor : cellBg ,
880- borderTopColor : borderColor ,
881- paddingBottom : bottomInset ,
882- } ,
883- ] }
884- >
885- < ScrollView
886- horizontal
887- showsHorizontalScrollIndicator = { false }
888- contentContainerStyle = { styles . tabScrollContent }
889- >
890- { sheetInfo . names . map ( ( name , index ) => (
891- < TouchableOpacity
892- key = { `sheet-tab-${ name } ` }
893- onPress = { ( ) => setActiveSheetIndex ( index ) }
894- style = { { opacity : index === activeSheetIndex ? 1 : 0.6 } }
895- >
896- < GlassTabButton name = { name } isDark = { theme . isDark } textColor = { theme . colors . foreground } />
897- </ TouchableOpacity >
898- ) ) }
899- </ ScrollView >
900- </ View >
901- ) }
854+ { /* Glass controls (zoom, tabs) are rendered by parent outside ScrollView for proper liquid glass */ }
902855 </ View >
903856 ) ;
904857}
@@ -963,70 +916,4 @@ const styles = StyleSheet.create({
963916 headerText : {
964917 fontWeight : '500' ,
965918 } ,
966- zoomControls : {
967- position : 'absolute' ,
968- right : 16 ,
969- flexDirection : 'row' ,
970- alignItems : 'center' ,
971- zIndex : 20 ,
972- gap : 8 ,
973- } ,
974- glassButton : {
975- borderRadius : 19 ,
976- overflow : 'hidden' ,
977- } ,
978- glassLabelContainer : {
979- borderRadius : 19 ,
980- overflow : 'hidden' ,
981- } ,
982- zoomButtonInner : {
983- width : 38 ,
984- height : 38 ,
985- alignItems : 'center' ,
986- justifyContent : 'center' ,
987- } ,
988- zoomButtonText : {
989- fontSize : 22 ,
990- fontWeight : '500' ,
991- } ,
992- zoomLabelInner : {
993- height : 38 ,
994- paddingHorizontal : 12 ,
995- alignItems : 'center' ,
996- justifyContent : 'center' ,
997- } ,
998- zoomLabel : {
999- fontSize : 15 ,
1000- fontWeight : '600' ,
1001- minWidth : 44 ,
1002- textAlign : 'center' ,
1003- } ,
1004- tabBar : {
1005- position : 'absolute' ,
1006- bottom : 0 ,
1007- left : 0 ,
1008- right : 0 ,
1009- flexDirection : 'row' ,
1010- borderTopWidth : StyleSheet . hairlineWidth ,
1011- paddingTop : 10 ,
1012- paddingHorizontal : 8 ,
1013- } ,
1014- tabScrollContent : {
1015- gap : 8 ,
1016- paddingHorizontal : 8 ,
1017- } ,
1018- glassTab : {
1019- borderRadius : 19 ,
1020- overflow : 'hidden' ,
1021- } ,
1022- tabInner : {
1023- height : 38 ,
1024- paddingHorizontal : 16 ,
1025- justifyContent : 'center' ,
1026- alignItems : 'center' ,
1027- } ,
1028- tabText : {
1029- fontSize : 15 ,
1030- fontWeight : '500' ,
1031- } ,
1032919} ) ;
0 commit comments