186186 <div
187187 ref =" gridScrollContainer"
188188 class =" flex-1 overflow-y-auto p-1.5 sm:p-3"
189- @scroll =" handleScroll"
190189 >
191190 <div class =" grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-1.5 sm:gap-2.5" >
192191 <div
193192 v-for =" item in paginatedItems"
194193 :key =" item.item_code"
195194 v-memo =" [item.item_code, item.actual_qty, item.stock_qty, item.rate, item.price_list_rate]"
196- @click =" handleItemClick(item)"
195+ @touchstart.passive =" getOptimizedClickHandler(item).touchstart"
196+ @touchmove.passive =" getOptimizedClickHandler(item).touchmove"
197+ @touchend.passive =" getOptimizedClickHandler(item).touchend"
198+ @click =" getOptimizedClickHandler(item).click"
197199 class =" relative bg-white border border-gray-200 rounded-lg p-1.5 sm:p-2.5 cursor-pointer hover:border-blue-400 hover:shadow-md transition-[border-color,box-shadow] duration-100 touch-manipulation"
198200 >
199201 <!-- Stock Badge - Positioned at top right of card -->
345347 <div
346348 ref =" listScrollContainer"
347349 class =" flex-1 overflow-x-auto overflow-y-auto"
348- @scroll =" handleScroll"
349350 >
350351 <table class =" min-w-full divide-y divide-gray-200" >
351352 <thead class =" bg-gray-50 sticky top-0 z-0" >
@@ -491,6 +492,12 @@ import { formatCurrency as formatCurrencyUtil } from "@/utils/currency"
491492import { toast } from " frappe-ui"
492493import { storeToRefs } from " pinia"
493494import { computed , onMounted , onUnmounted , ref , watch } from " vue"
495+ import {
496+ createOptimizedClickHandler ,
497+ throttleRAF ,
498+ addPassiveListener ,
499+ runWhenIdle
500+ } from " @/utils/lowEndOptimizations"
494501
495502const props = defineProps ({
496503 posProfile: String ,
@@ -648,36 +655,31 @@ watch(
648655let scrollTimeout = null
649656const SCROLL_THROTTLE_MS = 100
650657
651- // Infinite scroll handler with throttling
652- function handleScroll (event ) {
653- // Clear existing timeout
654- if (scrollTimeout) {
655- clearTimeout (scrollTimeout)
656- }
657-
658- // Throttle scroll handling
659- scrollTimeout = setTimeout (() => {
660- const container = event .target
661- const scrollPosition = container .scrollTop + container .clientHeight
662- const scrollHeight = container .scrollHeight
658+ // Optimized scroll handler using RAF throttling
659+ const handleScrollRAF = throttleRAF ((event ) => {
660+ const container = event .target
661+ const scrollPosition = container .scrollTop + container .clientHeight
662+ const scrollHeight = container .scrollHeight
663+ const threshold = 200
663664
664- // Load more when user is within 200px of the bottom
665- const threshold = 200
665+ const isSearching = searchTerm .value && searchTerm .value .trim ().length > 0
666666
667- // Only trigger infinite scroll when browsing (not searching)
668- // Search shows all results immediately from server
669- const isSearching = searchTerm .value && searchTerm .value .trim ().length > 0
670-
671- if (
672- ! isSearching &&
673- scrollHeight - scrollPosition < threshold &&
674- hasMore .value &&
675- ! loadingMore .value &&
676- ! loading .value
677- ) {
667+ if (
668+ ! isSearching &&
669+ scrollHeight - scrollPosition < threshold &&
670+ hasMore .value &&
671+ ! loadingMore .value &&
672+ ! loading .value
673+ ) {
674+ // Use runWhenIdle to load more items without blocking scroll
675+ runWhenIdle (() => {
678676 itemStore .loadMoreItems ()
679- }
680- }, SCROLL_THROTTLE_MS )
677+ }, { timeout: 1000 })
678+ }
679+ })
680+
681+ function handleScroll (event ) {
682+ handleScrollRAF (event )
681683}
682684
683685onMounted (() => {
@@ -686,8 +688,31 @@ onMounted(() => {
686688 itemStore .loadItemGroups ()
687689 }
688690
689- // Attach scroll listeners for infinite scroll
690- // Note: We'll use onScroll event on the containers directly in template
691+ // Add passive scroll listeners for better performance
692+ const cleanupFns = []
693+
694+ if (gridScrollContainer .value ) {
695+ const cleanup = addPassiveListener (
696+ gridScrollContainer .value ,
697+ ' scroll' ,
698+ handleScroll,
699+ { passive: true }
700+ )
701+ cleanupFns .push (cleanup)
702+ }
703+
704+ if (listScrollContainer .value ) {
705+ const cleanup = addPassiveListener (
706+ listScrollContainer .value ,
707+ ' scroll' ,
708+ handleScroll,
709+ { passive: true }
710+ )
711+ cleanupFns .push (cleanup)
712+ }
713+
714+ // Store cleanup functions for onUnmounted
715+ window .__scrollCleanup = cleanupFns
691716})
692717
693718onUnmounted (() => {
@@ -699,6 +724,15 @@ onUnmounted(() => {
699724 clearTimeout (scrollTimeout)
700725 scrollTimeout = null
701726 }
727+
728+ // Cleanup passive listeners
729+ if (window .__scrollCleanup ) {
730+ window .__scrollCleanup .forEach (cleanup => cleanup ())
731+ delete window .__scrollCleanup
732+ }
733+
734+ // Clear optimized click handlers
735+ optimizedClickHandlers .clear ()
702736})
703737
704738// Handle keydown for barcode scanner detection
@@ -770,6 +804,23 @@ function handleSearchInput(event) {
770804 }
771805}
772806
807+ // Create optimized click handlers for better touch response
808+ const optimizedClickHandlers = new Map ()
809+
810+ function getOptimizedClickHandler (item ) {
811+ const key = item .item_code
812+ if (! optimizedClickHandlers .has (key)) {
813+ const handler = createOptimizedClickHandler (() => {
814+ handleItemClick (item)
815+ }, {
816+ feedback: true ,
817+ haptic: true
818+ })
819+ optimizedClickHandlers .set (key, handler)
820+ }
821+ return optimizedClickHandlers .get (key)
822+ }
823+
773824function handleItemClick (item ) {
774825 emit (" item-selected" , item)
775826}
@@ -1045,4 +1096,36 @@ function getStockStatus(qty) {
10451096 - ms- overflow- style: none; /* IE and Edge */
10461097 scrollbar- width: none; /* Firefox */
10471098}
1099+
1100+ /* Performance optimizations for low-end devices */
1101+ [class *= " grid-cols-" ] > div {
1102+ /* Tell browser which properties will change */
1103+ will- change: opacity;
1104+ /* Use GPU acceleration for transforms */
1105+ transform: translateZ (0 );
1106+ /* Optimize for speed over quality */
1107+ backface- visibility: hidden;
1108+ }
1109+
1110+ /* Optimize scroll containers */
1111+ .overflow - y- auto, .overflow - x- auto {
1112+ /* Enable smooth scrolling with GPU acceleration */
1113+ - webkit- overflow- scrolling: touch;
1114+ /* Create stacking context for better compositing */
1115+ transform: translateZ (0 );
1116+ will- change: scroll- position;
1117+ }
1118+
1119+ /* Reduce paint areas */
1120+ .relative {
1121+ /* Isolate paint regions */
1122+ isolation: isolate;
1123+ }
1124+
1125+ /* Optimize images */
1126+ img {
1127+ /* Use browser's image optimization */
1128+ image- rendering: - webkit- optimize- contrast;
1129+ image- rendering: crisp- edges;
1130+ }
10481131< / style>
0 commit comments