blob: c41f08b6ea30610081f0bc22e537ff31e3f9affa [file] [log] [blame]
Changhao Han006b8c02020-04-27 14:44:051// Copyright 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Changhao Han5040a442020-06-24 11:56:055import * as Common from '../common/common.js';
Changhao Han006b8c02020-04-27 14:44:056import * as UI from '../ui/ui.js';
7
Changhao Han5040a442020-06-24 11:56:058const ls = Common.ls;
9
Changhao Han006b8c02020-04-27 14:44:0510/**
11 * @enum {string}
12 * Use a normal object instead of making it null-prototyped because
13 * Closure requires enum initialization to be an object literal.
14 * Will be a proper enum class once this file becomes TypeScript.
15 */
16export const AdornerCategories = {
17 Security: 'Security',
18 Layout: 'Layout',
19 Default: 'Default',
20};
21Object.freeze(AdornerCategories);
22
23const template = document.createElement('template');
24template.innerHTML = `
25 <style>
26 :host {
27 display: inline-flex;
28 }
29
Changhao Han72f885b2020-06-02 13:27:1030 :host(.hidden) {
Changhao Han006b8c02020-04-27 14:44:0531 display: none;
32 }
33
Changhao Han72f885b2020-06-02 13:27:1034 :host(.clickable) {
35 cursor: pointer;
36 }
37
38 :host(:focus) slot {
39 border: var(--adorner-border-focus, 1px solid #1a73e8);
40 }
41
42 :host([aria-pressed=true]) slot {
43 color: var(--adorner-text-color-active, #ffffff);
44 background-color: var(--adorner-background-color-active, #1a73e8);
45 }
46
47 slot {
Changhao Hanbc887de2020-06-22 08:32:0548 display: inline-flex;
49 box-sizing: border-box;
Changhao Han006b8c02020-04-27 14:44:0550 height: 13px;
Changhao Han006b8c02020-04-27 14:44:0551 padding: 0 6px;
52 font-size: 8.5px;
Changhao Han006b8c02020-04-27 14:44:0553 color: var(--adorner-text-color, #3c4043);
54 background-color: var(--adorner-background-color, #f1f3f4);
55 border: var(--adorner-border, 1px solid #dadce0);
56 border-radius: var(--adorner-border-radius, 10px);
57 }
58
Changhao Hanbc887de2020-06-22 08:32:0559 ::slotted(*) {
60 height: 10px;
61 }
Changhao Han006b8c02020-04-27 14:44:0562 </style>
63 <slot name="content"></slot>
64`;
65
66export class Adorner extends HTMLElement {
67 /**
68 *
69 * @param {!HTMLElement} content
70 * @param {string} name
Changhao Han72f885b2020-06-02 13:27:1071 * @param {!{category: (!AdornerCategories|undefined)}} options
Changhao Han006b8c02020-04-27 14:44:0572 * @return {!Adorner}
73 */
Changhao Han72f885b2020-06-02 13:27:1074 // @ts-ignore typedef TODO(changhaohan): properly type options once this is .ts
Changhao Han006b8c02020-04-27 14:44:0575 static create(content, name, options = {}) {
Changhao Han72f885b2020-06-02 13:27:1076 const {category = AdornerCategories.Default} = options;
77
Changhao Han006b8c02020-04-27 14:44:0578 const adorner = /** @type {!Adorner} */ (document.createElement('devtools-adorner'));
79 content.slot = 'content';
80 adorner.append(content);
81
82 adorner.name = name;
Changhao Han72f885b2020-06-02 13:27:1083 adorner.category = category;
Changhao Han006b8c02020-04-27 14:44:0584
Changhao Han006b8c02020-04-27 14:44:0585 return adorner;
86 }
87
88 constructor() {
89 super();
90
91 const shadowRoot = this.attachShadow({mode: 'open'});
92 shadowRoot.appendChild(template.content.cloneNode(true));
93
94 this.name = '';
95 this.category = AdornerCategories.Default;
Changhao Han72f885b2020-06-02 13:27:1096 this._isToggle = false;
Changhao Han5040a442020-06-24 11:56:0597 this._ariaLabelDefault = ls`adorner`;
98 this._ariaLabelActive = ls`adorner active`;
Changhao Han006b8c02020-04-27 14:44:0599 }
100
101 /**
102 * @override
103 */
104 connectedCallback() {
Changhao Han72f885b2020-06-02 13:27:10105 if (!this.getAttribute('aria-label')) {
Changhao Han5040a442020-06-24 11:56:05106 UI.ARIAUtils.setAccessibleName(this, ls`${this.name} adorner`);
Changhao Han72f885b2020-06-02 13:27:10107 }
Changhao Han006b8c02020-04-27 14:44:05108 }
109
Changhao Hanb0f1ce12020-07-28 07:22:26110 /**
111 * @return {boolean}
112 */
113 isActive() {
114 return this.getAttribute('aria-pressed') === 'true';
115 }
116
117 /**
118 * Toggle the active state of the adorner. Optionally pass `true` to force-set
119 * an active state; pass `false` to force-set an inactive state.
120 * @param {boolean=} forceActiveState
121 */
122 toggle(forceActiveState) {
Changhao Han72f885b2020-06-02 13:27:10123 if (!this._isToggle) {
124 return;
125 }
Changhao Hanb0f1ce12020-07-28 07:22:26126 const shouldBecomeActive = forceActiveState === undefined ? !this.isActive() : forceActiveState;
127 UI.ARIAUtils.setPressed(this, shouldBecomeActive);
128 UI.ARIAUtils.setAccessibleName(this, shouldBecomeActive ? this._ariaLabelActive : this._ariaLabelDefault);
Changhao Han72f885b2020-06-02 13:27:10129 }
130
Changhao Han006b8c02020-04-27 14:44:05131 show() {
132 this.classList.remove('hidden');
133 }
134
135 hide() {
136 this.classList.add('hidden');
137 }
Changhao Han72f885b2020-06-02 13:27:10138
139 /**
140 * Make adorner interactive by responding to click events with the provided action
141 * and simulating ARIA-capable toggle button behavior.
142 * @param {!EventListener} action
143 * @param {!{isToggle: (boolean|undefined), shouldPropagateOnKeydown: (boolean|undefined), ariaLabelDefault: (string|undefined), ariaLabelActive: (string|undefined)}} options
144 */
145 // @ts-ignore typedef TODO(changhaohan): properly type options once this is .ts
146 addInteraction(action, options = {}) {
147 const {isToggle = false, shouldPropagateOnKeydown = false, ariaLabelDefault, ariaLabelActive} = options;
148
Changhao Han72f885b2020-06-02 13:27:10149 this._isToggle = isToggle;
150
151 if (ariaLabelDefault) {
152 this._ariaLabelDefault = ariaLabelDefault;
153 UI.ARIAUtils.setAccessibleName(this, ariaLabelDefault);
154 }
155
156 if (isToggle) {
Changhao Hanb0f1ce12020-07-28 07:22:26157 this.addEventListener('click', () => {
158 this.toggle();
159 });
Changhao Han72f885b2020-06-02 13:27:10160 if (ariaLabelActive) {
161 this._ariaLabelActive = ariaLabelActive;
162 }
Changhao Hanb0f1ce12020-07-28 07:22:26163 this.toggle(false /* initialize inactive state */);
Changhao Han72f885b2020-06-02 13:27:10164 }
165
Changhao Han1f94e602020-08-06 14:57:50166 this.addEventListener('click', action);
167
Changhao Han72f885b2020-06-02 13:27:10168 // Simulate an ARIA-capable toggle button
169 this.classList.add('clickable');
170 UI.ARIAUtils.markAsButton(this);
171 this.tabIndex = 0;
172 this.addEventListener('keydown', event => {
173 if (event.code === 'Enter' || event.code === 'Space') {
174 this.click();
175 if (!shouldPropagateOnKeydown) {
176 event.stopPropagation();
177 }
178 }
179 });
180 }
Changhao Han006b8c02020-04-27 14:44:05181}
182
183self.customElements.define('devtools-adorner', Adorner);