Merge changes from topic "ts-syntax-layer"

* changes:
  Convert files to typescript
  Rename files to preserve history
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index 811b011..b311efc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -165,6 +165,7 @@
       margin-bottom: var(--spacing-l);
       max-height: var(--relation-chain-max-height, 2em);
       overflow: hidden;
+      position: relative; /* for arrowToCurrentChange to have position:absolute and be hidden */
     }
     .commitContainer {
       display: flex;
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index 9b6d674..b28b33a 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -222,17 +222,6 @@
     return GerritNav.getUrlForChangeById(changeNum, project, opt_patchNum);
   }
 
-  _computeChangeContainerClass(currentChange, relatedChange) {
-    const classes = ['changeContainer'];
-    if ([relatedChange, currentChange].includes(undefined)) {
-      return classes;
-    }
-    if (this._changesEqual(relatedChange, currentChange)) {
-      classes.push('thisChange');
-    }
-    return classes.join(' ');
-  }
-
   /**
    * Do the given objects describe the same change? Compares the changes by
    * their numbers.
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
index 1bc99e6..d4cd0f6 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_html.ts
@@ -37,9 +37,8 @@
     .changeContainer {
       display: flex;
     }
-    .changeContainer.thisChange:before {
-      content: '➔';
-      width: 1.2em;
+    .arrowToCurrentChange {
+      position: absolute;
     }
     h4,
     section div {
@@ -105,9 +104,15 @@
           items="[[_relatedResponse.changes]]"
           as="related"
         >
-          <div
-            class$="rightIndent [[_computeChangeContainerClass(change, related)]]"
-          >
+          <template is="dom-if" if="[[_changesEqual(related, change)]]">
+            <span
+              role="img"
+              class="arrowToCurrentChange"
+              aria-label="Arrow marking current change"
+              >➔</span
+            >
+          </template>
+          <div class="rightIndent changeContainer">
             <a
               href$="[[_computeChangeURL(related._change_number, related.project, related._revision_number)]]"
               class$="[[_computeLinkClass(related)]]"
@@ -131,7 +136,15 @@
           items="[[_submittedTogether.changes]]"
           as="related"
         >
-          <div class$="[[_computeChangeContainerClass(change, related)]]">
+          <template is="dom-if" if="[[_changesEqual(related, change)]]">
+            <span
+              role="img"
+              class="arrowToCurrentChange"
+              aria-label="Arrow marking current change"
+              >➔</span
+            >
+          </template>
+          <div class="changeContainer">
             <a
               href$="[[_computeChangeURL(related._number, related.project)]]"
               class$="[[_computeLinkClass(related)]]"
@@ -143,6 +156,8 @@
               tabindex="-1"
               title="Submittable"
               class$="submittableCheck [[_computeLinkClass(related)]]"
+              role="img"
+              aria-label="Submittable"
               >✓</span
             >
           </div>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js
index 7cce9e2..e4aeccf 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.js
@@ -209,19 +209,6 @@
     ]);
   });
 
-  test('_computeChangeContainerClass', () => {
-    const change1 = {change_id: 123, _number: 0};
-    const change2 = {change_id: 456, _change_number: 1};
-    const change3 = {change_id: 123, _number: 2};
-
-    assert.notEqual(element._computeChangeContainerClass(
-        change1, change1).indexOf('thisChange'), -1);
-    assert.equal(element._computeChangeContainerClass(
-        change1, change2).indexOf('thisChange'), -1);
-    assert.equal(element._computeChangeContainerClass(
-        change1, change3).indexOf('thisChange'), -1);
-  });
-
   test('_changesEqual', () => {
     const change1 = {change_id: 123, _number: 0};
     const change2 = {change_id: 456, _number: 1};
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
index 8eeb184..6fc7a17 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper.ts
@@ -65,9 +65,8 @@
   /**
    * Get value of the property from wrapped object. Waits for the property
    * to be initialized if it isn't defined.
-   *
    */
-  get(name: string) {
+  get(name: string): Promise<unknown> {
     if (this._elementHasProperty(name)) {
       return Promise.resolve(this.element[name]);
     }
@@ -80,7 +79,7 @@
       });
       this._promises.set(name, promise);
     }
-    return this._promises.get(name);
+    return this._promises.get(name)!;
   }
 
   /**
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
deleted file mode 100644
index 72cdc46..0000000
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/**
- * @license
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import '../../shared/gr-js-api-interface/gr-js-api-interface.js';
-import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
-import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin.js';
-import {PolymerElement} from '@polymer/polymer/polymer-element.js';
-import {htmlTemplate} from './gr-endpoint-decorator_html.js';
-import {getPluginEndpoints} from '../../shared/gr-js-api-interface/gr-plugin-endpoints.js';
-import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader.js';
-
-const INIT_PROPERTIES_TIMEOUT_MS = 10000;
-
-/** @extends PolymerElement */
-class GrEndpointDecorator extends GestureEventListeners(
-    LegacyElementMixin(
-        PolymerElement)) {
-  static get template() { return htmlTemplate; }
-
-  static get is() { return 'gr-endpoint-decorator'; }
-
-  static get properties() {
-    return {
-      name: String,
-      /** @type {!Map} */
-      _domHooks: {
-        type: Map,
-        value() { return new Map(); },
-      },
-      /**
-       * This map prevents importing the same endpoint twice.
-       * Without caching, if a plugin is loaded after the loaded plugins
-       * callback fires, it will be imported twice and appear twice on the page.
-       *
-       * @type {!Map}
-       */
-      _initializedPlugins: {
-        type: Map,
-        value() { return new Map(); },
-      },
-    };
-  }
-
-  /** @override */
-  detached() {
-    super.detached();
-    for (const [el, domHook] of this._domHooks) {
-      domHook.handleInstanceDetached(el);
-    }
-    getPluginEndpoints().onDetachedEndpoint(this.name, this._endpointCallBack);
-  }
-
-  _initDecoration(name, plugin, slot) {
-    const el = document.createElement(name);
-    return this._initProperties(el, plugin,
-        this.getContentChildren().find(
-            el => el.nodeName !== 'GR-ENDPOINT-PARAM'))
-        .then(el => {
-          const slotEl = slot ?
-            this.querySelector(`gr-endpoint-slot[name=${slot}]`) :
-            null;
-          if (slot && slotEl) {
-            slotEl.parentNode.insertBefore(el, slotEl.nextSibling);
-          } else {
-            this._appendChild(el);
-          }
-          return el;
-        });
-  }
-
-  _initReplacement(name, plugin) {
-    this.getContentChildNodes()
-        .filter(node => node.nodeName !== 'GR-ENDPOINT-PARAM')
-        .forEach(node => node.remove());
-    const el = document.createElement(name);
-    return this._initProperties(el, plugin).then(
-        el => this._appendChild(el));
-  }
-
-  _getEndpointParams() {
-    return Array.from(
-        this.querySelectorAll('gr-endpoint-param'));
-  }
-
-  /**
-   * @param {!Element} el
-   * @param {!Object} plugin
-   * @param {!Element=} opt_content
-   * @return {!Promise<Element>}
-   */
-  _initProperties(el, plugin, opt_content) {
-    el.plugin = plugin;
-    if (opt_content) {
-      el.content = opt_content;
-    }
-    const expectProperties = this._getEndpointParams().map(paramEl => {
-      const helper = plugin.attributeHelper(paramEl);
-      const paramName = paramEl.getAttribute('name');
-      return helper.get('value').then(
-          value => helper.bind('value',
-              value => plugin.attributeHelper(el).set(paramName, value))
-      );
-    });
-    let timeoutId;
-    const timeout = new Promise(
-        resolve => timeoutId = setTimeout(() => {
-          console.warn(
-              'Timeout waiting for endpoint properties initialization: ' +
-            `plugin ${plugin.getPluginName()}, endpoint ${this.name}`);
-        }, INIT_PROPERTIES_TIMEOUT_MS));
-    return Promise.race([timeout, Promise.all(expectProperties)])
-        .then(() => el)
-        .finally(() => {
-          if (timeoutId) clearTimeout(timeoutId);
-        });
-  }
-
-  _appendChild(el) {
-    return this.root.appendChild(el);
-  }
-
-  _initModule({moduleName, plugin, type, domHook, slot}) {
-    const name = plugin.getPluginName() + '.' + moduleName;
-    if (this._initializedPlugins.get(name)) {
-      return;
-    }
-    let initPromise;
-    switch (type) {
-      case 'decorate':
-        initPromise = this._initDecoration(moduleName, plugin, slot);
-        break;
-      case 'replace':
-        initPromise = this._initReplacement(moduleName, plugin);
-        break;
-    }
-    if (!initPromise) {
-      console.warn('Unable to initialize module ' + name);
-    }
-    this._initializedPlugins.set(name, true);
-    initPromise.then(el => {
-      domHook.handleInstanceAttached(el);
-      this._domHooks.set(el, domHook);
-    });
-  }
-
-  /** @override */
-  ready() {
-    super.ready();
-    this._endpointCallBack = this._initModule.bind(this);
-    getPluginEndpoints().onNewEndpoint(this.name, this._endpointCallBack);
-    if (this.name) {
-      getPluginLoader().awaitPluginsLoaded()
-          .then(() => getPluginEndpoints().getAndImportPlugins(this.name))
-          .then(() =>
-            getPluginEndpoints()
-                .getDetails(this.name)
-                .forEach(this._initModule, this)
-          );
-    }
-  }
-}
-
-customElements.define(GrEndpointDecorator.is, GrEndpointDecorator);
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
new file mode 100644
index 0000000..674297d
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.ts
@@ -0,0 +1,199 @@
+/**
+ * @license
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '../../shared/gr-js-api-interface/gr-js-api-interface';
+import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
+import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
+import {PolymerElement} from '@polymer/polymer/polymer-element';
+import {htmlTemplate} from './gr-endpoint-decorator_html';
+import {
+  getPluginEndpoints,
+  ModuleInfo,
+} from '../../shared/gr-js-api-interface/gr-plugin-endpoints';
+import {getPluginLoader} from '../../shared/gr-js-api-interface/gr-plugin-loader';
+import {customElement, property} from '@polymer/decorators';
+import {HookApi, PluginApi} from '../gr-plugin-types';
+
+const INIT_PROPERTIES_TIMEOUT_MS = 10000;
+
+@customElement('gr-endpoint-decorator')
+class GrEndpointDecorator extends GestureEventListeners(
+  LegacyElementMixin(PolymerElement)
+) {
+  static get template() {
+    return htmlTemplate;
+  }
+
+  @property({type: String})
+  name!: string;
+
+  @property({type: Object})
+  _domHooks = new Map<HTMLElement, HookApi>();
+
+  @property({type: Object})
+  _initializedPlugins = new Map<string, boolean>();
+
+  /**
+   * This is the callback that the plugin endpoint manager should be calling
+   * when a new element is registered for this endpoint. It points to
+   * _initModule().
+   */
+  _endpointCallBack: (info: ModuleInfo) => void = () => {};
+
+  /** @override */
+  detached() {
+    super.detached();
+    for (const [el, domHook] of this._domHooks) {
+      domHook.handleInstanceDetached(el);
+    }
+    getPluginEndpoints().onDetachedEndpoint(this.name, this._endpointCallBack);
+  }
+
+  _initDecoration(
+    name: string,
+    plugin: PluginApi,
+    slot?: string
+  ): Promise<HTMLElement> {
+    const el = document.createElement(name);
+    return this._initProperties(
+      el,
+      plugin,
+      this.getContentChildren().find(el => el.nodeName !== 'GR-ENDPOINT-PARAM')
+    ).then(el => {
+      const slotEl = slot
+        ? this.querySelector(`gr-endpoint-slot[name=${slot}]`)
+        : null;
+      if (slot && slotEl?.parentNode) {
+        slotEl.parentNode.insertBefore(el, slotEl.nextSibling);
+      } else {
+        this._appendChild(el);
+      }
+      return el;
+    });
+  }
+
+  _initReplacement(name: string, plugin: PluginApi): Promise<HTMLElement> {
+    this.getContentChildNodes()
+      .filter(node => node.nodeName !== 'GR-ENDPOINT-PARAM')
+      .forEach(node => (node as ChildNode).remove());
+    const el = document.createElement(name);
+    return this._initProperties(el, plugin).then((el: HTMLElement) =>
+      this._appendChild(el)
+    );
+  }
+
+  _getEndpointParams() {
+    return Array.from(this.querySelectorAll('gr-endpoint-param'));
+  }
+
+  _initProperties(
+    htmlEl: HTMLElement,
+    plugin: PluginApi,
+    content?: HTMLElement
+  ) {
+    const el = htmlEl as HTMLElement & {
+      plugin?: PluginApi;
+      content?: HTMLElement;
+    };
+    el.plugin = plugin;
+    if (content) {
+      el.content = content;
+    }
+    const expectProperties = this._getEndpointParams().map(paramEl => {
+      const helper = plugin.attributeHelper(paramEl);
+      const paramName = paramEl.getAttribute('name');
+      if (!paramName) throw Error('plugin endpoint parameter missing a name');
+      return helper
+        .get('value')
+        .then(() =>
+          helper.bind('value', value =>
+            plugin.attributeHelper(el).set(paramName, value)
+          )
+        );
+    });
+    // TODO(TS): Should be a number, but TS thinks that is must be some weird
+    // NodeJS.Timeout object.
+    let timeoutId: any;
+    const timeout = new Promise(
+      () =>
+        (timeoutId = setTimeout(() => {
+          console.warn(
+            'Timeout waiting for endpoint properties initialization: ' +
+              `plugin ${plugin.getPluginName()}, endpoint ${this.name}`
+          );
+        }, INIT_PROPERTIES_TIMEOUT_MS))
+    );
+    return Promise.race([timeout, Promise.all(expectProperties)])
+      .then(() => el)
+      .finally(() => {
+        if (timeoutId) clearTimeout(timeoutId);
+      });
+  }
+
+  _appendChild(el: HTMLElement): HTMLElement {
+    if (!this.root) throw Error('plugin endpoint decorator missing root');
+    return this.root.appendChild(el);
+  }
+
+  _initModule({moduleName, plugin, type, domHook, slot}: ModuleInfo) {
+    const name = plugin.getPluginName() + '.' + moduleName;
+    if (this._initializedPlugins.get(name)) {
+      return;
+    }
+    let initPromise;
+    switch (type) {
+      case 'decorate':
+        initPromise = this._initDecoration(moduleName, plugin, slot);
+        break;
+      case 'replace':
+        initPromise = this._initReplacement(moduleName, plugin);
+        break;
+    }
+    if (!initPromise) {
+      throw Error(`unknown endpoint type ${type} used by plugin ${name}`);
+    }
+    this._initializedPlugins.set(name, true);
+    initPromise.then(el => {
+      if (domHook) {
+        domHook.handleInstanceAttached(el);
+        this._domHooks.set(el, domHook);
+      }
+    });
+  }
+
+  /** @override */
+  ready() {
+    super.ready();
+    this._endpointCallBack = (info: ModuleInfo) => this._initModule(info);
+    getPluginEndpoints().onNewEndpoint(this.name, this._endpointCallBack);
+    if (this.name) {
+      getPluginLoader()
+        .awaitPluginsLoaded()
+        .then(() => getPluginEndpoints().getAndImportPlugins(this.name))
+        .then(() =>
+          getPluginEndpoints()
+            .getDetails(this.name)
+            .forEach(this._initModule, this)
+        );
+    }
+  }
+}
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-endpoint-decorator': GrEndpointDecorator;
+  }
+}
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts b/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts
index 7ac670b..538c78a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-types.ts
@@ -37,6 +37,8 @@
   getAllAttached(): HTMLElement[];
   getLastAttached(): Promise<HTMLElement>;
   getModuleName(): string;
+  handleInstanceDetached(instance: HTMLElement): void;
+  handleInstanceAttached(instance: HTMLElement): void;
 }
 
 export enum TargetElement {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
index 2aabf34..3935ef1 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.ts
@@ -21,7 +21,7 @@
 
 type Callback = (value: any) => void;
 
-interface ModuleInfo {
+export interface ModuleInfo {
   moduleName: string;
   plugin: PluginApi;
   pluginUrl?: URL;
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
index aabbac1..4ac868d 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.ts
@@ -63,7 +63,18 @@
     if (this.focusableNodes) {
       return this.focusableNodes;
     }
-    return super._focusableNodes;
+    // TODO(TS): to avoid ts error for:
+    // Only public and protected methods of the base class are accessible
+    // via the 'super' keyword.
+    // we call IronFocsablesHelper directly here
+    // Currently IronFocsablesHelper is not exported from iron-focusables-helper
+    // as it should so we use Polymer.IronFocsablesHelper here instead
+    // (can not use the IronFocsablesHelperClass
+    // in case different behavior due to singleton)
+    // once the type contains the exported member,
+    // should replace with:
+    // import {IronFocusablesHelper} from '@polymer/iron-overlay-behavior/iron-focusables-helper';
+    return (window.Polymer as any).IronFocusablesHelper.getTabbableNodes(this);
   }
 
   /** @override */
diff --git a/polygerrit-ui/app/scripts/bundled-polymer.js b/polygerrit-ui/app/scripts/bundled-polymer.js
index 780d82a..6fef454 100644
--- a/polygerrit-ui/app/scripts/bundled-polymer.js
+++ b/polygerrit-ui/app/scripts/bundled-polymer.js
@@ -66,6 +66,9 @@
 import 'polymer-bridges/polymer/lib/elements/custom-style_bridge.js';
 import 'polymer-bridges/polymer/lib/legacy/mutable-data-behavior_bridge.js';
 import 'polymer-bridges/polymer/polymer-legacy_bridge.js';
+
+// This is needed due to the Polymer.IronFocusablesHelper in gr-overlay.ts
+import 'polymer-bridges/iron-overlay-behavior/iron-focusables-helper_bridge.js';
 import {importHref} from './import-href.js';
 
 window.Polymer.importHref = importHref;