@@ -30,7 +30,16 @@ import {
3030 stringToKeyValuePairsCore ,
3131 arrayToMapCore ,
3232} from './word-mode' ;
33+ import { createGetDebouncedCapitaliseText } from './word-mode' ;
3334import { enableSentenceMode , disableSentenceMode } from './sentence-mode' ;
35+ import {
36+ DEFAULT_DEBOUNCE_DELAY as _DEFAULT_DEBOUNCE_DELAY ,
37+ debounce as _debounce ,
38+ __resetDebouncedMapForTests as _resetDebouncedMapForTests ,
39+ clearDebouncedCapitalisationCache as _clearDebouncedCapitalisationCache ,
40+ flushAndClearDebouncedCapitalisations as _flushAndClearDebouncedCapitalisations ,
41+ cancelDebouncedForElement as _cancelDebouncedForElement ,
42+ } from './debounce' ;
3443// Re-export commonly used option and key names so tests can reliably import them from a single module.
3544export {
3645 shouldCapitaliseI ,
@@ -792,139 +801,32 @@ export function arrayToMap(obj) {
792801 return { } ;
793802}
794803
795- // Debounce function for sliding window delay
796- export const DEFAULT_DEBOUNCE_DELAY = 5000 ;
797-
798- export function debounce ( func , delay ) {
799- // Normalise delay: fall back to DEFAULT_DEBOUNCE_DELAY for invalid values (NaN, negative, null, undefined)
800- const normalisedDelay =
801- Number . isFinite ( delay ) && delay >= 0 ? delay : DEFAULT_DEBOUNCE_DELAY ;
802-
803- // Special case: a zero delay should execute immediately (synchronously) as per test expectations
804- if ( normalisedDelay === 0 ) {
805- return function ( ...args ) {
806- return func . apply ( this , args ) ;
807- } ;
808- }
809-
810- let timeoutId ;
811- return function ( ...args ) {
812- if ( timeoutId ) {
813- clearTimeout ( timeoutId ) ; // sliding window behaviour
814- }
815- timeoutId = setTimeout ( ( ) => func . apply ( this , args ) , normalisedDelay ) ;
816- } ;
817- }
804+ // Re-export debounce helpers from dedicated module
805+ export const DEFAULT_DEBOUNCE_DELAY = _DEFAULT_DEBOUNCE_DELAY ;
806+ export const debounce = _debounce ;
818807
819808// Helper to expose current sentence case mode without leaking internal dictionary object
820809export function isSentenceCaseEnabled ( ) {
821810 return ! ! optionsDictionary [ shouldConvertToSentenceCase ] ;
822811}
823812
824- // Value shape: { fn: Function, timeoutId: number|null }
825- let debouncedCapitalizationMap = new WeakMap ( ) ;
826-
827813// TEST-ONLY helper (safe in prod; no reference leakage) to clear debounced map
828- export function __resetDebouncedMapForTests ( ) {
829- debouncedCapitalizationMap = new WeakMap ( ) ;
830- }
831-
832- // Public helper to clear per-element debounced functions (used when switching modes)
833- export function clearDebouncedCapitalisationCache ( ) {
834- // Cancel any outstanding timers without flushing
835- debouncedCapitalizationMap = new WeakMap ( ) ;
836- }
837-
838- // Force flush (run immediately) all pending debounced capitalisations then clear cache.
839- export function flushAndClearDebouncedCapitalisations ( ) {
840- try {
841- debouncedCapitalizationMap . forEach ?. ( ( ) => { } ) ; // no-op safeguard for older environments
842- } catch {
843- /* ignore */
844- }
845- // Iterate via WeakMap is not directly possible; instead we store wrapped functions that self-record timeout IDs.
846- // So this helper is best-effort: callers should hold element references if precise flushing is required.
847- }
848-
849- // Explicit cancel for a specific element (used when switching modes for active element)
850- export function cancelDebouncedForElement ( element ) {
851- if ( ! element || typeof element !== 'object' ) return ;
852- try {
853- const entry = debouncedCapitalizationMap . get ( element ) ;
854- if ( entry && entry . timeoutId ) {
855- clearTimeout ( entry . timeoutId ) ;
856- entry . timeoutId = null ;
857- }
858- } catch ( e ) {
859- // Silently handle WeakMap errors during extension reload
860- console . debug (
861- 'WeakMap access error (extension may be reloading):' ,
862- e . message
863- ) ;
864- }
865- }
866-
867- export function getDebouncedCapitaliseText (
868- element ,
869- delay = DEFAULT_DEBOUNCE_DELAY ,
870- capitaliserFn = capitaliseTextProxy
871- ) {
872- // Defensive check: element must be a valid object for WeakMap
873- if ( ! element || typeof element !== 'object' ) return ( ) => { } ;
874-
875- const existing = debouncedCapitalizationMap . get ( element ) ;
876- if ( existing && typeof existing . fn === 'function' ) return existing . fn ;
877-
878- let timeoutId = null ;
879- const wrapped = function ( targetElement ) {
880- // Defensive check for extension context invalidation
881- if ( ! targetElement || typeof targetElement !== 'object' ) return ;
882- if ( timeoutId ) clearTimeout ( timeoutId ) ;
883- // Immediate execute if delay == 0
884- if ( ! Number . isFinite ( delay ) || delay < 0 ) delay = DEFAULT_DEBOUNCE_DELAY ;
885- if ( delay === 0 ) {
886- capitaliserFn (
887- targetElement ,
888- shouldCapitalise ,
889- shouldCapitaliseForI ,
890- getText ,
891- setText
892- ) ;
893- return ;
894- }
895- timeoutId = setTimeout ( ( ) => {
896- timeoutId = null ;
897- try {
898- capitaliserFn (
899- targetElement ,
900- shouldCapitalise ,
901- shouldCapitaliseForI ,
902- getText ,
903- setText
904- ) ;
905- } catch {
906- /* ignore */
907- }
908- } , delay ) ;
909- try {
910- debouncedCapitalizationMap . set ( element , { fn : wrapped , timeoutId } ) ;
911- } catch ( e ) {
912- // Silently handle WeakMap errors during extension reload
913- console . debug (
914- 'WeakMap set error (extension may be reloading):' ,
915- e . message
916- ) ;
917- }
918- } ;
919-
920- try {
921- debouncedCapitalizationMap . set ( element , { fn : wrapped , timeoutId } ) ;
922- } catch ( e ) {
923- // Silently handle WeakMap errors during extension reload
924- console . debug ( 'WeakMap set error (extension may be reloading):' , e . message ) ;
925- }
926- return wrapped ;
927- }
814+ export const __resetDebouncedMapForTests = _resetDebouncedMapForTests ;
815+ export const clearDebouncedCapitalisationCache =
816+ _clearDebouncedCapitalisationCache ;
817+ export const flushAndClearDebouncedCapitalisations =
818+ _flushAndClearDebouncedCapitalisations ;
819+ export const cancelDebouncedForElement = _cancelDebouncedForElement ;
820+
821+ // Compatibility wrapper: adapt existing capitaliser signature to the debounce core's expectation
822+ // Create the compat wrapper via factory to avoid circular imports
823+ export const getDebouncedCapitaliseText = createGetDebouncedCapitaliseText ( {
824+ getTextFn : getText ,
825+ setTextFn : setText ,
826+ shouldCapitaliseFn : shouldCapitalise ,
827+ shouldCapitaliseForIFn : shouldCapitaliseForI ,
828+ capitaliseTextProxyFn : capitaliseTextProxy ,
829+ } ) ;
928830
929831// Retroactively apply enabled rules across the entire text of a single element (used when toggling features on)
930832export function fullReprocessElement ( element ) {
0 commit comments