setting-pane.mjs (4189B)
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 { html } from "chrome://global/content/vendor/lit.all.mjs"; 6 import { MozLitElement } from "chrome://global/content/lit-utils.mjs"; 7 8 /** 9 * @typedef {object} SettingPaneConfig 10 * @property {string} [parent] The pane that links to this one. 11 * @property {string} l10nId Fluent id for the heading/description. 12 * @property {string[]} groupIds What setting groups should be rendered. 13 * @property {string} [iconSrc] Optional icon shown in the page header. 14 * @property {string} [module] Import path for module housing the config. 15 * @property {() => boolean} [visible] If this pane is visible. 16 */ 17 18 export class SettingPane extends MozLitElement { 19 static properties = { 20 name: { type: String }, 21 isSubPane: { type: Boolean }, 22 config: { type: Object }, 23 }; 24 25 static queries = { 26 pageHeaderEl: "moz-page-header", 27 }; 28 29 constructor() { 30 super(); 31 /** @type {string} */ 32 this.name = undefined; 33 /** @type {boolean} */ 34 this.isSubPane = false; 35 /** @type {SettingPaneConfig} */ 36 this.config = undefined; 37 } 38 39 createRenderRoot() { 40 return this; 41 } 42 43 async getUpdateComplete() { 44 let result = await super.getUpdateComplete(); 45 // @ts-ignore bug 1997478 46 await this.pageHeaderEl.updateComplete; 47 return result; 48 } 49 50 goBack() { 51 window.gotoPref(this.config.parent); 52 } 53 54 handleVisibility() { 55 if (this.config.visible) { 56 let visible = this.config.visible(); 57 if (!visible && !this.isSubPane) { 58 let categoryButton = /** @type {XULElement} */ ( 59 document.querySelector(`#categories [value="${this.name}"]`) 60 ); 61 if (categoryButton) { 62 categoryButton.remove(); 63 } 64 this.remove(); 65 } 66 } 67 } 68 69 connectedCallback() { 70 super.connectedCallback(); 71 72 this.handleVisibility(); 73 74 document.addEventListener( 75 "paneshown", 76 /** 77 * @param {CustomEvent} e 78 */ 79 e => { 80 if (this.isSubPane && e.detail.category === this.name) { 81 /** 82 * Automatically focus to the first focusable element in the 83 * sub page when it's accessed, which is always the Back Button. 84 */ 85 this.pageHeaderEl.backButtonEl.focus(); 86 /** 87 * ...but execute moveFocus here to move to the very 88 * next focusable element after the Back button (if there is one). 89 */ 90 Services.focus.moveFocus( 91 window, 92 null, 93 Services.focus.MOVEFOCUS_FORWARD, 94 Services.focus.FLAG_BYJS 95 ); 96 } 97 } 98 ); 99 this.setAttribute("data-category", this.name); 100 this.hidden = true; 101 if (this.isSubPane) { 102 this.setAttribute("data-hidden-from-search", "true"); 103 this.setAttribute("data-subpanel", "true"); 104 this._createCategoryButton(); 105 } 106 } 107 108 init() { 109 if (!this.hasUpdated) { 110 this.performUpdate(); 111 } 112 if (this.config.module) { 113 ChromeUtils.importESModule(this.config.module, { global: "current" }); 114 } 115 for (let groupId of this.config.groupIds) { 116 window.initSettingGroup(groupId); 117 } 118 } 119 120 _createCategoryButton() { 121 let categoryButton = document.createXULElement("richlistitem"); 122 categoryButton.classList.add("category"); 123 if (this.isSubPane) { 124 categoryButton.classList.add("hidden-category"); 125 } 126 categoryButton.setAttribute("value", this.name); 127 document.getElementById("categories").append(categoryButton); 128 } 129 130 /** @param {string} groupId */ 131 groupTemplate(groupId) { 132 return html`<setting-group groupid=${groupId}></setting-group>`; 133 } 134 135 render() { 136 return html` 137 <moz-page-header 138 data-l10n-id=${this.config.l10nId} 139 .iconSrc=${this.config.iconSrc} 140 .backButton=${this.isSubPane} 141 @navigate-back=${this.goBack} 142 ></moz-page-header> 143 ${this.config.groupIds.map(groupId => this.groupTemplate(groupId))} 144 `; 145 } 146 } 147 customElements.define("setting-pane", SettingPane);