SidebarState.sys.mjs (24493B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; 6 7 const lazy = {}; 8 9 XPCOMUtils.defineLazyPreferenceGetter( 10 lazy, 11 "verticalTabsEnabled", 12 "sidebar.verticalTabs" 13 ); 14 15 const DEFAULT_LAUNCHER_VISIBLE = false; 16 17 /** 18 * The properties that make up a sidebar's UI state. 19 * 20 * @typedef {object} SidebarStateProps 21 * 22 * @property {boolean} command 23 * The id of the current sidebar panel. The panel may be closed and still have a command value. 24 * Re-opening the sidebar panel will then load the current command id. 25 * @property {boolean} panelOpen 26 * Whether there is an open panel. 27 * @property {number} panelWidth 28 * Current width of the sidebar panel. 29 * @property {boolean} launcherVisible 30 * Whether the launcher is visible. 31 * This is always true when the sidebar.visibility pref value is "always-show", and toggle between true/false when visibility is "hide-sidebar" 32 * @property {boolean} launcherExpanded 33 * Whether the launcher is expanded. 34 * When sidebar.visibility pref value is "always-show", the toolbar button serves to toggle this property 35 * @property {boolean} launcherDragActive 36 * Whether the launcher is currently being dragged. 37 * @property {boolean} pinnedTabsDragActive 38 * Whether the pinned tabs container is currently being dragged. 39 * @property {boolean} toolsDragActive 40 * Whether the tools container is currently being dragged. 41 * @property {boolean} launcherHoverActive 42 * Whether the launcher is currently being hovered. 43 * @property {number} launcherWidth 44 * Current width of the sidebar launcher. 45 * @property {number} expandedLauncherWidth 46 * Width of the expanded launcher 47 * @property {number} pinnedTabsHeight 48 * Current height of the pinned tabs container 49 * @property {number} expandedPinnedTabsHeight 50 * Height of the pinned tabs container when the sidebar is expanded 51 * @property {number} collapsedPinnedTabsHeight 52 * Height of the pinned tabs container when the sidebar is collapsed 53 * @property {number} toolsHeight 54 * Current height of the tools container 55 * @property {number} expandedToolsHeight 56 * Height of the tools container when the sidebar is expanded 57 * @property {number} collapsedToolsHeight 58 * Height of the tools container when the sidebar is collapsed 59 */ 60 61 const LAUNCHER_MINIMUM_WIDTH = 100; 62 const SIDEBAR_MAXIMUM_WIDTH = "75vw"; 63 64 const LEGACY_USED_PREF = "sidebar.old-sidebar.has-used"; 65 const REVAMP_USED_PREF = "sidebar.new-sidebar.has-used"; 66 67 /** 68 * A reactive data store for the sidebar's UI state. Similar to Lit's 69 * ReactiveController, any updates to properties can potentially trigger UI 70 * updates, or updates to other properties. 71 */ 72 export class SidebarState { 73 #controller = null; 74 /** @type {SidebarStateProps} */ 75 #props = { 76 ...SidebarState.defaultProperties, 77 }; 78 #launcherEverVisible = false; 79 80 /** @type {SidebarStateProps} */ 81 static defaultProperties = Object.freeze({ 82 command: "", 83 launcherDragActive: false, 84 launcherExpanded: false, 85 launcherHoverActive: false, 86 launcherVisible: false, 87 panelOpen: false, 88 pinnedTabsDragActive: false, 89 toolsDragActive: false, 90 }); 91 92 /** 93 * Construct a new SidebarState. 94 * 95 * @param {SidebarController} controller 96 * The controller this state belongs to. SidebarState is instantiated 97 * per-window, thereby allowing us to retrieve DOM elements attached to 98 * the controller. 99 */ 100 constructor(controller) { 101 this.#controller = controller; 102 this.revampEnabled = controller.sidebarRevampEnabled; 103 this.revampVisibility = controller.sidebarRevampVisibility; 104 105 if (this.revampEnabled) { 106 this.#props.launcherVisible = this.defaultLauncherVisible; 107 } 108 } 109 110 /** 111 * Get the sidebar launcher. 112 * 113 * @returns {HTMLElement} 114 */ 115 get #launcherEl() { 116 return this.#controller.sidebarMain; 117 } 118 119 /** 120 * Get parent element of the sidebar launcher. 121 * 122 * @returns {XULElement} 123 */ 124 get #launcherContainerEl() { 125 return this.#controller.sidebarContainer; 126 } 127 128 /** 129 * Get the sidebar panel element. 130 * 131 * @returns {XULElement} 132 */ 133 get #sidebarBoxEl() { 134 return this.#controller._box; 135 } 136 137 /** 138 * Get the sidebar panel. 139 * 140 * @returns {XULElement} 141 */ 142 get #panelEl() { 143 return this.#controller._box; 144 } 145 146 /** 147 * Get the pinned tabs container element. 148 * 149 * @returns {XULElement} 150 */ 151 get #pinnedTabsContainerEl() { 152 return this.#controller._pinnedTabsContainer; 153 } 154 155 /** 156 * Get the items-wrapper part of the pinned tabs container element. 157 * 158 * @returns {XULElement} 159 */ 160 get #pinnedTabsItemsWrapper() { 161 return this.#pinnedTabsContainerEl.shadowRoot.querySelector( 162 "[part=items-wrapper]" 163 ); 164 } 165 166 /** 167 * Get the tools container element. 168 * 169 * @returns {XULElement} 170 */ 171 get #toolsContainer() { 172 return this.#controller.sidebarMain?.buttonsWrapper; 173 } 174 175 /** 176 * Get the tools button-group element. 177 * 178 * @returns {XULElement} 179 */ 180 get #toolsButtonGroup() { 181 return this.#controller.sidebarMain?.buttonGroup; 182 } 183 184 /** 185 * Get window object from the controller. 186 */ 187 get #controllerGlobal() { 188 return this.#launcherContainerEl.ownerGlobal; 189 } 190 191 /** 192 * Update the starting properties according to external factors such as 193 * window type and user preferences. 194 */ 195 initializeState(showLauncher = this.defaultLauncherVisible) { 196 const isPopup = !this.#controllerGlobal.toolbar.visible; 197 if (isPopup) { 198 // Don't show launcher if we're in a popup window. 199 this.launcherVisible = false; 200 } else { 201 if (lazy.verticalTabsEnabled) { 202 this.#props.launcherExpanded = true; 203 } 204 this.launcherVisible = showLauncher; 205 } 206 207 // Explicitly trigger effects to ensure that the UI is kept up to date. 208 this.launcherExpanded = this.#props.launcherExpanded; 209 } 210 211 /** 212 * Load the state information given by session store, backup state, or 213 * adopted window. 214 * 215 * @param {SidebarStateProps} props 216 * New properties to overwrite the default state with. 217 */ 218 loadInitialState(props) { 219 // Override any initial launcher visible state when the new sidebar has not been 220 // made visible yet 221 let hasPreviousVisibleState = false; 222 if (props.hasOwnProperty("hidden")) { 223 props.launcherVisible = !props.hidden; 224 hasPreviousVisibleState = true; 225 delete props.hidden; 226 } 227 if (props.hasOwnProperty("launcherVisible")) { 228 hasPreviousVisibleState = true; 229 } 230 231 const hasSidebarLauncherBeenVisible = 232 this.#controller.SidebarManager.hasSidebarLauncherBeenVisible; 233 234 // We override a falsey launcherVisible property with the default value if 235 // there's no explicitly visible/hidden state and its not been visible before 236 if ( 237 !props.launcherVisible && 238 !hasPreviousVisibleState && 239 !hasSidebarLauncherBeenVisible 240 ) { 241 props.launcherVisible = this.defaultLauncherVisible; 242 } 243 for (const [key, value] of Object.entries(props)) { 244 if (value === undefined) { 245 // `undefined` means we should use the default value. 246 continue; 247 } 248 switch (key) { 249 case "command": 250 this.command = value; 251 break; 252 case "panelWidth": 253 this.#panelEl.style.width = `${value}px`; 254 break; 255 case "width": 256 this.#panelEl.style.width = value; 257 break; 258 case "expanded": 259 this.launcherExpanded = value; 260 break; 261 case "panelOpen": 262 // we need to know if we have a command value before finalizing panelOpen 263 break; 264 case "expandedPinnedTabsHeight": 265 case "collapsedPinnedTabsHeight": 266 this.updatePinnedTabsHeight(); 267 break; 268 case "expandedToolsHeight": 269 case "collapsedToolsHeight": 270 this.updateToolsHeight(); 271 break; 272 default: 273 this[key] = value; 274 } 275 } 276 277 if (this.command && !props.hasOwnProperty("panelOpen")) { 278 // legacy state saved before panelOpen was a thing 279 props.panelOpen = true; 280 } 281 if (!this.command) { 282 props.panelOpen = false; 283 } 284 this.panelOpen = !!props.panelOpen; 285 if (this.command && this.panelOpen) { 286 this.launcherVisible = true; 287 // show() is async, so make sure we return its promise here 288 return this.#controller.showInitially(this.command); 289 } 290 return this.#controller.hide(); 291 } 292 293 /** 294 * Toggle the value of a boolean property. 295 * 296 * @param {string} key 297 * The property to toggle. 298 */ 299 toggle(key) { 300 if (Object.hasOwn(this.#props, key)) { 301 this[key] = !this[key]; 302 } 303 } 304 305 /** 306 * Serialize the state properties for persistence in session store or prefs. 307 * 308 * @returns {SidebarStateProps} 309 */ 310 getProperties() { 311 const props = { 312 command: this.command, 313 panelOpen: this.panelOpen, 314 panelWidth: this.panelWidth, 315 launcherWidth: convertToInt(this.launcherWidth), 316 expandedLauncherWidth: convertToInt(this.expandedLauncherWidth), 317 launcherExpanded: this.launcherExpanded, 318 launcherVisible: this.launcherVisible, 319 pinnedTabsHeight: this.pinnedTabsHeight, 320 expandedPinnedTabsHeight: this.expandedPinnedTabsHeight, 321 collapsedPinnedTabsHeight: this.collapsedPinnedTabsHeight, 322 toolsHeight: this.toolsHeight, 323 expandedToolsHeight: this.expandedToolsHeight, 324 collapsedToolsHeight: this.collapsedToolsHeight, 325 }; 326 // omit any properties with undefined values' 327 for (let [key, value] of Object.entries(props)) { 328 if (value === undefined) { 329 delete props[key]; 330 } 331 } 332 return props; 333 } 334 335 get panelOpen() { 336 return this.#props.panelOpen; 337 } 338 339 set panelOpen(open) { 340 if (this.#props.panelOpen == open) { 341 return; 342 } 343 this.#props.panelOpen = !!open; 344 if (open) { 345 // Launcher must be visible to open a panel. 346 this.launcherVisible = true; 347 348 Services.prefs.setBoolPref( 349 this.revampEnabled ? REVAMP_USED_PREF : LEGACY_USED_PREF, 350 true 351 ); 352 } 353 354 const mainEl = this.#controller.sidebarContainer; 355 const boxEl = this.#controller._box; 356 const contentAreaEl = 357 this.#controllerGlobal.document.getElementById("tabbrowser-tabbox"); 358 if (mainEl?.toggleAttribute) { 359 mainEl.toggleAttribute("sidebar-panel-open", open); 360 } 361 boxEl.toggleAttribute("sidebar-panel-open", open); 362 contentAreaEl.toggleAttribute("sidebar-panel-open", open); 363 } 364 365 get panelWidth() { 366 // Use the value from `style`. This is a more accurate user preference, as 367 // opposed to what the resize observer gives us. 368 return convertToInt(this.#panelEl?.style.width); 369 } 370 371 set panelWidth(width) { 372 this.#launcherContainerEl.style.maxWidth = `calc(${SIDEBAR_MAXIMUM_WIDTH} - ${width}px)`; 373 } 374 375 get expandedPinnedTabsHeight() { 376 return this.#props.expandedPinnedTabsHeight; 377 } 378 379 set expandedPinnedTabsHeight(height) { 380 this.#props.expandedPinnedTabsHeight = height; 381 this.updatePinnedTabsHeight(); 382 } 383 384 get collapsedPinnedTabsHeight() { 385 return this.#props.collapsedPinnedTabsHeight; 386 } 387 388 set collapsedPinnedTabsHeight(height) { 389 this.#props.collapsedPinnedTabsHeight = height; 390 this.updatePinnedTabsHeight(); 391 } 392 393 get expandedToolsHeight() { 394 return this.#props.expandedToolsHeight; 395 } 396 397 set expandedToolsHeight(height) { 398 this.#props.expandedToolsHeight = height; 399 this.updateToolsHeight(); 400 } 401 402 get collapsedToolsHeight() { 403 return this.#props.collapsedToolsHeight; 404 } 405 406 set collapsedToolsHeight(height) { 407 this.#props.collapsedToolsHeight = height; 408 this.updateToolsHeight(); 409 } 410 411 get defaultLauncherVisible() { 412 if (!this.revampEnabled) { 413 return false; 414 } 415 416 // default/fallback value for vertical tabs is to always be visible initially 417 if (lazy.verticalTabsEnabled) { 418 return true; 419 } 420 return DEFAULT_LAUNCHER_VISIBLE; 421 } 422 423 get launcherVisible() { 424 return this.#props.launcherVisible; 425 } 426 427 get launcherEverVisible() { 428 return this.#launcherEverVisible; 429 } 430 431 /** 432 * Update the launcher `visible` and `expanded` states 433 * 434 * @param {boolean} visible 435 * Show or hide the launcher. Defaults to the value returned by the defaultLauncherVisible getter 436 * @param {boolean} forceExpandValue 437 */ 438 updateVisibility( 439 visible = this.defaultLauncherVisible, 440 forceExpandValue = null 441 ) { 442 switch (this.revampVisibility) { 443 case "hide-sidebar": 444 if (lazy.verticalTabsEnabled) { 445 forceExpandValue = visible; 446 } 447 this.launcherVisible = visible; 448 break; 449 case "always-show": 450 case "expand-on-hover": 451 this.launcherVisible = true; 452 break; 453 } 454 if (forceExpandValue !== null) { 455 this.launcherExpanded = forceExpandValue; 456 } 457 } 458 459 set launcherVisible(visible) { 460 if (!this.revampEnabled) { 461 // Launcher not supported in legacy sidebar. 462 this.#props.launcherVisible = false; 463 this.#launcherContainerEl.hidden = true; 464 return; 465 } 466 this.#props.launcherVisible = visible; 467 if (visible) { 468 this.#launcherEverVisible = true; 469 } 470 this.#launcherContainerEl.hidden = !visible; 471 this.#launcherEl.requestUpdate(); 472 this.#updateTabbrowser(visible); 473 this.#sidebarBoxEl.style.paddingInlineStart = 474 this.panelOpen && !visible ? "var(--space-small)" : "unset"; 475 } 476 477 get launcherExpanded() { 478 return this.#props.launcherExpanded; 479 } 480 481 set launcherExpanded(expanded) { 482 if (!this.revampEnabled) { 483 // Launcher not supported in legacy sidebar. 484 this.#props.launcherExpanded = false; 485 return; 486 } 487 const previousExpanded = this.#props.launcherExpanded; 488 this.#props.launcherExpanded = expanded; 489 this.#launcherEl.expanded = expanded; 490 if (expanded && !previousExpanded) { 491 Glean.sidebar.expand.record(); 492 } 493 // Marking the tab container element as expanded or not simplifies the CSS logic 494 // and selectors considerably. 495 const { tabContainer } = this.#controllerGlobal.gBrowser; 496 const mainEl = this.#controller.sidebarContainer; 497 const splitterEl = this.#controller._launcherSplitter; 498 const boxEl = this.#controller._box; 499 const contentAreaEl = 500 this.#controllerGlobal.document.getElementById("tabbrowser-tabbox"); 501 tabContainer.toggleAttribute("expanded", expanded); 502 if (mainEl?.toggleAttribute) { 503 mainEl.toggleAttribute("sidebar-launcher-expanded", expanded); 504 } 505 splitterEl?.toggleAttribute("sidebar-launcher-expanded", expanded); 506 boxEl?.toggleAttribute("sidebar-launcher-expanded", expanded); 507 contentAreaEl.toggleAttribute("sidebar-launcher-expanded", expanded); 508 this.#controller.updateToolbarButton(); 509 if (!this.launcherDragActive) { 510 this.#updateLauncherWidth(); 511 } 512 if ( 513 !this.pinnedTabsDragActive && 514 this.#controller.sidebarRevampVisibility !== "expand-on-hover" 515 ) { 516 this.updatePinnedTabsHeight(); 517 } 518 this.handleUpdateToolsHeightOnLauncherExpanded(); 519 } 520 521 get launcherDragActive() { 522 return this.#props.launcherDragActive; 523 } 524 525 set launcherDragActive(active) { 526 this.#props.launcherDragActive = active; 527 if (active) { 528 // Temporarily disable expand on hover functionality while dragging 529 if (this.#controller.sidebarRevampVisibility === "expand-on-hover") { 530 this.#controller.toggleExpandOnHover(false); 531 } 532 533 this.#launcherEl.toggleAttribute("customWidth", true); 534 } else if (this.launcherWidth < LAUNCHER_MINIMUM_WIDTH) { 535 // Re-enable expand on hover if necessary 536 if (this.#controller.sidebarRevampVisibility === "expand-on-hover") { 537 this.#controller.toggleExpandOnHover(true, true); 538 } 539 540 // Snap back to collapsed state when the new width is too narrow. 541 this.launcherExpanded = false; 542 if (this.revampVisibility === "hide-sidebar") { 543 this.launcherVisible = false; 544 } 545 } else { 546 // Re-enable expand on hover if necessary 547 if (this.#controller.sidebarRevampVisibility === "expand-on-hover") { 548 this.#controller.toggleExpandOnHover(true, true); 549 } 550 551 // Store the user-preferred launcher width. 552 this.expandedLauncherWidth = this.launcherWidth; 553 } 554 const rootEl = this.#controllerGlobal.document.documentElement; 555 rootEl.toggleAttribute("sidebar-launcher-drag-active", active); 556 } 557 558 get pinnedTabsDragActive() { 559 return this.#props.pinnedTabsDragActive; 560 } 561 562 set pinnedTabsDragActive(active) { 563 this.#props.pinnedDragActive = active; 564 565 let itemsWrapperHeight = 566 this.#controllerGlobal.windowUtils.getBoundsWithoutFlushing( 567 this.#pinnedTabsItemsWrapper 568 ).height; 569 let pinnedTabsContainerHeight = 570 this.#controllerGlobal.windowUtils.getBoundsWithoutFlushing( 571 this.#pinnedTabsContainerEl 572 ).height; 573 if (!active) { 574 this.pinnedTabsHeight = Math.min( 575 pinnedTabsContainerHeight, 576 itemsWrapperHeight 577 ); 578 // Store the user-preferred pinned tabs height. 579 if (this.#props.launcherExpanded) { 580 this.expandedPinnedTabsHeight = this.pinnedTabsHeight; 581 } else { 582 this.collapsedPinnedTabsHeight = this.pinnedTabsHeight; 583 } 584 } 585 } 586 587 get toolsDragActive() { 588 return this.#props.toolsDragActive; 589 } 590 591 set toolsDragActive(active) { 592 this.#props.toolsDragActive = active; 593 let maxToolsHeight = this.maxToolsHeight; 594 if (!active && this.#toolsContainer) { 595 let buttonGroupHeight = 596 this.#controllerGlobal.windowUtils.getBoundsWithoutFlushing( 597 this.#toolsContainer 598 ).height; 599 this.toolsHeight = 600 buttonGroupHeight > maxToolsHeight ? maxToolsHeight : buttonGroupHeight; 601 if ( 602 buttonGroupHeight > maxToolsHeight && 603 this.#controller.sidebarRevampVisibility !== "expand-on-hover" 604 ) { 605 this.#launcherEl.shouldShowOverflowButton = false; 606 } 607 // Store the user-preferred tools height. 608 if (this.#props.launcherExpanded) { 609 this.expandedToolsHeight = this.toolsHeight; 610 } else { 611 this.collapsedToolsHeight = this.toolsHeight; 612 } 613 } 614 } 615 616 get maxToolsHeight() { 617 const FIRST_LAST_TAB_PADDING = 5.8833; 618 if (!this.#toolsButtonGroup) { 619 return null; 620 } 621 let referenceToolButton; 622 if (this.#toolsButtonGroup.children.length > 1) { 623 referenceToolButton = this.#toolsButtonGroup.children[1]; 624 } else { 625 referenceToolButton = this.#toolsButtonGroup.children[0]; 626 } 627 let toolRect = 628 this.#controllerGlobal.windowUtils.getBoundsWithoutFlushing( 629 referenceToolButton 630 ); 631 let extraPadding = 0; 632 if (this.#toolsButtonGroup.children.length >= 3) { 633 extraPadding = FIRST_LAST_TAB_PADDING * 2; 634 } else if (this.#toolsButtonGroup.children.length === 2) { 635 extraPadding = FIRST_LAST_TAB_PADDING; 636 } 637 return this.#props.launcherExpanded 638 ? "unset" 639 : toolRect.height * this.#toolsButtonGroup.children.length + extraPadding; 640 } 641 642 get launcherHoverActive() { 643 return this.#props.launcherHoverActive; 644 } 645 646 set launcherHoverActive(active) { 647 this.#props.launcherHoverActive = active; 648 } 649 650 get launcherWidth() { 651 return this.#props.launcherWidth; 652 } 653 654 set launcherWidth(width) { 655 this.#props.launcherWidth = width; 656 const { document } = this.#controllerGlobal; 657 if (!document.documentElement.hasAttribute("inDOMFullscreen")) { 658 this.#panelEl.style.maxWidth = `calc(${SIDEBAR_MAXIMUM_WIDTH} - ${width}px)`; 659 // Expand the launcher when it gets wide enough. 660 if (this.launcherDragActive) { 661 this.launcherExpanded = width >= LAUNCHER_MINIMUM_WIDTH; 662 } 663 } 664 } 665 666 get expandedLauncherWidth() { 667 return this.#props.expandedLauncherWidth; 668 } 669 670 set expandedLauncherWidth(width) { 671 this.#props.expandedLauncherWidth = width; 672 this.#updateLauncherWidth(); 673 } 674 675 /** 676 * If the sidebar is expanded, resize the launcher to the user-preferred 677 * width (if available). If it is collapsed, reset the launcher width. 678 */ 679 #updateLauncherWidth() { 680 if (this.launcherExpanded && this.expandedLauncherWidth) { 681 this.#launcherContainerEl.style.width = `${this.expandedLauncherWidth}px`; 682 } else if (!this.launcherExpanded) { 683 this.#launcherContainerEl.style.width = ""; 684 } 685 this.#launcherEl.toggleAttribute( 686 "customWidth", 687 !!this.expandedLauncherWidth 688 ); 689 } 690 691 get pinnedTabsHeight() { 692 return this.#props.pinnedTabsHeight; 693 } 694 695 set pinnedTabsHeight(height) { 696 this.#props.pinnedTabsHeight = height; 697 if (this.launcherExpanded && lazy.verticalTabsEnabled) { 698 this.expandedPinnedTabsHeight = height; 699 } else if (lazy.verticalTabsEnabled) { 700 this.collapsedPinnedTabsHeight = height; 701 } 702 } 703 704 get toolsHeight() { 705 return this.#props.toolsHeight; 706 } 707 708 set toolsHeight(height) { 709 this.#props.toolsHeight = height; 710 if (this.launcherExpanded) { 711 this.expandedToolsHeight = height; 712 } else { 713 this.collapsedToolsHeight = height; 714 } 715 } 716 717 /** 718 * When the sidebar is expanded/collapsed, resize the pinned tabs container to the user-preferred 719 * height (if available). 720 */ 721 updatePinnedTabsHeight() { 722 if (!lazy.verticalTabsEnabled) { 723 if (this.#pinnedTabsContainerEl) { 724 this.#pinnedTabsContainerEl.style.height = ""; 725 } 726 return; 727 } 728 if (this.launcherExpanded && this.expandedPinnedTabsHeight) { 729 this.#pinnedTabsContainerEl.style.height = `${this.expandedPinnedTabsHeight}px`; 730 } else if (!this.launcherExpanded && this.collapsedPinnedTabsHeight) { 731 this.#pinnedTabsContainerEl.style.height = `${this.collapsedPinnedTabsHeight}px`; 732 } 733 } 734 735 /** 736 * When the sidebar is expanded or collapsed, resize the tools container to the expected height. 737 */ 738 handleUpdateToolsHeightOnLauncherExpanded() { 739 if (!this.toolsDragActive) { 740 if (this.#controller.sidebarRevampVisibility !== "expand-on-hover") { 741 this.updateToolsHeight(); 742 } else if (this.#toolsContainer) { 743 this.#toolsContainer.style.height = this.#props.launcherExpanded 744 ? "" 745 : "0"; 746 } 747 } 748 } 749 750 /** 751 * Resize the tools container to the user-preferred height (if available). 752 */ 753 updateToolsHeight() { 754 if (this.#toolsContainer) { 755 if (!lazy.verticalTabsEnabled) { 756 this.#toolsContainer.style.height = ""; 757 return; 758 } 759 760 if ( 761 this.launcherExpanded && 762 this.#props.expandedToolsHeight !== undefined 763 ) { 764 this.#toolsContainer.style.height = `${this.#props.expandedToolsHeight}px`; 765 } else if ( 766 !this.launcherExpanded && 767 this.#props.collapsedToolsHeight !== undefined 768 ) { 769 this.#toolsContainer.style.height = `${this.#props.collapsedToolsHeight}px`; 770 } else if ( 771 (this.launcherExpanded && 772 this.#props.expandedToolsHeight === undefined) || 773 (!this.launcherExpanded && 774 this.#props.collapsedToolsHeight === undefined) 775 ) { 776 this.#toolsContainer.style.height = ""; 777 } 778 } 779 } 780 781 #updateTabbrowser(isSidebarShown) { 782 this.#controllerGlobal.document 783 .getElementById("tabbrowser-tabbox") 784 .toggleAttribute("sidebar-shown", isSidebarShown); 785 } 786 787 get command() { 788 return this.#props.command || ""; 789 } 790 791 set command(id) { 792 if (id && !this.#controller.sidebars.has(id)) { 793 throw new Error("Setting command to an invalid value"); 794 } 795 if (id && id !== this.#props.command) { 796 this.#props.command = id; 797 // We need the attribute to mirror the command property as its used as a CSS hook 798 this.#controller._box.setAttribute("sidebarcommand", id); 799 } else if (!id) { 800 delete this.#props.command; 801 this.#controller._box.setAttribute("sidebarcommand", ""); 802 } 803 } 804 } 805 806 /** 807 * Convert a value to an integer. 808 * 809 * @param {string} value 810 * The value to convert. 811 * @returns {number} 812 * The resulting integer, or `undefined` if it's not a number. 813 */ 814 function convertToInt(value) { 815 const intValue = parseInt(value); 816 if (isNaN(intValue)) { 817 return undefined; 818 } 819 return intValue; 820 }