Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 1 | import { queryAll } from '../utils/util.js' |
| 2 | import { colorToRgb, colorBrightness } from '../utils/color.js' |
| 3 | |
| 4 | /** |
| 5 | * Creates and updates slide backgrounds. |
| 6 | */ |
| 7 | export default class Backgrounds { |
| 8 | |
| 9 | constructor( Reveal ) { |
| 10 | |
| 11 | this.Reveal = Reveal; |
| 12 | |
| 13 | } |
| 14 | |
| 15 | render() { |
| 16 | |
| 17 | this.element = document.createElement( 'div' ); |
| 18 | this.element.className = 'backgrounds'; |
| 19 | this.Reveal.getRevealElement().appendChild( this.element ); |
| 20 | |
| 21 | } |
| 22 | |
| 23 | /** |
| 24 | * Creates the slide background elements and appends them |
| 25 | * to the background container. One element is created per |
| 26 | * slide no matter if the given slide has visible background. |
| 27 | */ |
| 28 | create() { |
| 29 | |
| 30 | // Clear prior backgrounds |
| 31 | this.element.innerHTML = ''; |
| 32 | this.element.classList.add( 'no-transition' ); |
| 33 | |
| 34 | // Iterate over all horizontal slides |
| 35 | this.Reveal.getHorizontalSlides().forEach( slideh => { |
| 36 | |
| 37 | let backgroundStack = this.createBackground( slideh, this.element ); |
| 38 | |
| 39 | // Iterate over all vertical slides |
| 40 | queryAll( slideh, 'section' ).forEach( slidev => { |
| 41 | |
| 42 | this.createBackground( slidev, backgroundStack ); |
| 43 | |
| 44 | backgroundStack.classList.add( 'stack' ); |
| 45 | |
| 46 | } ); |
| 47 | |
| 48 | } ); |
| 49 | |
| 50 | // Add parallax background if specified |
| 51 | if( this.Reveal.getConfig().parallaxBackgroundImage ) { |
| 52 | |
| 53 | this.element.style.backgroundImage = 'url("' + this.Reveal.getConfig().parallaxBackgroundImage + '")'; |
| 54 | this.element.style.backgroundSize = this.Reveal.getConfig().parallaxBackgroundSize; |
| 55 | this.element.style.backgroundRepeat = this.Reveal.getConfig().parallaxBackgroundRepeat; |
| 56 | this.element.style.backgroundPosition = this.Reveal.getConfig().parallaxBackgroundPosition; |
| 57 | |
| 58 | // Make sure the below properties are set on the element - these properties are |
| 59 | // needed for proper transitions to be set on the element via CSS. To remove |
| 60 | // annoying background slide-in effect when the presentation starts, apply |
| 61 | // these properties after short time delay |
| 62 | setTimeout( () => { |
| 63 | this.Reveal.getRevealElement().classList.add( 'has-parallax-background' ); |
| 64 | }, 1 ); |
| 65 | |
| 66 | } |
| 67 | else { |
| 68 | |
| 69 | this.element.style.backgroundImage = ''; |
| 70 | this.Reveal.getRevealElement().classList.remove( 'has-parallax-background' ); |
| 71 | |
| 72 | } |
| 73 | |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Creates a background for the given slide. |
| 78 | * |
| 79 | * @param {HTMLElement} slide |
| 80 | * @param {HTMLElement} container The element that the background |
| 81 | * should be appended to |
| 82 | * @return {HTMLElement} New background div |
| 83 | */ |
| 84 | createBackground( slide, container ) { |
| 85 | |
| 86 | // Main slide background element |
| 87 | let element = document.createElement( 'div' ); |
| 88 | element.className = 'slide-background ' + slide.className.replace( /present|past|future/, '' ); |
| 89 | |
| 90 | // Inner background element that wraps images/videos/iframes |
| 91 | let contentElement = document.createElement( 'div' ); |
| 92 | contentElement.className = 'slide-background-content'; |
| 93 | |
| 94 | element.appendChild( contentElement ); |
| 95 | container.appendChild( element ); |
| 96 | |
| 97 | slide.slideBackgroundElement = element; |
| 98 | slide.slideBackgroundContentElement = contentElement; |
| 99 | |
| 100 | // Syncs the background to reflect all current background settings |
| 101 | this.sync( slide ); |
| 102 | |
| 103 | return element; |
| 104 | |
| 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Renders all of the visual properties of a slide background |
| 109 | * based on the various background attributes. |
| 110 | * |
| 111 | * @param {HTMLElement} slide |
| 112 | */ |
| 113 | sync( slide ) { |
| 114 | |
| 115 | const element = slide.slideBackgroundElement, |
| 116 | contentElement = slide.slideBackgroundContentElement; |
| 117 | |
| 118 | const data = { |
| 119 | background: slide.getAttribute( 'data-background' ), |
| 120 | backgroundSize: slide.getAttribute( 'data-background-size' ), |
| 121 | backgroundImage: slide.getAttribute( 'data-background-image' ), |
| 122 | backgroundVideo: slide.getAttribute( 'data-background-video' ), |
| 123 | backgroundIframe: slide.getAttribute( 'data-background-iframe' ), |
| 124 | backgroundColor: slide.getAttribute( 'data-background-color' ), |
Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 125 | backgroundGradient: slide.getAttribute( 'data-background-gradient' ), |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 126 | backgroundRepeat: slide.getAttribute( 'data-background-repeat' ), |
| 127 | backgroundPosition: slide.getAttribute( 'data-background-position' ), |
| 128 | backgroundTransition: slide.getAttribute( 'data-background-transition' ), |
| 129 | backgroundOpacity: slide.getAttribute( 'data-background-opacity' ), |
| 130 | }; |
| 131 | |
| 132 | const dataPreload = slide.hasAttribute( 'data-preload' ); |
| 133 | |
| 134 | // Reset the prior background state in case this is not the |
| 135 | // initial sync |
| 136 | slide.classList.remove( 'has-dark-background' ); |
| 137 | slide.classList.remove( 'has-light-background' ); |
| 138 | |
| 139 | element.removeAttribute( 'data-loaded' ); |
| 140 | element.removeAttribute( 'data-background-hash' ); |
| 141 | element.removeAttribute( 'data-background-size' ); |
| 142 | element.removeAttribute( 'data-background-transition' ); |
| 143 | element.style.backgroundColor = ''; |
| 144 | |
| 145 | contentElement.style.backgroundSize = ''; |
| 146 | contentElement.style.backgroundRepeat = ''; |
| 147 | contentElement.style.backgroundPosition = ''; |
| 148 | contentElement.style.backgroundImage = ''; |
| 149 | contentElement.style.opacity = ''; |
| 150 | contentElement.innerHTML = ''; |
| 151 | |
| 152 | if( data.background ) { |
| 153 | // Auto-wrap image urls in url(...) |
Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 154 | if( /^(http|file|\/\/)/gi.test( data.background ) || /\.(svg|png|jpg|jpeg|gif|bmp|webp)([?#\s]|$)/gi.test( data.background ) ) { |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 155 | slide.setAttribute( 'data-background-image', data.background ); |
| 156 | } |
| 157 | else { |
| 158 | element.style.background = data.background; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | // Create a hash for this combination of background settings. |
| 163 | // This is used to determine when two slide backgrounds are |
| 164 | // the same. |
Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 165 | if( data.background || data.backgroundColor || data.backgroundGradient || data.backgroundImage || data.backgroundVideo || data.backgroundIframe ) { |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 166 | element.setAttribute( 'data-background-hash', data.background + |
| 167 | data.backgroundSize + |
| 168 | data.backgroundImage + |
| 169 | data.backgroundVideo + |
| 170 | data.backgroundIframe + |
| 171 | data.backgroundColor + |
Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 172 | data.backgroundGradient + |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 173 | data.backgroundRepeat + |
| 174 | data.backgroundPosition + |
| 175 | data.backgroundTransition + |
| 176 | data.backgroundOpacity ); |
| 177 | } |
| 178 | |
| 179 | // Additional and optional background properties |
| 180 | if( data.backgroundSize ) element.setAttribute( 'data-background-size', data.backgroundSize ); |
| 181 | if( data.backgroundColor ) element.style.backgroundColor = data.backgroundColor; |
Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 182 | if( data.backgroundGradient ) element.style.backgroundImage = data.backgroundGradient; |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 183 | if( data.backgroundTransition ) element.setAttribute( 'data-background-transition', data.backgroundTransition ); |
| 184 | |
| 185 | if( dataPreload ) element.setAttribute( 'data-preload', '' ); |
| 186 | |
| 187 | // Background image options are set on the content wrapper |
| 188 | if( data.backgroundSize ) contentElement.style.backgroundSize = data.backgroundSize; |
| 189 | if( data.backgroundRepeat ) contentElement.style.backgroundRepeat = data.backgroundRepeat; |
| 190 | if( data.backgroundPosition ) contentElement.style.backgroundPosition = data.backgroundPosition; |
| 191 | if( data.backgroundOpacity ) contentElement.style.opacity = data.backgroundOpacity; |
| 192 | |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 193 | const contrastClass = this.getContrastClass( slide ); |
| 194 | |
| 195 | if( typeof contrastClass === 'string' ) { |
| 196 | slide.classList.add( contrastClass ); |
| 197 | } |
| 198 | |
| 199 | } |
| 200 | |
| 201 | /** |
| 202 | * Returns a class name that can be applied to a slide to indicate |
| 203 | * if it has a light or dark background. |
| 204 | * |
| 205 | * @param {*} slide |
| 206 | * |
| 207 | * @returns {string|null} |
| 208 | */ |
| 209 | getContrastClass( slide ) { |
| 210 | |
| 211 | const element = slide.slideBackgroundElement; |
| 212 | |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 213 | // If this slide has a background color, we add a class that |
| 214 | // signals if it is light or dark. If the slide has no background |
| 215 | // color, no class will be added |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 216 | let contrastColor = slide.getAttribute( 'data-background-color' ); |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 217 | |
| 218 | // If no bg color was found, or it cannot be converted by colorToRgb, check the computed background |
| 219 | if( !contrastColor || !colorToRgb( contrastColor ) ) { |
| 220 | let computedBackgroundStyle = window.getComputedStyle( element ); |
| 221 | if( computedBackgroundStyle && computedBackgroundStyle.backgroundColor ) { |
| 222 | contrastColor = computedBackgroundStyle.backgroundColor; |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | if( contrastColor ) { |
| 227 | const rgb = colorToRgb( contrastColor ); |
| 228 | |
| 229 | // Ignore fully transparent backgrounds. Some browsers return |
| 230 | // rgba(0,0,0,0) when reading the computed background color of |
| 231 | // an element with no background |
| 232 | if( rgb && rgb.a !== 0 ) { |
| 233 | if( colorBrightness( contrastColor ) < 128 ) { |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 234 | return 'has-dark-background'; |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 235 | } |
| 236 | else { |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 237 | return 'has-light-background'; |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 238 | } |
| 239 | } |
| 240 | } |
| 241 | |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 242 | return null; |
| 243 | |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Bubble the 'has-light-background'/'has-dark-background' classes. |
| 248 | */ |
| 249 | bubbleSlideContrastClassToElement( slide, target ) { |
| 250 | |
| 251 | [ 'has-light-background', 'has-dark-background' ].forEach( classToBubble => { |
| 252 | if( slide.classList.contains( classToBubble ) ) { |
| 253 | target.classList.add( classToBubble ); |
| 254 | } |
| 255 | else { |
| 256 | target.classList.remove( classToBubble ); |
| 257 | } |
| 258 | }, this ); |
| 259 | |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 260 | } |
| 261 | |
| 262 | /** |
| 263 | * Updates the background elements to reflect the current |
| 264 | * slide. |
| 265 | * |
| 266 | * @param {boolean} includeAll If true, the backgrounds of |
| 267 | * all vertical slides (not just the present) will be updated. |
| 268 | */ |
| 269 | update( includeAll = false ) { |
| 270 | |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 271 | let config = this.Reveal.getConfig(); |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 272 | let currentSlide = this.Reveal.getCurrentSlide(); |
| 273 | let indices = this.Reveal.getIndices(); |
| 274 | |
| 275 | let currentBackground = null; |
| 276 | |
| 277 | // Reverse past/future classes when in RTL mode |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 278 | let horizontalPast = config.rtl ? 'future' : 'past', |
| 279 | horizontalFuture = config.rtl ? 'past' : 'future'; |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 280 | |
| 281 | // Update the classes of all backgrounds to match the |
| 282 | // states of their slides (past/present/future) |
| 283 | Array.from( this.element.childNodes ).forEach( ( backgroundh, h ) => { |
| 284 | |
| 285 | backgroundh.classList.remove( 'past', 'present', 'future' ); |
| 286 | |
| 287 | if( h < indices.h ) { |
| 288 | backgroundh.classList.add( horizontalPast ); |
| 289 | } |
| 290 | else if ( h > indices.h ) { |
| 291 | backgroundh.classList.add( horizontalFuture ); |
| 292 | } |
| 293 | else { |
| 294 | backgroundh.classList.add( 'present' ); |
| 295 | |
| 296 | // Store a reference to the current background element |
| 297 | currentBackground = backgroundh; |
| 298 | } |
| 299 | |
| 300 | if( includeAll || h === indices.h ) { |
| 301 | queryAll( backgroundh, '.slide-background' ).forEach( ( backgroundv, v ) => { |
| 302 | |
| 303 | backgroundv.classList.remove( 'past', 'present', 'future' ); |
| 304 | |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 305 | const indexv = typeof indices.v === 'number' ? indices.v : 0; |
| 306 | |
| 307 | if( v < indexv ) { |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 308 | backgroundv.classList.add( 'past' ); |
| 309 | } |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 310 | else if ( v > indexv ) { |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 311 | backgroundv.classList.add( 'future' ); |
| 312 | } |
| 313 | else { |
| 314 | backgroundv.classList.add( 'present' ); |
| 315 | |
| 316 | // Only if this is the present horizontal and vertical slide |
| 317 | if( h === indices.h ) currentBackground = backgroundv; |
| 318 | } |
| 319 | |
| 320 | } ); |
| 321 | } |
| 322 | |
| 323 | } ); |
| 324 | |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 325 | // The previous background may refer to a DOM element that has |
| 326 | // been removed after a presentation is synced & bgs are recreated |
| 327 | if( this.previousBackground && !this.previousBackground.closest( 'body' ) ) { |
| 328 | this.previousBackground = null; |
| 329 | } |
| 330 | |
| 331 | if( currentBackground && this.previousBackground ) { |
| 332 | |
| 333 | // Don't transition between identical backgrounds. This |
| 334 | // prevents unwanted flicker. |
| 335 | let previousBackgroundHash = this.previousBackground.getAttribute( 'data-background-hash' ); |
| 336 | let currentBackgroundHash = currentBackground.getAttribute( 'data-background-hash' ); |
| 337 | |
| 338 | if( currentBackgroundHash && currentBackgroundHash === previousBackgroundHash && currentBackground !== this.previousBackground ) { |
| 339 | this.element.classList.add( 'no-transition' ); |
| 340 | |
| 341 | // If multiple slides have the same background video, carry |
| 342 | // the <video> element forward so that it plays continuously |
| 343 | // across multiple slides |
| 344 | const currentVideo = currentBackground.querySelector( 'video' ); |
| 345 | const previousVideo = this.previousBackground.querySelector( 'video' ); |
| 346 | |
| 347 | if( currentVideo && previousVideo ) { |
| 348 | |
| 349 | const currentVideoParent = currentVideo.parentNode; |
| 350 | const previousVideoParent = previousVideo.parentNode; |
| 351 | |
| 352 | // Swap the two videos |
| 353 | previousVideoParent.appendChild( currentVideo ); |
| 354 | currentVideoParent.appendChild( previousVideo ); |
| 355 | |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | } |
| 360 | |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 361 | // Stop content inside of previous backgrounds |
| 362 | if( this.previousBackground ) { |
| 363 | |
| 364 | this.Reveal.slideContent.stopEmbeddedContent( this.previousBackground, { unloadIframes: !this.Reveal.slideContent.shouldPreload( this.previousBackground ) } ); |
| 365 | |
| 366 | } |
| 367 | |
| 368 | // Start content in the current background |
| 369 | if( currentBackground ) { |
| 370 | |
| 371 | this.Reveal.slideContent.startEmbeddedContent( currentBackground ); |
| 372 | |
| 373 | let currentBackgroundContent = currentBackground.querySelector( '.slide-background-content' ); |
| 374 | if( currentBackgroundContent ) { |
| 375 | |
| 376 | let backgroundImageURL = currentBackgroundContent.style.backgroundImage || ''; |
| 377 | |
| 378 | // Restart GIFs (doesn't work in Firefox) |
| 379 | if( /\.gif/i.test( backgroundImageURL ) ) { |
| 380 | currentBackgroundContent.style.backgroundImage = ''; |
| 381 | window.getComputedStyle( currentBackgroundContent ).opacity; |
| 382 | currentBackgroundContent.style.backgroundImage = backgroundImageURL; |
| 383 | } |
| 384 | |
| 385 | } |
| 386 | |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 387 | this.previousBackground = currentBackground; |
| 388 | |
| 389 | } |
| 390 | |
| 391 | // If there's a background brightness flag for this slide, |
| 392 | // bubble it to the .reveal container |
| 393 | if( currentSlide ) { |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 394 | this.bubbleSlideContrastClassToElement( currentSlide, this.Reveal.getRevealElement() ); |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 395 | } |
| 396 | |
| 397 | // Allow the first background to apply without transition |
| 398 | setTimeout( () => { |
| 399 | this.element.classList.remove( 'no-transition' ); |
Marc Kupietz | 9c036a4 | 2024-05-14 13:17:25 +0200 | [diff] [blame^] | 400 | }, 10 ); |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 401 | |
| 402 | } |
| 403 | |
| 404 | /** |
| 405 | * Updates the position of the parallax background based |
| 406 | * on the current slide index. |
| 407 | */ |
| 408 | updateParallax() { |
| 409 | |
| 410 | let indices = this.Reveal.getIndices(); |
| 411 | |
| 412 | if( this.Reveal.getConfig().parallaxBackgroundImage ) { |
| 413 | |
| 414 | let horizontalSlides = this.Reveal.getHorizontalSlides(), |
| 415 | verticalSlides = this.Reveal.getVerticalSlides(); |
| 416 | |
| 417 | let backgroundSize = this.element.style.backgroundSize.split( ' ' ), |
| 418 | backgroundWidth, backgroundHeight; |
| 419 | |
| 420 | if( backgroundSize.length === 1 ) { |
| 421 | backgroundWidth = backgroundHeight = parseInt( backgroundSize[0], 10 ); |
| 422 | } |
| 423 | else { |
| 424 | backgroundWidth = parseInt( backgroundSize[0], 10 ); |
| 425 | backgroundHeight = parseInt( backgroundSize[1], 10 ); |
| 426 | } |
| 427 | |
| 428 | let slideWidth = this.element.offsetWidth, |
| 429 | horizontalSlideCount = horizontalSlides.length, |
| 430 | horizontalOffsetMultiplier, |
| 431 | horizontalOffset; |
| 432 | |
| 433 | if( typeof this.Reveal.getConfig().parallaxBackgroundHorizontal === 'number' ) { |
| 434 | horizontalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundHorizontal; |
| 435 | } |
| 436 | else { |
| 437 | horizontalOffsetMultiplier = horizontalSlideCount > 1 ? ( backgroundWidth - slideWidth ) / ( horizontalSlideCount-1 ) : 0; |
| 438 | } |
| 439 | |
| 440 | horizontalOffset = horizontalOffsetMultiplier * indices.h * -1; |
| 441 | |
| 442 | let slideHeight = this.element.offsetHeight, |
| 443 | verticalSlideCount = verticalSlides.length, |
| 444 | verticalOffsetMultiplier, |
| 445 | verticalOffset; |
| 446 | |
| 447 | if( typeof this.Reveal.getConfig().parallaxBackgroundVertical === 'number' ) { |
| 448 | verticalOffsetMultiplier = this.Reveal.getConfig().parallaxBackgroundVertical; |
| 449 | } |
| 450 | else { |
| 451 | verticalOffsetMultiplier = ( backgroundHeight - slideHeight ) / ( verticalSlideCount-1 ); |
| 452 | } |
| 453 | |
| 454 | verticalOffset = verticalSlideCount > 0 ? verticalOffsetMultiplier * indices.v : 0; |
| 455 | |
| 456 | this.element.style.backgroundPosition = horizontalOffset + 'px ' + -verticalOffset + 'px'; |
| 457 | |
| 458 | } |
| 459 | |
| 460 | } |
| 461 | |
Marc Kupietz | 09b7575 | 2023-10-07 09:32:19 +0200 | [diff] [blame] | 462 | destroy() { |
| 463 | |
| 464 | this.element.remove(); |
| 465 | |
| 466 | } |
| 467 | |
Christophe Dervieux | e1893ae | 2021-10-07 17:09:02 +0200 | [diff] [blame] | 468 | } |