1// Copyright (C) 2019 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import * as MicroModal from 'micromodal'; 16import * as m from 'mithril'; 17 18// We need any here so we can accept vnodes with arbitrary attrs. 19// tslint:disable-next-line:no-any 20export type AnyAttrsVnode = m.Vnode<any, {}>; 21 22interface ModalDefinition { 23 title: string; 24 content: AnyAttrsVnode; 25 buttons: Button[]; 26} 27 28export interface Button { 29 text: string; 30 primary: boolean; 31 id: string; 32 action: () => void; 33} 34 35// We need to create a div outside of the mithril's render root (<main>), that's 36// why the manual DOM manipulation. 37function getOrCreateDOM() { 38 let div = document.getElementById('main-modal') as HTMLElement; 39 if (div) return div; 40 div = document.createElement('div'); 41 div.id = 'main-modal'; 42 div.classList.add('modal'); 43 div.classList.add('micromodal-slide'); 44 (div as {} as {ariaHidden: boolean}).ariaHidden = true; 45 document.body.appendChild(div); 46 MicroModal.init(); 47 return div; 48} 49 50export async function showModal(attrs: ModalDefinition): Promise<void> { 51 const modal = getOrCreateDOM(); 52 m.render( 53 modal, 54 m('.modal-overlay[data-micromodal-close]', 55 {tabindex: -1}, 56 m('.modal-container[aria-labelledby=mm-title][aria-model][role=dialog]', 57 m('header.modal-header', 58 m('h2.modal-title', {id: 'mm-title'}, attrs.title), 59 m('button.modal-close[aria-label=Close Modal]' + 60 '[data-micromodal-close]')), 61 m('main.modal-content', attrs.content), 62 m('footer.modal-footer', ...makeButtons(attrs.buttons))))); 63 return new Promise(resolve => { 64 MicroModal.show( 65 'main-modal', {onClose: () => resolve(), awaitCloseAnimation: true}); 66 }); 67} 68 69export function hideModel() { 70 MicroModal.close(); 71} 72 73function makeButtons(buttonDefinition: Button[]): Array<m.Vnode<Button>> { 74 const buttons: Array<m.Vnode<Button>> = []; 75 buttonDefinition.forEach(button => { 76 buttons.push( 77 m('button[data-micromodal-close].modal-btn', 78 { 79 class: button.primary ? 'modal-btn-primary' : '', 80 id: button.id, 81 onclick: button.action 82 }, 83 button.text)); 84 }); 85 return buttons; 86} 87