@@ -125,7 +125,7 @@ function normalizeHashValue(hashValue) {
125125export function extractMatchRouteId ( windowRef , documentRef ) {
126126 const locationRef = windowRef ?. location || documentRef ?. defaultView ?. location || null ;
127127 const routePath = normalizeRoutePath ( locationRef ?. pathname || "" ) ;
128- const match = routePath . match ( MATCH_ROUTE_PATTERN ) ;
128+ const match = MATCH_ROUTE_PATTERN . exec ( routePath ) ;
129129 return match ?. [ 1 ] || "" ;
130130}
131131
@@ -272,6 +272,51 @@ function hasDegradedControlPaneText(node) {
272272 return / ( 2 0 | 1 9 | 1 8 | 1 7 | 1 6 | 1 5 | b u l l ) / i. test ( compactText ) || / \d { 6 , } / . test ( compactText ) ;
273273}
274274
275+ function selectDegradedHostPanePair ( hostRect , childEntries ) {
276+ if ( ! hostRect || childEntries . length < 2 ) {
277+ return null ;
278+ }
279+
280+ const dominantChildren = childEntries
281+ . filter ( ( entry ) => entry . rect . height >= hostRect . height * DEGRADED_HOST_MIN_PANE_HEIGHT_RATIO )
282+ . sort ( ( left , right ) => {
283+ const leftArea = left . rect . width * left . rect . height ;
284+ const rightArea = right . rect . width * right . rect . height ;
285+ return rightArea - leftArea ;
286+ } )
287+ . slice ( 0 , 2 )
288+ . sort ( ( left , right ) => left . rect . left - right . rect . left ) ;
289+
290+ return dominantChildren . length === 2 ? dominantChildren : null ;
291+ }
292+
293+ function buildDegradedHostCandidate ( current , gridRoot , hostRect , panePair ) {
294+ if ( ! Array . isArray ( panePair ) || panePair . length !== 2 ) {
295+ return null ;
296+ }
297+
298+ const [ leftPane , rightPane ] = panePair ;
299+ const paneSpan = leftPane . rect . width + rightPane . rect . width ;
300+ const leftWidthRatio = hostRect . width > 0 ? leftPane . rect . width / hostRect . width : 0 ;
301+ const rightWidthRatio = hostRect . width > 0 ? rightPane . rect . width / hostRect . width : 0 ;
302+ if (
303+ ! containsNode ( leftPane . node , gridRoot ) ||
304+ paneSpan < hostRect . width * DEGRADED_HOST_MIN_PANE_SPAN_RATIO ||
305+ leftWidthRatio < DEGRADED_HOST_MIN_LEFT_PANE_WIDTH_RATIO ||
306+ rightWidthRatio < DEGRADED_HOST_MIN_RIGHT_PANE_WIDTH_RATIO ||
307+ ! hasDegradedControlPaneText ( rightPane . node )
308+ ) {
309+ return null ;
310+ }
311+
312+ return {
313+ hostNode : current ,
314+ leftPaneNode : leftPane . node ,
315+ rightPaneNode : rightPane . node ,
316+ rightPaneText : readCompactNodeText ( rightPane . node ) ,
317+ } ;
318+ }
319+
275320function findDegradedHostCandidate ( extracted , options = { } ) {
276321 const gridRoot = extracted ?. gridSnapshot ?. root || null ;
277322 const documentRef = options . documentRef || extracted ?. documentRef || null ;
@@ -293,37 +338,10 @@ function findDegradedHostCandidate(extracted, options = {}) {
293338 while ( current && current !== documentRef . body && current !== documentRef . documentElement ) {
294339 const hostRect = readNodeRect ( current ) ;
295340 const childEntries = readVisibleChildEntries ( current ) ;
296- if ( hostRect && childEntries . length >= 2 ) {
297- const dominantChildren = childEntries
298- . filter ( ( entry ) => entry . rect . height >= hostRect . height * DEGRADED_HOST_MIN_PANE_HEIGHT_RATIO )
299- . sort ( ( left , right ) => {
300- const leftArea = left . rect . width * left . rect . height ;
301- const rightArea = right . rect . width * right . rect . height ;
302- return rightArea - leftArea ;
303- } )
304- . slice ( 0 , 2 )
305- . sort ( ( left , right ) => left . rect . left - right . rect . left ) ;
306-
307- if ( dominantChildren . length === 2 ) {
308- const [ leftPane , rightPane ] = dominantChildren ;
309- const paneSpan = leftPane . rect . width + rightPane . rect . width ;
310- const leftWidthRatio = hostRect . width > 0 ? leftPane . rect . width / hostRect . width : 0 ;
311- const rightWidthRatio = hostRect . width > 0 ? rightPane . rect . width / hostRect . width : 0 ;
312- if (
313- containsNode ( leftPane . node , gridRoot ) &&
314- paneSpan >= hostRect . width * DEGRADED_HOST_MIN_PANE_SPAN_RATIO &&
315- leftWidthRatio >= DEGRADED_HOST_MIN_LEFT_PANE_WIDTH_RATIO &&
316- rightWidthRatio >= DEGRADED_HOST_MIN_RIGHT_PANE_WIDTH_RATIO &&
317- hasDegradedControlPaneText ( rightPane . node )
318- ) {
319- return {
320- hostNode : current ,
321- leftPaneNode : leftPane . node ,
322- rightPaneNode : rightPane . node ,
323- rightPaneText : readCompactNodeText ( rightPane . node ) ,
324- } ;
325- }
326- }
341+ const panePair = selectDegradedHostPanePair ( hostRect , childEntries ) ;
342+ const candidate = buildDegradedHostCandidate ( current , gridRoot , hostRect , panePair ) ;
343+ if ( candidate ) {
344+ return candidate ;
327345 }
328346
329347 current = current . parentElement || null ;
@@ -631,7 +649,7 @@ function hasOwnMarkValue(node, options = {}) {
631649 }
632650 const cricketRules = options . cricketRules ;
633651 const allowTextMarkValue = options . allowTextMarkValue !== false ;
634- if ( typeof node . getAttribute === "function" && node . getAttribute ( "data- marks") !== null ) {
652+ if ( Object . hasOwn ( node ?. dataset || { } , " marks") ) {
635653 return true ;
636654 }
637655 if ( typeof node . querySelectorAll === "function" ) {
@@ -963,17 +981,8 @@ function haveEquivalentThrowSignatures(leftThrows, rightThrows, cricketRules, ta
963981 return left . every ( ( entry , index ) => entry === right [ index ] ) ;
964982}
965983
966- function shouldApplyActiveThrowPreview ( options = { } ) {
967- const gameState = options . gameState ;
968- const cricketRules = options . cricketRules ;
969- const targetOrder = options . targetOrder ;
970- const activeThrows = Array . isArray ( options . activeThrows ) ? options . activeThrows : [ ] ;
971- const activePlayerIndex = Number . isFinite ( Number ( options . activePlayerIndex ) )
972- ? Math . max ( 0 , Math . round ( Number ( options . activePlayerIndex ) ) )
973- : 0 ;
974- const snapshotMatch = options . snapshotMatch || null ;
975-
976- const debug = {
984+ function createActiveThrowPreviewDebug ( activeThrows , cricketRules , targetOrder ) {
985+ return {
977986 throwCount : activeThrows . length ,
978987 applied : false ,
979988 suppressionReason : "" ,
@@ -982,66 +991,120 @@ function shouldApplyActiveThrowPreview(options = {}) {
982991 activeThrowsSignature : toThrowSignatureParts ( activeThrows , cricketRules , targetOrder ) . join ( "," ) ,
983992 activeTurnSignature : "" ,
984993 } ;
994+ }
985995
986- if ( ! activeThrows . length ) {
987- debug . suppressionReason = "no-active-throws" ;
988- return debug ;
989- }
990-
996+ function readActiveTurnPreviewState ( gameState , cricketRules , targetOrder ) {
991997 const activeTurn =
992998 gameState && typeof gameState . getActiveTurn === "function"
993999 ? gameState . getActiveTurn ( )
9941000 : null ;
1001+ if ( ! activeTurn || typeof activeTurn !== "object" ) {
1002+ return {
1003+ finished : false ,
1004+ signature : "" ,
1005+ } ;
1006+ }
9951007
996- if ( activeTurn && typeof activeTurn === "object" ) {
997- debug . activeTurnSignature = toThrowSignatureParts (
1008+ return {
1009+ finished : Boolean ( String ( activeTurn . finishedAt || "" ) . trim ( ) ) ,
1010+ signature : toThrowSignatureParts (
9981011 Array . isArray ( activeTurn . throws ) ? activeTurn . throws : [ ] ,
9991012 cricketRules ,
10001013 targetOrder
1001- ) . join ( "," ) ;
1002- debug . activeTurnFinished = Boolean ( String ( activeTurn . finishedAt || "" ) . trim ( ) ) ;
1014+ ) . join ( "," ) ,
1015+ } ;
1016+ }
1017+
1018+ function listSnapshotTurns ( snapshotMatch ) {
1019+ return Array . isArray ( snapshotMatch ?. turns )
1020+ ? snapshotMatch . turns . filter ( ( turn ) => turn && typeof turn === "object" )
1021+ : [ ] ;
1022+ }
1023+
1024+ function isTurnOwnedByPlayer ( turn , playerId ) {
1025+ return String ( turn ?. playerId || "" ) . trim ( ) === playerId ;
1026+ }
1027+
1028+ function isFinishedTurnWithThrows ( turn ) {
1029+ return Boolean ( String ( turn ?. finishedAt || "" ) . trim ( ) ) &&
1030+ Array . isArray ( turn ?. throws ) &&
1031+ turn . throws . length > 0 ;
1032+ }
1033+
1034+ function shouldSuppressPreviewForFinishedSnapshotTurn (
1035+ snapshotMatch ,
1036+ activePlayerIndex ,
1037+ activeThrows ,
1038+ cricketRules ,
1039+ targetOrder
1040+ ) {
1041+ const activePlayerId = getPlayerIdByIndex ( snapshotMatch , activePlayerIndex ) ;
1042+ if ( ! activePlayerId ) {
1043+ return false ;
1044+ }
1045+
1046+ const turns = listSnapshotTurns ( snapshotMatch ) ;
1047+ const unfinishedTurnsForActivePlayer = turns . filter ( ( turn ) => {
1048+ return isTurnOwnedByPlayer ( turn , activePlayerId ) && ! String ( turn . finishedAt || "" ) . trim ( ) ;
1049+ } ) ;
1050+ if ( unfinishedTurnsForActivePlayer . length > 0 ) {
1051+ return false ;
1052+ }
1053+
1054+ const newestFinishedTurn = selectNewestTurnCandidate (
1055+ turns . filter ( ( turn ) => {
1056+ return isTurnOwnedByPlayer ( turn , activePlayerId ) && isFinishedTurnWithThrows ( turn ) ;
1057+ } )
1058+ ) ;
1059+ return Boolean (
1060+ newestFinishedTurn &&
1061+ haveEquivalentThrowSignatures (
1062+ newestFinishedTurn . throws ,
1063+ activeThrows ,
1064+ cricketRules ,
1065+ targetOrder
1066+ )
1067+ ) ;
1068+ }
1069+
1070+ function shouldApplyActiveThrowPreview ( options = { } ) {
1071+ const gameState = options . gameState ;
1072+ const cricketRules = options . cricketRules ;
1073+ const targetOrder = options . targetOrder ;
1074+ const activeThrows = Array . isArray ( options . activeThrows ) ? options . activeThrows : [ ] ;
1075+ const activePlayerIndex = Number . isFinite ( Number ( options . activePlayerIndex ) )
1076+ ? Math . max ( 0 , Math . round ( Number ( options . activePlayerIndex ) ) )
1077+ : 0 ;
1078+ const snapshotMatch = options . snapshotMatch || null ;
1079+
1080+ const debug = createActiveThrowPreviewDebug ( activeThrows , cricketRules , targetOrder ) ;
1081+
1082+ if ( ! activeThrows . length ) {
1083+ debug . suppressionReason = "no-active-throws" ;
1084+ return debug ;
10031085 }
10041086
1087+ const activeTurnState = readActiveTurnPreviewState ( gameState , cricketRules , targetOrder ) ;
1088+ debug . activeTurnSignature = activeTurnState . signature ;
1089+ debug . activeTurnFinished = activeTurnState . finished ;
1090+
10051091 if ( debug . activeTurnFinished ) {
10061092 debug . suppressionReason = "active-turn-finished" ;
10071093 return debug ;
10081094 }
10091095
1010- if ( snapshotMatch && Array . isArray ( snapshotMatch . turns ) ) {
1011- const activePlayerId = getPlayerIdByIndex ( snapshotMatch , activePlayerIndex ) ;
1012- const turns = snapshotMatch . turns . filter ( ( turn ) => turn && typeof turn === "object" ) ;
1013- const unfinishedTurnsForActivePlayer = turns . filter ( ( turn ) => {
1014- return (
1015- String ( turn . playerId || "" ) . trim ( ) === activePlayerId &&
1016- ! String ( turn . finishedAt || "" ) . trim ( )
1017- ) ;
1018- } ) ;
1019-
1020- if ( activePlayerId && unfinishedTurnsForActivePlayer . length === 0 ) {
1021- const finishedTurnsForActivePlayer = turns . filter ( ( turn ) => {
1022- return (
1023- String ( turn . playerId || "" ) . trim ( ) === activePlayerId &&
1024- String ( turn . finishedAt || "" ) . trim ( ) &&
1025- Array . isArray ( turn . throws ) &&
1026- turn . throws . length > 0
1027- ) ;
1028- } ) ;
1029-
1030- const newestFinishedTurn = selectNewestTurnCandidate ( finishedTurnsForActivePlayer ) ;
1031- if (
1032- newestFinishedTurn &&
1033- haveEquivalentThrowSignatures (
1034- newestFinishedTurn . throws ,
1035- activeThrows ,
1036- cricketRules ,
1037- targetOrder
1038- )
1039- ) {
1040- debug . matchedFinishedTurn = true ;
1041- debug . suppressionReason = "matches-last-finished-turn" ;
1042- return debug ;
1043- }
1044- }
1096+ if (
1097+ shouldSuppressPreviewForFinishedSnapshotTurn (
1098+ snapshotMatch ,
1099+ activePlayerIndex ,
1100+ activeThrows ,
1101+ cricketRules ,
1102+ targetOrder
1103+ )
1104+ ) {
1105+ debug . matchedFinishedTurn = true ;
1106+ debug . suppressionReason = "matches-last-finished-turn" ;
1107+ return debug ;
10451108 }
10461109
10471110 debug . applied = true ;
0 commit comments