@@ -310,87 +310,111 @@ function RemoteFunctions(config) {
310310 }
311311
312312 NodeMoreOptionsBox . prototype = {
313- create : function ( ) {
314- // Remove existing more options box if any
315- this . remove ( ) ;
316-
317- let elemBounds = this . element . getBoundingClientRect ( ) ;
318-
319- // for styling the svg's
320- if ( ! document . getElementById ( "node-more-options-style" ) ) {
321- const style = document . createElement ( "style" ) ;
322- style . id = "node-more-options-style" ;
323- style . textContent = `
324- .node-options span > svg {
325- width: 16px;
326- height: 16px;
327- display: block;
328- }
329- ` ;
330- document . head . appendChild ( style ) ;
331- }
332-
333- // create the container
313+ _style : function ( ) {
334314 this . body = window . document . createElement ( "div" ) ;
335- this . body . style . setProperty ( "z-index" , 2147483647 ) ;
336- this . body . style . setProperty ( "position" , "fixed" ) ;
337315
338- const boxWidth = 82 ;
316+ // this is shadow DOM.
317+ // we need it because if we add the box directly to the DOM then users style might override it.
318+ // {mode: "closed"} means that users will not be able to access the shadow DOM
319+ const shadow = this . body . attachShadow ( { mode : "closed" } ) ;
339320
340- this . body . style . setProperty ( "left" , ( elemBounds . right - boxWidth ) + "px" ) ;
341- this . body . style . setProperty (
342- "top" ,
343- // if there's not enough space to show the box above the element,
344- // we show it below the element
345- ( elemBounds . top - 30 < 0 ? elemBounds . top + elemBounds . height + 5 : elemBounds . top - 30 ) + "px"
346- ) ;
347- this . body . style . setProperty ( "font-size" , "12px" ) ;
348- this . body . style . setProperty ( "font-family" , "Arial, sans-serif" ) ;
321+ // the element that was clicked
322+ let elemBounds = this . element . getBoundingClientRect ( ) ;
349323
350- // style the box with a blue background. this will appear on the right side of the clicked DOM element
351- this . body . style . setProperty ( "background" , "#4285F4" ) ;
352- this . body . style . setProperty ( "color" , "white" ) ;
353- this . body . style . setProperty ( "border-radius" , "3px" ) ;
354- this . body . style . setProperty ( "padding" , "5px 8px" ) ;
355- this . body . style . setProperty ( "box-shadow" , "0 2px 5px rgba(0,0,0,0.2)" ) ;
356- this . body . style . setProperty ( "width" , boxWidth + "px" ) ;
324+ // the box width and the positions where it should be placed
325+ const boxWidth = 82 ;
326+ const leftPos = ( elemBounds . right - boxWidth ) ;
327+ const topPos = ( elemBounds . top - 30 < 0 ? elemBounds . top + elemBounds . height + 5 : elemBounds . top - 30 ) ;
357328
329+ // the icons that is displayed in the box
358330 const ICONS = {
359- arrowUp : `<svg viewBox="0 0 24 24" fill="currentColor">
360- <path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.59 5.58L20 12l-8-8-8 8z"/>
361- </svg>` ,
362-
363- copy : `<svg viewBox="0 0 24 24" fill="currentColor">
364- <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
365- </svg>` ,
366-
367- trash : `<svg viewBox="0 0 24 24" fill="currentColor">
368- <path d="M6 7V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2h3v2h-2l-1.5 12.5a2 2 0 0 1-2 1.5H8.5a2 2 0 0 1-2-1.5L5 9H3V7h3zm2 0h8V5H8v2z"/>
369- </svg>`
331+ arrowUp : `
332+ <svg viewBox="0 0 24 24" fill="currentColor">
333+ <path d="M4 12l1.41 1.41L11 7.83V20h2V7.83l5.59 5.58L20 12l-8-8-8 8z"/>
334+ </svg>
335+ ` ,
336+
337+ copy : `
338+ <svg viewBox="0 0 24 24" fill="currentColor">
339+ <path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0
340+ 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/>
341+ </svg>
342+ ` ,
343+
344+ trash : `
345+ <svg viewBox="0 0 24 24" fill="currentColor">
346+ <path d="M6 7V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2h3v2h-2l-1.5 12.5a2 2 0 0
347+ 1-2 1.5H8.5a2 2 0 0 1-2-1.5L5 9H3V7h3zm2 0h8V5H8v2z"/>
348+ </svg>
349+ `
370350 } ;
371351
372- let content = `<div class="node-options" style="display: flex; gap: 8px; align-items: center;" >
373- <span style="cursor: pointer; display: flex; align-items: center;" data-action="select-parent" title="Select Parent">
352+ let content = `<div class="node-options">
353+ <span data-action="select-parent" title="Select Parent">
374354 ${ ICONS . arrowUp }
375355 </span>
376- <span style="cursor: pointer; display: flex; align-items: center;" data-action="duplicate" title="Duplicate">
356+ <span data-action="duplicate" title="Duplicate">
377357 ${ ICONS . copy }
378358 </span>
379- <span style="cursor: pointer; display: flex; align-items: center;" data-action="delete" title="Delete">
359+ <span data-action="delete" title="Delete">
380360 ${ ICONS . trash }
381361 </span>
382362 </div>` ;
383363
384- this . body . innerHTML = content ;
364+ const styles = `
365+ .box {
366+ background-color: #4285F4;
367+ color: white;
368+ border-radius: 3px;
369+ padding: 5px 8px;
370+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
371+ font-size: 12px;
372+ font-family: Arial, sans-serif;
373+ z-index: 2147483647;
374+ position: fixed;
375+ left: ${ leftPos } px;
376+ top: ${ topPos } px;
377+ width: ${ boxWidth } px;
378+ box-sizing: border-box;
379+ }
380+
381+ .node-options {
382+ display: flex;
383+ gap: 8px;
384+ align-items: center;
385+ }
386+
387+ .node-options span {
388+ cursor: pointer;
389+ display: flex;
390+ align-items: center;
391+ }
392+
393+ .node-options span > svg {
394+ width: 16px;
395+ height: 16px;
396+ display: block;
397+ }
398+ ` ;
399+
400+ // add everything to the shadow box
401+ shadow . innerHTML = `<style>${ styles } </style><div class="box">${ content } </div>` ;
402+ this . _shadow = shadow ;
403+ } ,
404+
405+ create : function ( ) {
406+ this . remove ( ) ; // remove existing box if already present
407+ this . _style ( ) ; // style the box
408+
385409 window . document . body . appendChild ( this . body ) ;
386410
387- // add the click handler to all the buttons
388- const spans = this . body . querySelectorAll ( '.node-options span' ) ;
411+ // add click handler to all the buttons
412+ const spans = this . _shadow . querySelectorAll ( '.node-options span' ) ;
389413 spans . forEach ( span => {
390- // to differentiate between each button click we can use the data-action attribute
391414 span . addEventListener ( 'click' , ( event ) => {
392415 event . stopPropagation ( ) ;
393416 event . preventDefault ( ) ;
417+ // data-action is to differentiate between the buttons (duplicate, delete or select-parent)
394418 const action = event . currentTarget . getAttribute ( 'data-action' ) ;
395419 handleOptionClick ( event , action , this . element ) ;
396420 this . remove ( ) ;
0 commit comments