| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 {DomUtils} from "./tobago-utils"; |
| import {Listener, Phase} from "./tobago-listener"; |
| |
| export class Focus { |
| |
| private static getHidden(): HTMLInputElement { |
| return document.getElementById(DomUtils.page().id + DomUtils.SUB_COMPONENT_SEP + "lastFocusId") as HTMLInputElement; |
| } |
| |
| static setLastFocusId(id: string): void { |
| this.getHidden().value = id; |
| } |
| |
| static getLastFocusId(): string { |
| return this.getHidden().value; |
| } |
| |
| /** |
| * Sets the focus to the requested element or to the first possible if |
| * no element is explicitly requested. |
| * |
| * The priority order is: |
| * - error (the first error element gets the focus) |
| * - auto (the element with the tobago tag attribute focus="true" gets the focus) |
| * - last (the element from the last request with same id gets the focus, not AJAX) |
| * - first (the first input element (without tabindex=-1) gets the focus, not AJAX) |
| */ |
| static init = function (element: HTMLElement) { |
| |
| const activeInputs = Focus.activeInputs(element); |
| |
| for (const focusable of activeInputs) { |
| focusable.addEventListener("focus", function (event: FocusEvent) { |
| const target = event.target as HTMLElement; |
| if (target.style.visibility !== "hidden" && target.style.display != "none") { |
| // remember the last focused element, for later |
| Focus.setLastFocusId(target.id); |
| } |
| }); |
| } |
| |
| // autofocus in popups doesn't work automatically... so we fix that here |
| const modals = document.getElementsByClassName("modal"); |
| for (let i = 0; i < modals.length; i++) { |
| modals.item(i).addEventListener('shown.bs.modal', Focus.initAutoFocus); |
| } |
| |
| for (const hasDanger of DomUtils.selfOrElementsByClassName(element, ".has-danger")) { |
| const activeInputsInsideDanger = Focus.activeInputs(hasDanger); |
| if (activeInputsInsideDanger.length > 0) { |
| activeInputsInsideDanger[0].focus(); |
| return; |
| } |
| } |
| |
| const autoFocus = element.querySelector("[autofocus]"); |
| if (autoFocus) { |
| // nothing to do, because the browser make the work. |
| return; |
| } |
| |
| if (element.parentElement) { |
| // seems to be AJAX, so end here |
| return; |
| } |
| |
| const lastFocusId = Focus.getLastFocusId(); |
| if (lastFocusId) { |
| const element = document.getElementById(lastFocusId); |
| if (element) { |
| element.focus(); |
| } |
| return; |
| } |
| |
| if (activeInputs.length > 0) { |
| activeInputs[0].focus(); |
| return; |
| } |
| }; |
| |
| private static activeInputs(element: HTMLElement) { |
| return DomUtils.selfOrQuerySelectorAll(element, |
| "input:not([type='hidden']):not([disabled]):not([tabindex='-1'])," + |
| "select:not([disabled]):not([tabindex='-1'])," + |
| "textarea:not([disabled]):not([tabindex='-1'])") |
| .filter((element) => |
| element.style.visibility !== "hidden" |
| && element.style.display != "none"); |
| } |
| |
| private static initAutoFocus(event) { |
| const modal = event.currentTarget as HTMLElement; |
| (modal.querySelector("[autofocus]") as HTMLElement).focus(); |
| } |
| } |
| |
| Listener.register(Focus.init, Phase.DOCUMENT_READY); |
| Listener.register(Focus.init, Phase.AFTER_UPDATE); |