@@ -285,6 +285,10 @@ function RemoteFunctions(config = {}) {
285285 position: absolute !important;
286286 }
287287
288+ .overlay-container.hidden {
289+ display: none !important;
290+ }
291+
288292 .outline {
289293 position: absolute !important;
290294 box-sizing: border-box !important;
@@ -304,11 +308,163 @@ function RemoteFunctions(config = {}) {
304308 return _highlightShadowRoot ;
305309 }
306310
311+ // Overlay pool — overlays are created once and reused across highlights.
312+ // When released, they stay in the shadow DOM (hidden) ready for instant reuse.
313+ // This eliminates all DOM creation/destruction from the highlight hot paths.
314+ const _overlayPool = [ ] ;
315+
316+ function _createOverlayStructure ( ) {
317+ const div = window . document . createElement ( "div" ) ;
318+ div . className = "overlay-container hidden" ;
319+
320+ function createRect ( ) {
321+ const r = window . document . createElement ( "div" ) ;
322+ r . className = "rect" ;
323+ return r ;
324+ }
325+
326+ const padTop = createRect ( ) , padBottom = createRect ( ) ,
327+ padLeft = createRect ( ) , padRight = createRect ( ) ;
328+ const marTop = createRect ( ) , marBottom = createRect ( ) ,
329+ marLeft = createRect ( ) , marRight = createRect ( ) ;
330+ const outline = window . document . createElement ( "div" ) ;
331+ outline . className = "outline" ;
332+
333+ div . appendChild ( padTop ) ;
334+ div . appendChild ( padBottom ) ;
335+ div . appendChild ( padLeft ) ;
336+ div . appendChild ( padRight ) ;
337+ div . appendChild ( marTop ) ;
338+ div . appendChild ( marBottom ) ;
339+ div . appendChild ( marLeft ) ;
340+ div . appendChild ( marRight ) ;
341+ div . appendChild ( outline ) ;
342+
343+ // Cache child references for O(1) access during updates
344+ div . _refs = {
345+ padTop, padBottom, padLeft, padRight,
346+ marTop, marBottom, marLeft, marRight,
347+ outline
348+ } ;
349+
350+ _ensureHighlightShadowRoot ( ) . appendChild ( div ) ;
351+ return div ;
352+ }
353+
354+ function _getOverlay ( ) {
355+ return _overlayPool . length > 0 ? _overlayPool . pop ( ) : _createOverlayStructure ( ) ;
356+ }
357+
358+ function _releaseOverlay ( overlay ) {
359+ overlay . classList . add ( 'hidden' ) ;
360+ overlay . trackingElement = null ;
361+ _overlayPool . push ( overlay ) ;
362+ }
363+
364+ // Update an existing overlay's position, dimensions, and colors to match the target element.
365+ // No DOM elements are created or destroyed — only style properties are updated.
366+ function _updateOverlay ( overlay , element ) {
367+ const bounds = element . getBoundingClientRect ( ) ;
368+ if ( bounds . width === 0 && bounds . height === 0 ) {
369+ overlay . classList . add ( 'hidden' ) ;
370+ return ;
371+ }
372+
373+ const cs = window . getComputedStyle ( element ) ;
374+
375+ // Parse box model values (getComputedStyle always resolves to px)
376+ const bt = parseFloat ( cs . borderTopWidth ) || 0 ,
377+ br = parseFloat ( cs . borderRightWidth ) || 0 ,
378+ bb = parseFloat ( cs . borderBottomWidth ) || 0 ,
379+ bl = parseFloat ( cs . borderLeftWidth ) || 0 ;
380+ const pt = parseFloat ( cs . paddingTop ) || 0 ,
381+ pr = parseFloat ( cs . paddingRight ) || 0 ,
382+ pb = parseFloat ( cs . paddingBottom ) || 0 ,
383+ pl = parseFloat ( cs . paddingLeft ) || 0 ;
384+ const mt = parseFloat ( cs . marginTop ) || 0 ,
385+ mr = parseFloat ( cs . marginRight ) || 0 ,
386+ mb = parseFloat ( cs . marginBottom ) || 0 ,
387+ ml = parseFloat ( cs . marginLeft ) || 0 ;
388+
389+ // Compute the 4 absolute boxes exactly like dev tools:
390+ // getBoundingClientRect() always returns the border box regardless of box-sizing.
391+ const scroll = LivePreviewView . screenOffset ( element ) ;
392+ const borderBox = {
393+ left : scroll . left ,
394+ top : scroll . top ,
395+ width : bounds . width ,
396+ height : bounds . height
397+ } ;
398+ const paddingBox = {
399+ left : borderBox . left + bl ,
400+ top : borderBox . top + bt ,
401+ width : borderBox . width - bl - br ,
402+ height : borderBox . height - bt - bb
403+ } ;
404+ const contentBox = {
405+ left : paddingBox . left + pl ,
406+ top : paddingBox . top + pt ,
407+ width : paddingBox . width - pl - pr ,
408+ height : paddingBox . height - pt - pb
409+ } ;
410+ const marginBox = {
411+ left : borderBox . left - ml ,
412+ top : borderBox . top - mt ,
413+ width : borderBox . width + ml + mr ,
414+ height : borderBox . height + mt + mb
415+ } ;
416+
417+ // Update container position
418+ overlay . trackingElement = element ;
419+ overlay . style . left = marginBox . left + "px" ;
420+ overlay . style . top = marginBox . top + "px" ;
421+ overlay . style . width = marginBox . width + "px" ;
422+ overlay . style . height = marginBox . height + "px" ;
423+ overlay . classList . remove ( 'hidden' ) ;
424+
425+ const refs = overlay . _refs ;
426+ const mLeft = marginBox . left ;
427+
428+ // Update a rect's position, size, and color in place
429+ function setRect ( rect , left , top , width , height , color ) {
430+ const s = rect . style ;
431+ s . left = ( left - mLeft ) + "px" ;
432+ s . top = ( top - marginBox . top ) + "px" ;
433+ s . width = Math . max ( 0 , width ) + "px" ;
434+ s . height = Math . max ( 0 , height ) + "px" ;
435+ s . backgroundColor = color ;
436+ }
437+
438+ // Padding region
439+ const padColor = COLORS . highlightPadding ;
440+ setRect ( refs . padTop , paddingBox . left , paddingBox . top , paddingBox . width , pt , padColor ) ;
441+ setRect ( refs . padBottom , paddingBox . left , contentBox . top + contentBox . height , paddingBox . width , pb , padColor ) ;
442+ setRect ( refs . padLeft , paddingBox . left , contentBox . top , pl , contentBox . height , padColor ) ;
443+ setRect ( refs . padRight , contentBox . left + contentBox . width , contentBox . top , pr , contentBox . height , padColor ) ;
444+
445+ // Margin region
446+ const margColor = COLORS . highlightMargin ;
447+ setRect ( refs . marTop , marginBox . left , marginBox . top , marginBox . width , mt , margColor ) ;
448+ setRect ( refs . marBottom , marginBox . left , borderBox . top + borderBox . height , marginBox . width , mb , margColor ) ;
449+ setRect ( refs . marLeft , marginBox . left , borderBox . top , ml , borderBox . height , margColor ) ;
450+ setRect ( refs . marRight , borderBox . left + borderBox . width , borderBox . top , mr , borderBox . height , margColor ) ;
451+
452+ // Outline
453+ const isEditable = element . hasAttribute ( GLOBALS . DATA_BRACKETS_ID_ATTR ) ;
454+ const outlineColor = isEditable ? COLORS . outlineEditable : COLORS . outlineNonEditable ;
455+ const outlineStyle = refs . outline . style ;
456+ outlineStyle . left = ( borderBox . left - mLeft ) + "px" ;
457+ outlineStyle . top = ( borderBox . top - marginBox . top ) + "px" ;
458+ outlineStyle . width = borderBox . width + "px" ;
459+ outlineStyle . height = borderBox . height + "px" ;
460+ outlineStyle . border = `1px solid ${ outlineColor } ` ;
461+ }
462+
307463 function Highlight ( trigger ) {
308464 this . trigger = ! ! trigger ;
309465 this . elements = [ ] ;
310466 this . selector = "" ;
311- this . _divs = [ ] ;
467+ this . _overlays = [ ] ;
312468 }
313469
314470 Highlight . prototype = {
@@ -321,16 +477,16 @@ function RemoteFunctions(config = {}) {
321477 }
322478
323479 this . elements . push ( element ) ;
324- this . _createOverlay ( element ) ;
480+ const overlay = _getOverlay ( ) ;
481+ this . _overlays . push ( overlay ) ;
482+ _updateOverlay ( overlay , element ) ;
325483 } ,
326484
327485 clear : function ( ) {
328- this . _divs . forEach ( function ( div ) {
329- if ( div . parentNode ) {
330- div . parentNode . removeChild ( div ) ;
331- }
486+ this . _overlays . forEach ( function ( overlay ) {
487+ _releaseOverlay ( overlay ) ;
332488 } ) ;
333- this . _divs = [ ] ;
489+ this . _overlays = [ ] ;
334490
335491 if ( this . trigger ) {
336492 this . elements . forEach ( function ( el ) {
@@ -345,124 +501,21 @@ function RemoteFunctions(config = {}) {
345501 const elements = this . selector
346502 ? Array . from ( window . document . querySelectorAll ( this . selector ) )
347503 : this . elements . slice ( ) ;
348- this . clear ( ) ;
349- elements . forEach ( function ( el ) { this . add ( el ) ; } , this ) ;
350- } ,
351504
352- _createOverlay : function ( element ) {
353- const bounds = element . getBoundingClientRect ( ) ;
354- if ( bounds . width === 0 && bounds . height === 0 ) { return ; }
355-
356- const cs = window . getComputedStyle ( element ) ;
357-
358- // Parse box model values (getComputedStyle always resolves to px)
359- const bt = parseFloat ( cs . borderTopWidth ) || 0 ,
360- br = parseFloat ( cs . borderRightWidth ) || 0 ,
361- bb = parseFloat ( cs . borderBottomWidth ) || 0 ,
362- bl = parseFloat ( cs . borderLeftWidth ) || 0 ;
363- const pt = parseFloat ( cs . paddingTop ) || 0 ,
364- pr = parseFloat ( cs . paddingRight ) || 0 ,
365- pb = parseFloat ( cs . paddingBottom ) || 0 ,
366- pl = parseFloat ( cs . paddingLeft ) || 0 ;
367- const mt = parseFloat ( cs . marginTop ) || 0 ,
368- mr = parseFloat ( cs . marginRight ) || 0 ,
369- mb = parseFloat ( cs . marginBottom ) || 0 ,
370- ml = parseFloat ( cs . marginLeft ) || 0 ;
371-
372- // Compute the 4 absolute boxes exactly like dev tools:
373- // getBoundingClientRect() always returns the border box regardless of box-sizing.
374- const scroll = LivePreviewView . screenOffset ( element ) ;
375- const borderBox = {
376- left : scroll . left ,
377- top : scroll . top ,
378- width : bounds . width ,
379- height : bounds . height
380- } ;
381- const paddingBox = {
382- left : borderBox . left + bl ,
383- top : borderBox . top + bt ,
384- width : borderBox . width - bl - br ,
385- height : borderBox . height - bt - bb
386- } ;
387- const contentBox = {
388- left : paddingBox . left + pl ,
389- top : paddingBox . top + pt ,
390- width : paddingBox . width - pl - pr ,
391- height : paddingBox . height - pt - pb
392- } ;
393- const marginBox = {
394- left : borderBox . left - ml ,
395- top : borderBox . top - mt ,
396- width : borderBox . width + ml + mr ,
397- height : borderBox . height + mt + mb
398- } ;
399-
400- // Container div — sized to the margin box so all rects fit inside it
401- const div = window . document . createElement ( "div" ) ;
402- div . className = "overlay-container" ;
403- div . trackingElement = element ;
404- div . style . left = marginBox . left + "px" ;
405- div . style . top = marginBox . top + "px" ;
406- div . style . width = marginBox . width + "px" ;
407- div . style . height = marginBox . height + "px" ;
408-
409- // Helper to create a colored rect at absolute page coordinates, offset by the container origin
410- function makeRect ( left , top , width , height , color ) {
411- if ( width <= 0 || height <= 0 ) { return ; }
412- const r = window . document . createElement ( "div" ) ;
413- r . className = "rect" ;
414- r . style . left = ( left - marginBox . left ) + "px" ;
415- r . style . top = ( top - marginBox . top ) + "px" ;
416- r . style . width = width + "px" ;
417- r . style . height = height + "px" ;
418- r . style . backgroundColor = color ;
419- div . appendChild ( r ) ;
505+ // Adjust overlay count to match element count
506+ while ( this . _overlays . length > elements . length ) {
507+ _releaseOverlay ( this . _overlays . pop ( ) ) ;
420508 }
509+ while ( this . _overlays . length < elements . length ) {
510+ this . _overlays . push ( _getOverlay ( ) ) ;
511+ }
512+
513+ this . elements = elements ;
421514
422- // Padding region: 4 rects filling paddingBox minus contentBox
423- const padColor = COLORS . highlightPadding ;
424- // top padding
425- makeRect ( paddingBox . left , paddingBox . top ,
426- paddingBox . width , pt , padColor ) ;
427- // bottom padding
428- makeRect ( paddingBox . left , contentBox . top + contentBox . height ,
429- paddingBox . width , pb , padColor ) ;
430- // left padding
431- makeRect ( paddingBox . left , contentBox . top ,
432- pl , contentBox . height , padColor ) ;
433- // right padding
434- makeRect ( contentBox . left + contentBox . width , contentBox . top ,
435- pr , contentBox . height , padColor ) ;
436-
437- // Margin region: 4 rects filling marginBox minus borderBox
438- const margColor = COLORS . highlightMargin ;
439- // top margin
440- makeRect ( marginBox . left , marginBox . top ,
441- marginBox . width , mt , margColor ) ;
442- // bottom margin
443- makeRect ( marginBox . left , borderBox . top + borderBox . height ,
444- marginBox . width , mb , margColor ) ;
445- // left margin
446- makeRect ( marginBox . left , borderBox . top ,
447- ml , borderBox . height , margColor ) ;
448- // right margin
449- makeRect ( borderBox . left + borderBox . width , borderBox . top ,
450- mr , borderBox . height , margColor ) ;
451-
452- // Selection outline: 1px border at the border-box edge (drawn inside the border area)
453- const isEditable = element . hasAttribute ( GLOBALS . DATA_BRACKETS_ID_ATTR ) ;
454- const outlineColor = isEditable ? COLORS . outlineEditable : COLORS . outlineNonEditable ;
455- const outlineDiv = window . document . createElement ( "div" ) ;
456- outlineDiv . className = "outline" ;
457- outlineDiv . style . left = ( borderBox . left - marginBox . left ) + "px" ;
458- outlineDiv . style . top = ( borderBox . top - marginBox . top ) + "px" ;
459- outlineDiv . style . width = borderBox . width + "px" ;
460- outlineDiv . style . height = borderBox . height + "px" ;
461- outlineDiv . style . border = `1px solid ${ outlineColor } ` ;
462- div . appendChild ( outlineDiv ) ;
463-
464- _ensureHighlightShadowRoot ( ) . appendChild ( div ) ;
465- this . _divs . push ( div ) ;
515+ // Update all overlays in place — no DOM creation or destruction
516+ for ( let i = 0 ; i < elements . length ; i ++ ) {
517+ _updateOverlay ( this . _overlays [ i ] , elements [ i ] ) ;
518+ }
466519 }
467520 } ;
468521
@@ -1485,12 +1538,12 @@ function RemoteFunctions(config = {}) {
14851538
14861539 function getHighlightCount ( ) {
14871540 if ( ! _highlightShadowRoot ) { return 0 ; }
1488- return _highlightShadowRoot . querySelectorAll ( '.overlay-container' ) . length ;
1541+ return _highlightShadowRoot . querySelectorAll ( '.overlay-container:not(.hidden) ' ) . length ;
14891542 }
14901543
14911544 function getHighlightTrackingElement ( index ) {
14921545 if ( ! _highlightShadowRoot ) { return null ; }
1493- const overlay = _highlightShadowRoot . querySelectorAll ( '.overlay-container' ) [ index ] ;
1546+ const overlay = _highlightShadowRoot . querySelectorAll ( '.overlay-container:not(.hidden) ' ) [ index ] ;
14941547 if ( ! overlay || ! overlay . trackingElement ) { return null ; }
14951548 const el = overlay . trackingElement ;
14961549 return {
@@ -1501,7 +1554,7 @@ function RemoteFunctions(config = {}) {
15011554
15021555 function getHighlightStyle ( index , property ) {
15031556 if ( ! _highlightShadowRoot ) { return null ; }
1504- const overlay = _highlightShadowRoot . querySelectorAll ( '.overlay-container' ) [ index ] ;
1557+ const overlay = _highlightShadowRoot . querySelectorAll ( '.overlay-container:not(.hidden) ' ) [ index ] ;
15051558 return overlay ? overlay . style [ property ] : null ;
15061559 }
15071560
0 commit comments