diff --git a/ui/analyse/src/explorer/explorerConfig.ts b/ui/analyse/src/explorer/explorerConfig.ts index 17d0534ced52..2c7c468bf0a1 100644 --- a/ui/analyse/src/explorer/explorerConfig.ts +++ b/ui/analyse/src/explorer/explorerConfig.ts @@ -333,6 +333,7 @@ const playerModal = (ctrl: ExplorerConfigCtrl) => { ctrl.data.playerName.open(false); ctrl.root.redraw(); }, + modal: true, vnodes: [ h('h2', 'Personal opening explorer'), h('div.input-wrapper', [ diff --git a/ui/analyse/src/keyboard.ts b/ui/analyse/src/keyboard.ts index f170fb024f54..79ac69eb1d03 100644 --- a/ui/analyse/src/keyboard.ts +++ b/ui/analyse/src/keyboard.ts @@ -140,6 +140,7 @@ export function view(ctrl: AnalyseCtrl): VNode { return snabDialog({ class: 'help.keyboard-help', htmlUrl: xhr.url('/analysis/help', { study: !!ctrl.study }), + modal: true, onClose() { ctrl.keyboardHelp = false; ctrl.redraw(); diff --git a/ui/analyse/src/serverSideUnderboard.ts b/ui/analyse/src/serverSideUnderboard.ts index 670677529043..e8f68cc14cb9 100644 --- a/ui/analyse/src/serverSideUnderboard.ts +++ b/ui/analyse/src/serverSideUnderboard.ts @@ -150,7 +150,8 @@ export default function (element: HTMLElement, ctrl: AnalyseCtrl) { const url = `${baseUrl()}/embed/game/${data.game.id}?theme=auto&bg=auto${location.hash}`; const iframe = ``; domDialog({ - show: 'modal', + modal: true, + show: true, htmlText: '
' + $(this).html() + diff --git a/ui/analyse/src/study/chapterEditForm.ts b/ui/analyse/src/study/chapterEditForm.ts index e622342a438a..adc97a939d4a 100644 --- a/ui/analyse/src/study/chapterEditForm.ts +++ b/ui/analyse/src/study/chapterEditForm.ts @@ -68,6 +68,8 @@ export function view(ctrl: StudyChapterEditForm): VNode | undefined { ctrl.current(null); ctrl.redraw(); }, + modal: true, + noClickAway: true, vnodes: [ h('h2', i18n.study.editChapter), h( diff --git a/ui/analyse/src/study/chapterNewForm.ts b/ui/analyse/src/study/chapterNewForm.ts index 9ba070454e48..459cc9d33257 100644 --- a/ui/analyse/src/study/chapterNewForm.ts +++ b/ui/analyse/src/study/chapterNewForm.ts @@ -141,8 +141,8 @@ export function view(ctrl: StudyChapterNewForm): VNode { ctrl.isOpen(false); ctrl.redraw(); }, + modal: true, noClickAway: true, - onInsert: dlg => dlg.show(), vnodes: [ activeTab !== 'edit' && h('h2', [ diff --git a/ui/analyse/src/study/inviteForm.ts b/ui/analyse/src/study/inviteForm.ts index 57b78ed0c656..ab9602a15efd 100644 --- a/ui/analyse/src/study/inviteForm.ts +++ b/ui/analyse/src/study/inviteForm.ts @@ -64,6 +64,7 @@ export function view(ctrl: ReturnType): VNode { ctrl.open(false); ctrl.redraw(); }, + modal: true, noScrollable: true, vnodes: [ h('h2', i18n.study.inviteToTheStudy), diff --git a/ui/analyse/src/study/studyForm.ts b/ui/analyse/src/study/studyForm.ts index ed4428bf7c2a..0549059f7b35 100644 --- a/ui/analyse/src/study/studyForm.ts +++ b/ui/analyse/src/study/studyForm.ts @@ -256,6 +256,7 @@ export function view(ctrl: StudyForm): VNode { ctrl.open(false); ctrl.redraw(); }, + modal: true, noClickAway: true, vnodes: [ h( diff --git a/ui/analyse/src/study/studySearch.ts b/ui/analyse/src/study/studySearch.ts index 3be0b08190c0..41a08916db2d 100644 --- a/ui/analyse/src/study/studySearch.ts +++ b/ui/analyse/src/study/studySearch.ts @@ -55,6 +55,7 @@ export function view(ctrl: SearchCtrl) { onClose() { ctrl.open(false); }, + modal: true, vnodes: [ h('input', { attrs: { autofocus: 1, placeholder: `Search in ${ctrl.studyName}`, value: ctrl.query() }, diff --git a/ui/analyse/src/study/topics.ts b/ui/analyse/src/study/topics.ts index 3a3b887c5902..d9949f56572b 100644 --- a/ui/analyse/src/study/topics.ts +++ b/ui/analyse/src/study/topics.ts @@ -41,6 +41,7 @@ export const formView = (ctrl: TopicsCtrl, userId?: string): VNode => ctrl.open(false); ctrl.redraw(); }, + modal: true, vnodes: [ h('h2', i18n.study.topics), h( diff --git a/ui/analyse/src/view/actionMenu.ts b/ui/analyse/src/view/actionMenu.ts index 18602b38079e..f066a68bea65 100644 --- a/ui/analyse/src/view/actionMenu.ts +++ b/ui/analyse/src/view/actionMenu.ts @@ -117,7 +117,9 @@ export function view(ctrl: AnalyseCtrl): VNode { h( 'a', { - hook: bind('click', () => domDialog({ cash: $('.continue-with.g_' + d.game.id), show: 'modal' })), + hook: bind('click', () => + domDialog({ cash: $('.continue-with.g_' + d.game.id), modal: true, show: true }), + ), attrs: dataIcon(licon.Swords), }, i18n.site.continueFromHere, diff --git a/ui/bits/src/bits.cropDialog.ts b/ui/bits/src/bits.cropDialog.ts index 9414167412ff..7768fe4977c5 100644 --- a/ui/bits/src/bits.cropDialog.ts +++ b/ui/bits/src/bits.cropDialog.ts @@ -66,6 +66,7 @@ export async function initModule(o?: CropOpts): Promise { const dlg = await domDialog({ class: 'crop-viewer', css: [{ hashed: 'bits.cropDialog' }, { url: 'npm/cropper.min.css' }], + modal: true, htmlText: `

Crop image to desired shape

@@ -81,7 +82,7 @@ export async function initModule(o?: CropOpts): Promise { }, }); - dlg.showModal(); + dlg.show(); async function crop() { const view = dlg.view.querySelector('.crop-view') as HTMLElement; diff --git a/ui/bits/src/bits.diagnosticDialog.ts b/ui/bits/src/bits.diagnosticDialog.ts index 98f00fa6bc2e..c11095983256 100644 --- a/ui/bits/src/bits.diagnosticDialog.ts +++ b/ui/bits/src/bits.diagnosticDialog.ts @@ -31,6 +31,7 @@ export async function initModule(): Promise { const dlg = await domDialog({ class: 'diagnostic', css: [{ hashed: 'bits.diagnosticDialog' }], + modal: true, htmlText: `

Diagnostics

${flash}
${escaped}
@@ -52,7 +53,7 @@ export async function initModule(): Promise { setTimeout(() => copied.remove(), 2000); }), ); - dlg.showModal(); + dlg.show(); } const storageProxy: { [key: string]: { storageKey: string; validate: (val?: string) => boolean } } = { diff --git a/ui/bits/src/bits.forum.ts b/ui/bits/src/bits.forum.ts index 34ecccd45524..e8e70f433650 100644 --- a/ui/bits/src/bits.forum.ts +++ b/ui/bits/src/bits.forum.ts @@ -11,6 +11,7 @@ site.load.then(() => { domDialog({ cash: $('.forum-delete-modal'), attrs: { view: { action: link.href } }, + modal: true, }).then(dlg => { $(dlg.view) .find('form') @@ -22,7 +23,7 @@ site.load.then(() => { dlg.close(); }); $(dlg.view).find('form button.cancel').on('click', dlg.close); - dlg.showModal(); + dlg.show(); }); return false; }) @@ -31,10 +32,11 @@ site.load.then(() => { domDialog({ cash: $('.forum-relocate-modal'), attrs: { view: { action: link.href } }, + modal: true, }).then(dlg => { $(dlg.view).find('form').attr('action', link.href); $(dlg.view).find('form button.cancel').on('click', dlg.close); - dlg.showModal(); + dlg.show(); }); return false; }) diff --git a/ui/bits/src/bits.publicChats.ts b/ui/bits/src/bits.publicChats.ts index 0d9819cd2962..913094f026ee 100644 --- a/ui/bits/src/bits.publicChats.ts +++ b/ui/bits/src/bits.publicChats.ts @@ -40,7 +40,7 @@ site.load.then(() => { $('#communication').on('click', '.line:not(.lichess)', function (this: HTMLDivElement) { const $l = $(this); - domDialog({ cash: $('.timeout-modal') }).then(dlg => { + domDialog({ cash: $('.timeout-modal'), modal: true }).then(dlg => { $('.username', dlg.view).text($l.find('.user-link').text()); $('.text', dlg.view).text($l.text().split(' ').slice(1).join(' ')); $('.button', dlg.view).on('click', function (this: HTMLButtonElement) { @@ -58,7 +58,7 @@ site.load.then(() => { }).then(_ => setTimeout(reloadNow, 1000)); dlg.close(); }); - dlg.showModal(); + dlg.show(); }); }); }; diff --git a/ui/ceval/src/util.ts b/ui/ceval/src/util.ts index 7507d84d8346..801705a85620 100644 --- a/ui/ceval/src/util.ts +++ b/ui/ceval/src/util.ts @@ -39,6 +39,7 @@ export const sharedWasmMemory = (lo: number, hi = 32767): WebAssembly.Memory => export function showEngineError(engine: string, error: string): void { domDialog({ class: 'engine-error', + modal: true, htmlText: `

${escapeHtml(engine)} error

` + (error.includes('Status 503') @@ -57,6 +58,6 @@ export function showEngineError(engine: string, error: string): void { window.getSelection()?.addRange(range); }, 0); dlg.view.querySelector('.err')?.addEventListener('focus', select); - dlg.showModal(); + dlg.show(); }); } diff --git a/ui/cli/src/cli.ts b/ui/cli/src/cli.ts index e54c70740d95..aaad653f7748 100644 --- a/ui/cli/src/cli.ts +++ b/ui/cli/src/cli.ts @@ -60,7 +60,8 @@ function help() { domDialog({ css: [{ hashed: 'cli.help' }], class: 'clinput-help', - show: 'modal', + modal: true, + show: true, htmlText: '

Commands

' + commandHelp('/tv /follow', ' ', 'Watch someone play') + diff --git a/ui/common/css/component/_dialog.scss b/ui/common/css/component/_dialog.scss index 4148ec3bd744..cf2f02c6ec64 100644 --- a/ui/common/css/component/_dialog.scss +++ b/ui/common/css/component/_dialog.scss @@ -13,10 +13,13 @@ dialog { @include if-rtl { transform: translate(50%, -50%); } - z-index: $z-modal-111; padding: 0; border: none; background: $c-bg-high; + z-index: $z-modal-111; + &:has(.dialog-content.alert) { + z-index: $z-modal-alert-200; + } &::backdrop { background: $c-page-mask; @@ -80,6 +83,12 @@ dialog { } } +// applied to dialog wrapper div so we can fake HTMLDialogElement.showModal() in a snab render loop +.snab-modal-mask { + @extend %fullscreen-mask, %flex-center; + justify-content: center; +} + .dialog-content { text-align: center; padding: 2em; diff --git a/ui/common/src/dialog.ts b/ui/common/src/dialog.ts index 649fbce17996..0889e41b77d4 100644 --- a/ui/common/src/dialog.ts +++ b/ui/common/src/dialog.ts @@ -13,44 +13,45 @@ export interface Dialog { readonly view: HTMLElement; // your content div readonly returnValue?: 'ok' | 'cancel' | string; // how did we close? - showModal(): Promise; // resolves on close - show(): Promise; // resolves on close - updateActions(actions?: Action | Action[]): void; // set new actions, reattach existing if no arg provided + show(): Promise; // promise resolves on close + updateActions(actions?: Action | Action[]): void; // set new actions or reattach existing if no args close(returnValue?: string): void; } export interface DialogOpts { - class?: string; // zero or more classes for your view div - css?: ({ url: string } | { hashed: string })[]; // fetches hashed or full url css - htmlText?: string; // content, text will be used as-is - cash?: Cash; // content, overrides htmlText, will be cloned and any 'none' class removed - htmlUrl?: string; // content, overrides htmlText and cash, url will be xhr'd - append?: { node: HTMLElement; where?: string; how?: 'after' | 'before' | 'child' }[]; // default 'child' + class?: string; // classes for your view div + css?: ({ url: string } | { hashed: string })[]; // hashed or full url css + htmlText?: string; // content, htmlText is inserted as fragment into DOM + cash?: Cash; // content, precedence over htmlText, cash will be cloned and any 'none' class removed + htmlUrl?: string; // content, precedence over htmlText and cash, url will be xhr'd + append?: { node: HTMLElement; where?: string; how?: 'after' | 'before' | 'child' }[]; // default is 'child' attrs?: { dialog?: Attrs; view?: Attrs }; // optional attrs for dialog and view div - focus?: string; // query selector for element to focus on show - actions?: Action | Action[]; // add listeners to controls - onClose?: (dialog: Dialog) => void; // called when dialog closes + focus?: string; // query selector for focus on show + actions?: Action | Action[]; // add listeners to controls, call updateActions() to reattach + onClose?: (dialog: Dialog) => void; // always called when dialog closes noCloseButton?: boolean; // if true, no upper right corner close button noClickAway?: boolean; // if true, no click-away-to-close noScrollable?: boolean; // if true, no scrollable div container. Fixes dialogs containing an auto-completer + modal?: boolean; // if true, show as modal (darken everything else) } +// show is an explicit property for domDialog. export interface DomDialogOpts extends DialogOpts { parent?: Element; // for centering and dom placement, otherwise fixed on document.body - show?: 'modal' | boolean; // if true, auto-show. if 'modal', auto-show as modal + show?: boolean; // show dialog immediately after construction } -//snabDialog automatically shows as 'modal' unless onInsert callback is supplied +// for snabDialog, show is inferred from !onInsert export interface SnabDialogOpts extends DialogOpts { - vnodes?: LooseVNodes; // content, overrides other content properties - onInsert?: (dialog: Dialog) => void; // if supplied, you must call show() or showModal() manually + vnodes?: LooseVNodes; // content, overrides all other content properties + onInsert?: (dialog: Dialog) => void; // if provided you must also call show } export type ActionListener = (e: Event, dialog: Dialog, action: Action) => void; -// Actions are managed listeners / results that are easily reattached on DOM changes +// Actions are listeners / results for controls // if no event is specified, then 'click' is assumed -// if no selector is given, then the handler is attached to the view div +// if no selector is given, the handler is attached to the dialog-content view div export type Action = | { selector?: string; event?: string | string[]; listener: ActionListener } | { selector?: string; event?: string | string[]; result: string }; @@ -69,6 +70,7 @@ export async function alert(msg: string): Promise { await domDialog({ htmlText: escapeHtml(msg), class: 'alert', + modal: true, show: true, }); } @@ -93,7 +95,9 @@ export async function confirm( class: 'alert', noCloseButton: true, noClickAway: true, - show: 'modal', + modal: true, + show: true, + focus: '.yes', actions: [ { selector: '.yes', result: 'yes' }, { selector: '.no', result: 'no' }, @@ -114,7 +118,8 @@ export async function prompt(msg: string, def: string = ''): Promise { const [html] = await loadAssets(o); @@ -162,20 +167,17 @@ export async function domDialog(o: DomDialogOpts): Promise { (o.parent ?? document.body).appendChild(dialog); - const wrapper = new DialogWrapper(dialog, view, o); - if (o.show && o.show === 'modal') return wrapper.showModal(); - else if (o.show) return wrapper.show(); + const wrapper = new DialogWrapper(dialog, view, o, false); + if (o.show) return wrapper.show(); return wrapper; } -// snab dialogs without an onInsert callback are shown as modal by default. use onInsert callback to handle -// this yourself export function snabDialog(o: SnabDialogOpts): VNode { const ass = loadAssets(o); let dialog: HTMLDialogElement; - return h( + const dialogVNode = h( `dialog${isTouchDevice() ? '.touch-scroll' : ''}`, { key: o.class ?? 'dialog', @@ -204,9 +206,9 @@ export function snabDialog(o: SnabDialogOpts): VNode { hook: onInsert(async view => { const [html] = await ass; if (!o.vnodes && html) view.innerHTML = html; - const wrapper = new DialogWrapper(dialog, view, o); - if (o.onInsert) o.onInsert(wrapper); - else wrapper.showModal(); + const dlg = new DialogWrapper(dialog, view, o, true); + if (o.onInsert) o.onInsert(dlg); + else dlg.show(); }), }, o.vnodes, @@ -214,6 +216,8 @@ export function snabDialog(o: SnabDialogOpts): VNode { ), ], ); + if (!o.modal) return dialogVNode; + return h('div.snab-modal-mask' + (o.onInsert ? '.none' : ''), dialogVNode); } class DialogWrapper implements Dialog { @@ -235,6 +239,7 @@ class DialogWrapper implements Dialog { readonly dialog: HTMLDialogElement, readonly view: HTMLElement, readonly o: DialogOpts, + readonly isSnab: boolean, ) { if (dialogPolyfill) dialogPolyfill.registerDialog(dialog); // ios < 15.4 @@ -287,14 +292,13 @@ class DialogWrapper implements Dialog { } show = (): Promise => { - this.dialog.show(); - this.autoFocus(); - return new Promise(resolve => (this.resolve = resolve)); - }; - - showModal = (): Promise => { - this.view.scrollTop = 0; - this.dialog.showModal(); + if (this.o.modal) this.view.scrollTop = 0; + if (this.isSnab) { + if (this.dialog.parentElement === this.dialog.closest('.snab-modal-mask')) + this.dialog.parentElement?.classList.remove('none'); + this.dialog.show(); + } else if (this.o.modal) this.dialog.showModal(); + else this.dialog.show(); this.autoFocus(); return new Promise(resolve => (this.resolve = resolve)); }; @@ -321,7 +325,8 @@ class DialogWrapper implements Dialog { private autoFocus() { const focus = (this.o.focus ? this.view.querySelector(this.o.focus) : this.view.querySelector('input[autofocus]')) ?? - this.view.querySelectorAll(focusQuery)[1]; + this.view.querySelector(focusQuery); + if (!(focus instanceof HTMLElement)) return; focus.focus(); if (focus instanceof HTMLInputElement) focus.select(); @@ -332,7 +337,8 @@ class DialogWrapper implements Dialog { if (!this.dialog.returnValue) this.dialog.returnValue = 'cancel'; this.resolve?.(this); this.o.onClose?.(this); - this.dialog.remove(); + if (this.dialog.parentElement?.classList.contains('snab-modal-mask')) this.dialog.parentElement.remove(); + else this.dialog.remove(); for (const css of this.o.css ?? []) { if ('hashed' in css) site.asset.removeCssPath(css.hashed); else if ('url' in css) site.asset.removeCss(css.url); diff --git a/ui/common/src/linkPopup.ts b/ui/common/src/linkPopup.ts index 4d6653237c08..0413854a9319 100644 --- a/ui/common/src/linkPopup.ts +++ b/ui/common/src/linkPopup.ts @@ -28,10 +28,11 @@ export const onClick = (a: HTMLLinkElement): boolean => { ${i18n.site.proceedToX(url.host)}
`, + modal: true, }).then(dlg => { $('.cancel', dlg.view).on('click', dlg.close); $('a', dlg.view).on('click', () => setTimeout(dlg.close, 1000)); - dlg.showModal(); + dlg.show(); }); return false; }; diff --git a/ui/editor/src/view.ts b/ui/editor/src/view.ts index 843e30d6d5e7..aa3597fe7340 100644 --- a/ui/editor/src/view.ts +++ b/ui/editor/src/view.ts @@ -247,7 +247,7 @@ function controls(ctrl: EditorCtrl, state: EditorState): VNode { class: { button: true, 'button-empty': true, disabled: !state.playable }, on: { click: () => { - if (state.playable) domDialog({ cash: $('.continue-with'), show: 'modal' }); + if (state.playable) domDialog({ cash: $('.continue-with'), modal: true, show: true }); }, }, }, diff --git a/ui/keyboardMove/src/exports.ts b/ui/keyboardMove/src/exports.ts index e54d570a28ab..638cf2242fba 100644 --- a/ui/keyboardMove/src/exports.ts +++ b/ui/keyboardMove/src/exports.ts @@ -84,6 +84,7 @@ export function render(ctrl: KeyboardMove): VNode { class: 'help.keyboard-move-help', htmlUrl: '/help/keyboard-move', onClose: () => ctrl.helpModalOpen(false), + modal: true, }) : null, ]); diff --git a/ui/lobby/src/view/setup/modal.ts b/ui/lobby/src/view/setup/modal.ts index c40dd937242b..b4344a0d7c18 100644 --- a/ui/lobby/src/view/setup/modal.ts +++ b/ui/lobby/src/view/setup/modal.ts @@ -19,6 +19,7 @@ export default function setupModal(ctrl: LobbyController): MaybeVNode { class: 'game-setup', css: [{ hashed: 'lobby.setup' }], onClose: setupCtrl.closeModal, + modal: true, vnodes: [...views[setupCtrl.gameType](ctrl), ratingView(ctrl)], }); } diff --git a/ui/puzzle/src/keyboard.ts b/ui/puzzle/src/keyboard.ts index 78538e662b01..102e709e2bc3 100644 --- a/ui/puzzle/src/keyboard.ts +++ b/ui/puzzle/src/keyboard.ts @@ -39,4 +39,5 @@ export const view = (ctrl: PuzzleCtrl) => class: 'help', htmlUrl: '/training/help', onClose: () => ctrl.keyboardHelp(false), + modal: true, }); diff --git a/ui/puzzle/src/report.ts b/ui/puzzle/src/report.ts index ed3c7b2f0c28..ed0fff670bb8 100644 --- a/ui/puzzle/src/report.ts +++ b/ui/puzzle/src/report.ts @@ -74,6 +74,7 @@ export default class Report { domDialog({ focus: '.apply', + modal: true, htmlText: '
' + 'Report multiple solutions' + @@ -101,7 +102,7 @@ export default class Report { xhrReport(puzzleId, reason); dlg.close(); }); - dlg.showModal(); + dlg.show(); }); }; } diff --git a/ui/round/src/keyboard.ts b/ui/round/src/keyboard.ts index 7ff532e8971c..d50f57110f07 100644 --- a/ui/round/src/keyboard.ts +++ b/ui/round/src/keyboard.ts @@ -40,4 +40,5 @@ export const view = (ctrl: RoundController): VNode => ctrl.keyboardHelp = false; ctrl.redraw(); }, + modal: true, }); diff --git a/ui/simul/src/view/created.ts b/ui/simul/src/view/created.ts index dffcf2b6b346..55319b316dda 100644 --- a/ui/simul/src/view/created.ts +++ b/ui/simul/src/view/created.ts @@ -38,12 +38,13 @@ export default function (showText: (ctrl: SimulCtrl) => VNode | false) { else domDialog({ cash: $('.simul .continue-with'), + modal: true, }).then(dlg => { $('button.button', dlg.view).on('click', function (this: HTMLButtonElement) { xhr.join(ctrl.data.id, this.dataset.variant as VariantKey); dlg.close(); }); - dlg.showModal(); + dlg.show(); }); }) : {}, diff --git a/ui/tournament/src/view/battle.ts b/ui/tournament/src/view/battle.ts index 9e28afd9cb8b..bf9261ad54b4 100644 --- a/ui/tournament/src/view/battle.ts +++ b/ui/tournament/src/view/battle.ts @@ -13,12 +13,13 @@ export function joinWithTeamSelector(ctrl: TournamentController) { }; return snabDialog({ class: 'team-battle__choice', + modal: true, onInsert(dlg) { $('.team-picker__team', dlg.view).on('click', e => { ctrl.join(e.target.dataset['id']); dlg.close(); }); - dlg.showModal(); + dlg.show(); }, onClose, vnodes: [ diff --git a/ui/voice/src/view.ts b/ui/voice/src/view.ts index 056119daca4d..6be32fbb4270 100644 --- a/ui/voice/src/view.ts +++ b/ui/voice/src/view.ts @@ -150,7 +150,7 @@ function renderHelpModal(ctrl: VoiceCtrl) { } html += ''; dlg.view.innerHTML = html; - if (!dlg.open) dlg.showModal(); + if (!dlg.open) dlg.show(); }; return snabDialog({ @@ -158,6 +158,7 @@ function renderHelpModal(ctrl: VoiceCtrl) { htmlUrl: `/help/voice/${ctrl.moduleId}`, css: [{ hashed: 'voice.move.help' }], onClose: () => ctrl.showHelp(false), + modal: true, onInsert: async dlg => { if (ctrl.showHelp() === 'list') { showMoveList(dlg); @@ -181,7 +182,7 @@ function renderHelpModal(ctrl: VoiceCtrl) { .join(' '); }); $('.all-phrases-button', dlg.view).on('click', () => showMoveList(dlg)); - dlg.showModal(); + dlg.show(); }, }); }