]> git.wh0rd.org - tt-rss.git/blob - lib/dijit/Dialog.js.uncompressed.js
merge new hu_HU translation
[tt-rss.git] / lib / dijit / Dialog.js.uncompressed.js
1 require({cache:{
2 'url:dijit/templates/Dialog.html':"<div class=\"dijitDialog\" role=\"dialog\" aria-labelledby=\"${id}_title\">\n\t<div data-dojo-attach-point=\"titleBar\" class=\"dijitDialogTitleBar\">\n\t\t<span data-dojo-attach-point=\"titleNode\" class=\"dijitDialogTitle\" id=\"${id}_title\"\n\t\t\t\trole=\"heading\" level=\"1\"></span>\n\t\t<span data-dojo-attach-point=\"closeButtonNode\" class=\"dijitDialogCloseIcon\" data-dojo-attach-event=\"ondijitclick: onCancel\" title=\"${buttonCancel}\" role=\"button\" tabIndex=\"-1\">\n\t\t\t<span data-dojo-attach-point=\"closeText\" class=\"closeText\" title=\"${buttonCancel}\">x</span>\n\t\t</span>\n\t</div>\n\t<div data-dojo-attach-point=\"containerNode\" class=\"dijitDialogPaneContent\"></div>\n</div>\n"}});
3 define("dijit/Dialog", [
4 "require",
5 "dojo/_base/array", // array.forEach array.indexOf array.map
6 "dojo/_base/connect", // connect._keypress
7 "dojo/_base/declare", // declare
8 "dojo/_base/Deferred", // Deferred
9 "dojo/dom", // dom.isDescendant
10 "dojo/dom-class", // domClass.add domClass.contains
11 "dojo/dom-geometry", // domGeometry.position
12 "dojo/dom-style", // domStyle.set
13 "dojo/_base/event", // event.stop
14 "dojo/_base/fx", // fx.fadeIn fx.fadeOut
15 "dojo/i18n", // i18n.getLocalization
16 "dojo/keys",
17 "dojo/_base/lang", // lang.mixin lang.hitch
18 "dojo/on",
19 "dojo/ready",
20 "dojo/sniff", // has("ie") has("opera") has("dijit-legacy-requires")
21 "dojo/window", // winUtils.getBox, winUtils.get
22 "dojo/dnd/Moveable", // Moveable
23 "dojo/dnd/TimedMoveable", // TimedMoveable
24 "./focus",
25 "./_base/manager", // manager.defaultDuration
26 "./_Widget",
27 "./_TemplatedMixin",
28 "./_CssStateMixin",
29 "./form/_FormMixin",
30 "./_DialogMixin",
31 "./DialogUnderlay",
32 "./layout/ContentPane",
33 "dojo/text!./templates/Dialog.html",
34 "./main", // for back-compat, exporting dijit._underlay (remove in 2.0)
35 "dojo/i18n!./nls/common"
36 ], function(require, array, connect, declare, Deferred,
37 dom, domClass, domGeometry, domStyle, event, fx, i18n, keys, lang, on, ready, has, winUtils,
38 Moveable, TimedMoveable, focus, manager, _Widget, _TemplatedMixin, _CssStateMixin, _FormMixin, _DialogMixin,
39 DialogUnderlay, ContentPane, template, dijit){
40
41 // module:
42 // dijit/Dialog
43
44 /*=====
45 dijit._underlay = function(kwArgs){
46 // summary:
47 // A shared instance of a `dijit.DialogUnderlay`
48 //
49 // description:
50 // A shared instance of a `dijit.DialogUnderlay` created and
51 // used by `dijit.Dialog`, though never created until some Dialog
52 // or subclass thereof is shown.
53 };
54 =====*/
55
56 var _DialogBase = declare("dijit._DialogBase", [_TemplatedMixin, _FormMixin, _DialogMixin, _CssStateMixin], {
57 templateString: template,
58
59 baseClass: "dijitDialog",
60
61 cssStateNodes: {
62 closeButtonNode: "dijitDialogCloseIcon"
63 },
64
65 // Map widget attributes to DOMNode attributes.
66 _setTitleAttr: [
67 { node: "titleNode", type: "innerHTML" },
68 { node: "titleBar", type: "attribute" }
69 ],
70
71 // open: [readonly] Boolean
72 // True if Dialog is currently displayed on screen.
73 open: false,
74
75 // duration: Integer
76 // The time in milliseconds it takes the dialog to fade in and out
77 duration: manager.defaultDuration,
78
79 // refocus: Boolean
80 // A Toggle to modify the default focus behavior of a Dialog, which
81 // is to re-focus the element which had focus before being opened.
82 // False will disable refocusing. Default: true
83 refocus: true,
84
85 // autofocus: Boolean
86 // A Toggle to modify the default focus behavior of a Dialog, which
87 // is to focus on the first dialog element after opening the dialog.
88 // False will disable autofocusing. Default: true
89 autofocus: true,
90
91 // _firstFocusItem: [private readonly] DomNode
92 // The pointer to the first focusable node in the dialog.
93 // Set by `dijit/_DialogMixin._getFocusItems()`.
94 _firstFocusItem: null,
95
96 // _lastFocusItem: [private readonly] DomNode
97 // The pointer to which node has focus prior to our dialog.
98 // Set by `dijit/_DialogMixin._getFocusItems()`.
99 _lastFocusItem: null,
100
101 // doLayout: [protected] Boolean
102 // Don't change this parameter from the default value.
103 // This ContentPane parameter doesn't make sense for Dialog, since Dialog
104 // is never a child of a layout container, nor can you specify the size of
105 // Dialog in order to control the size of an inner widget.
106 doLayout: false,
107
108 // draggable: Boolean
109 // Toggles the moveable aspect of the Dialog. If true, Dialog
110 // can be dragged by it's title. If false it will remain centered
111 // in the viewport.
112 draggable: true,
113
114 _setDraggableAttr: function(/*Boolean*/ val){
115 // Avoid _WidgetBase behavior of copying draggable attribute to this.domNode,
116 // as that prevents text select on modern browsers (#14452)
117 this._set("draggable", val);
118 },
119
120 // aria-describedby: String
121 // Allows the user to add an aria-describedby attribute onto the dialog. The value should
122 // be the id of the container element of text that describes the dialog purpose (usually
123 // the first text in the dialog).
124 // | <div data-dojo-type="dijit/Dialog" aria-describedby="intro" .....>
125 // | <div id="intro">Introductory text</div>
126 // | <div>rest of dialog contents</div>
127 // | </div>
128 "aria-describedby": "",
129
130 // maxRatio: Number
131 // Maximum size to allow the dialog to expand to, relative to viewport size
132 maxRatio: 0.9,
133
134 postMixInProperties: function(){
135 var _nlsResources = i18n.getLocalization("dijit", "common");
136 lang.mixin(this, _nlsResources);
137 this.inherited(arguments);
138 },
139
140 postCreate: function(){
141 domStyle.set(this.domNode, {
142 display: "none",
143 position:"absolute"
144 });
145 this.ownerDocumentBody.appendChild(this.domNode);
146
147 this.inherited(arguments);
148
149 this.connect(this, "onExecute", "hide");
150 this.connect(this, "onCancel", "hide");
151 this._modalconnects = [];
152 },
153
154 onLoad: function(){
155 // summary:
156 // Called when data has been loaded from an href.
157 // Unlike most other callbacks, this function can be connected to (via `dojo.connect`)
158 // but should *not* be overridden.
159 // tags:
160 // callback
161
162 // when href is specified we need to reposition the dialog after the data is loaded
163 // and find the focusable elements
164 this._position();
165 if(this.autofocus && DialogLevelManager.isTop(this)){
166 this._getFocusItems(this.domNode);
167 focus.focus(this._firstFocusItem);
168 }
169 this.inherited(arguments);
170 },
171
172 _onBlur: function(by){
173 this.inherited(arguments);
174
175 // If focus was accidentally removed from the dialog, such as if the user clicked a blank
176 // area of the screen, or clicked the browser's address bar and then tabbed into the page,
177 // then refocus. Won't do anything if focus was removed because the Dialog was closed, or
178 // because a new Dialog popped up on top of the old one.
179 var refocus = lang.hitch(this, function(){
180 if(this.open && !this._destroyed && DialogLevelManager.isTop(this)){
181 this._getFocusItems(this.domNode);
182 focus.focus(this._firstFocusItem);
183 }
184 });
185 if(by == "mouse"){
186 // wait for mouse up, and then refocus dialog; otherwise doesn't work
187 on.once(this.ownerDocument, "mouseup", refocus);
188 }else{
189 refocus();
190 }
191 },
192
193 _endDrag: function(){
194 // summary:
195 // Called after dragging the Dialog. Saves the position of the dialog in the viewport,
196 // and also adjust position to be fully within the viewport, so user doesn't lose access to handle
197 var nodePosition = domGeometry.position(this.domNode),
198 viewport = winUtils.getBox(this.ownerDocument);
199 nodePosition.y = Math.min(Math.max(nodePosition.y, 0), (viewport.h - nodePosition.h));
200 nodePosition.x = Math.min(Math.max(nodePosition.x, 0), (viewport.w - nodePosition.w));
201 this._relativePosition = nodePosition;
202 this._position();
203 },
204
205 _setup: function(){
206 // summary:
207 // Stuff we need to do before showing the Dialog for the first
208 // time (but we defer it until right beforehand, for
209 // performance reasons).
210 // tags:
211 // private
212
213 var node = this.domNode;
214
215 if(this.titleBar && this.draggable){
216 this._moveable = new ((has("ie") == 6) ? TimedMoveable // prevent overload, see #5285
217 : Moveable)(node, { handle: this.titleBar });
218 this.connect(this._moveable, "onMoveStop", "_endDrag");
219 }else{
220 domClass.add(node,"dijitDialogFixed");
221 }
222
223 this.underlayAttrs = {
224 dialogId: this.id,
225 "class": array.map(this["class"].split(/\s/), function(s){ return s+"_underlay"; }).join(" "),
226 ownerDocument: this.ownerDocument
227 };
228 },
229
230 _size: function(){
231 // summary:
232 // If necessary, shrink dialog contents so dialog fits in viewport
233 // tags:
234 // private
235
236 this._checkIfSingleChild();
237
238 // If we resized the dialog contents earlier, reset them back to original size, so
239 // that if the user later increases the viewport size, the dialog can display w/out a scrollbar.
240 // Need to do this before the domGeometry.position(this.domNode) call below.
241 if(this._singleChild){
242 if(typeof this._singleChildOriginalStyle != "undefined"){
243 this._singleChild.domNode.style.cssText = this._singleChildOriginalStyle;
244 delete this._singleChildOriginalStyle;
245 }
246 }else{
247 domStyle.set(this.containerNode, {
248 width:"auto",
249 height:"auto"
250 });
251 }
252
253 var bb = domGeometry.position(this.domNode);
254
255 // Get viewport size but then reduce it by a bit; Dialog should always have some space around it
256 // to indicate that it's a popup. This will also compensate for possible scrollbars on viewport.
257 var viewport = winUtils.getBox(this.ownerDocument);
258 viewport.w *= this.maxRatio;
259 viewport.h *= this.maxRatio;
260
261 if(bb.w >= viewport.w || bb.h >= viewport.h){
262 // Reduce size of dialog contents so that dialog fits in viewport
263
264 var containerSize = domGeometry.position(this.containerNode),
265 w = Math.min(bb.w, viewport.w) - (bb.w - containerSize.w),
266 h = Math.min(bb.h, viewport.h) - (bb.h - containerSize.h);
267
268 if(this._singleChild && this._singleChild.resize){
269 if(typeof this._singleChildOriginalStyle == "undefined"){
270 this._singleChildOriginalStyle = this._singleChild.domNode.style.cssText;
271 }
272 this._singleChild.resize({w: w, h: h});
273 }else{
274 domStyle.set(this.containerNode, {
275 width: w + "px",
276 height: h + "px",
277 overflow: "auto",
278 position: "relative" // workaround IE bug moving scrollbar or dragging dialog
279 });
280 }
281 }else{
282 if(this._singleChild && this._singleChild.resize){
283 this._singleChild.resize();
284 }
285 }
286 },
287
288 _position: function(){
289 // summary:
290 // Position modal dialog in the viewport. If no relative offset
291 // in the viewport has been determined (by dragging, for instance),
292 // center the node. Otherwise, use the Dialog's stored relative offset,
293 // and position the node to top: left: values based on the viewport.
294 if(!domClass.contains(this.ownerDocumentBody, "dojoMove")){ // don't do anything if called during auto-scroll
295 var node = this.domNode,
296 viewport = winUtils.getBox(this.ownerDocument),
297 p = this._relativePosition,
298 bb = p ? null : domGeometry.position(node),
299 l = Math.floor(viewport.l + (p ? p.x : (viewport.w - bb.w) / 2)),
300 t = Math.floor(viewport.t + (p ? p.y : (viewport.h - bb.h) / 2))
301 ;
302 domStyle.set(node,{
303 left: l + "px",
304 top: t + "px"
305 });
306 }
307 },
308
309 _onKey: function(/*Event*/ evt){
310 // summary:
311 // Handles the keyboard events for accessibility reasons
312 // tags:
313 // private
314
315 if(evt.charOrCode){
316 var node = evt.target;
317 if(evt.charOrCode === keys.TAB){
318 this._getFocusItems(this.domNode);
319 }
320 var singleFocusItem = (this._firstFocusItem == this._lastFocusItem);
321 // see if we are shift-tabbing from first focusable item on dialog
322 if(node == this._firstFocusItem && evt.shiftKey && evt.charOrCode === keys.TAB){
323 if(!singleFocusItem){
324 focus.focus(this._lastFocusItem); // send focus to last item in dialog
325 }
326 event.stop(evt);
327 }else if(node == this._lastFocusItem && evt.charOrCode === keys.TAB && !evt.shiftKey){
328 if(!singleFocusItem){
329 focus.focus(this._firstFocusItem); // send focus to first item in dialog
330 }
331 event.stop(evt);
332 }else{
333 // see if the key is for the dialog
334 while(node){
335 if(node == this.domNode || domClass.contains(node, "dijitPopup")){
336 if(evt.charOrCode == keys.ESCAPE){
337 this.onCancel();
338 }else{
339 return; // just let it go
340 }
341 }
342 node = node.parentNode;
343 }
344 // this key is for the disabled document window
345 if(evt.charOrCode !== keys.TAB){ // allow tabbing into the dialog for a11y
346 event.stop(evt);
347 // opera won't tab to a div
348 }else if(!has("opera")){
349 try{
350 this._firstFocusItem.focus();
351 }catch(e){ /*squelch*/ }
352 }
353 }
354 }
355 },
356
357 show: function(){
358 // summary:
359 // Display the dialog
360 // returns: dojo/_base/Deferred
361 // Deferred object that resolves when the display animation is complete
362
363 if(this.open){ return; }
364
365 if(!this._started){
366 this.startup();
367 }
368
369 // first time we show the dialog, there's some initialization stuff to do
370 if(!this._alreadyInitialized){
371 this._setup();
372 this._alreadyInitialized=true;
373 }
374
375 if(this._fadeOutDeferred){
376 this._fadeOutDeferred.cancel();
377 }
378
379 // Recenter Dialog if user scrolls browser. Connecting to document doesn't work on IE, need to use window.
380 var win = winUtils.get(this.ownerDocument);
381 this._modalconnects.push(on(win, "scroll", lang.hitch(this, "resize")));
382
383 this._modalconnects.push(on(this.domNode, connect._keypress, lang.hitch(this, "_onKey")));
384
385 domStyle.set(this.domNode, {
386 opacity:0,
387 display:""
388 });
389
390 this._set("open", true);
391 this._onShow(); // lazy load trigger
392
393 this._size();
394 this._position();
395
396 // fade-in Animation object, setup below
397 var fadeIn;
398
399 this._fadeInDeferred = new Deferred(lang.hitch(this, function(){
400 fadeIn.stop();
401 delete this._fadeInDeferred;
402 }));
403
404 fadeIn = fx.fadeIn({
405 node: this.domNode,
406 duration: this.duration,
407 beforeBegin: lang.hitch(this, function(){
408 DialogLevelManager.show(this, this.underlayAttrs);
409 }),
410 onEnd: lang.hitch(this, function(){
411 if(this.autofocus && DialogLevelManager.isTop(this)){
412 // find focusable items each time dialog is shown since if dialog contains a widget the
413 // first focusable items can change
414 this._getFocusItems(this.domNode);
415 focus.focus(this._firstFocusItem);
416 }
417 this._fadeInDeferred.resolve(true);
418 delete this._fadeInDeferred;
419 })
420 }).play();
421
422 return this._fadeInDeferred;
423 },
424
425 hide: function(){
426 // summary:
427 // Hide the dialog
428 // returns: dojo/_base/Deferred
429 // Deferred object that resolves when the hide animation is complete
430
431 // If we haven't been initialized yet then we aren't showing and we can just return.
432 // Likewise if we are already hidden, or are currently fading out.
433 if(!this._alreadyInitialized || !this.open){
434 return;
435 }
436 if(this._fadeInDeferred){
437 this._fadeInDeferred.cancel();
438 }
439
440 // fade-in Animation object, setup below
441 var fadeOut;
442
443 this._fadeOutDeferred = new Deferred(lang.hitch(this, function(){
444 fadeOut.stop();
445 delete this._fadeOutDeferred;
446 }));
447 // fire onHide when the promise resolves.
448 this._fadeOutDeferred.then(lang.hitch(this, 'onHide'));
449
450 fadeOut = fx.fadeOut({
451 node: this.domNode,
452 duration: this.duration,
453 onEnd: lang.hitch(this, function(){
454 this.domNode.style.display = "none";
455 DialogLevelManager.hide(this);
456 this._fadeOutDeferred.resolve(true);
457 delete this._fadeOutDeferred;
458 })
459 }).play();
460
461 if(this._scrollConnected){
462 this._scrollConnected = false;
463 }
464 var h;
465 while(h = this._modalconnects.pop()){
466 h.remove();
467 }
468
469 if(this._relativePosition){
470 delete this._relativePosition;
471 }
472 this._set("open", false);
473
474 return this._fadeOutDeferred;
475 },
476
477 resize: function(){
478 // summary:
479 // Called when viewport scrolled or size changed. Position the Dialog and the underlay.
480 // tags:
481 // private
482 if(this.domNode.style.display != "none"){
483 if(DialogUnderlay._singleton){ // avoid race condition during show()
484 DialogUnderlay._singleton.layout();
485 }
486 this._position();
487 this._size();
488 }
489 },
490
491 destroy: function(){
492 if(this._fadeInDeferred){
493 this._fadeInDeferred.cancel();
494 }
495 if(this._fadeOutDeferred){
496 this._fadeOutDeferred.cancel();
497 }
498 if(this._moveable){
499 this._moveable.destroy();
500 }
501 var h;
502 while(h = this._modalconnects.pop()){
503 h.remove();
504 }
505
506 DialogLevelManager.hide(this);
507
508 this.inherited(arguments);
509 }
510 });
511
512 var Dialog = declare("dijit.Dialog", [ContentPane, _DialogBase], {
513 // summary:
514 // A modal dialog Widget.
515 // description:
516 // Pops up a modal dialog window, blocking access to the screen
517 // and also graying out the screen Dialog is extended from
518 // ContentPane so it supports all the same parameters (href, etc.).
519 // example:
520 // | <div data-dojo-type="dijit/Dialog" data-dojo-props="href: 'test.html'"></div>
521 // example:
522 // | var foo = new Dialog({ title: "test dialog", content: "test content" };
523 // | foo.placeAt(win.body());
524 // | foo.startup();
525 });
526 Dialog._DialogBase = _DialogBase; // for monkey patching and dojox/widget/DialogSimple
527
528 var DialogLevelManager = Dialog._DialogLevelManager = {
529 // summary:
530 // Controls the various active "levels" on the page, starting with the
531 // stuff initially visible on the page (at z-index 0), and then having an entry for
532 // each Dialog shown.
533
534 _beginZIndex: 950,
535
536 show: function(/*dijit/_WidgetBase*/ dialog, /*Object*/ underlayAttrs){
537 // summary:
538 // Call right before fade-in animation for new dialog.
539 // Saves current focus, displays/adjusts underlay for new dialog,
540 // and sets the z-index of the dialog itself.
541 //
542 // New dialog will be displayed on top of all currently displayed dialogs.
543 //
544 // Caller is responsible for setting focus in new dialog after the fade-in
545 // animation completes.
546
547 // Save current focus
548 ds[ds.length-1].focus = focus.curNode;
549
550 // Display the underlay, or if already displayed then adjust for this new dialog
551 // TODO: one underlay per document (based on dialog.ownerDocument)
552 var underlay = DialogUnderlay._singleton;
553 if(!underlay || underlay._destroyed){
554 underlay = dijit._underlay = DialogUnderlay._singleton = new DialogUnderlay(underlayAttrs);
555 }else{
556 underlay.set(dialog.underlayAttrs);
557 }
558
559 // Set z-index a bit above previous dialog
560 var zIndex = ds[ds.length-1].dialog ? ds[ds.length-1].zIndex + 2 : Dialog._DialogLevelManager._beginZIndex;
561 if(ds.length == 1){ // first dialog
562 underlay.show();
563 }
564 domStyle.set(DialogUnderlay._singleton.domNode, 'zIndex', zIndex - 1);
565
566 // Dialog
567 domStyle.set(dialog.domNode, 'zIndex', zIndex);
568
569 ds.push({dialog: dialog, underlayAttrs: underlayAttrs, zIndex: zIndex});
570 },
571
572 hide: function(/*dijit/_WidgetBase*/ dialog){
573 // summary:
574 // Called when the specified dialog is hidden/destroyed, after the fade-out
575 // animation ends, in order to reset page focus, fix the underlay, etc.
576 // If the specified dialog isn't open then does nothing.
577 //
578 // Caller is responsible for either setting display:none on the dialog domNode,
579 // or calling dijit/popup.hide(), or removing it from the page DOM.
580
581 if(ds[ds.length-1].dialog == dialog){
582 // Removing the top (or only) dialog in the stack, return focus
583 // to previous dialog
584
585 ds.pop();
586
587 var pd = ds[ds.length-1]; // the new active dialog (or the base page itself)
588
589 // Adjust underlay, unless the underlay widget has already been destroyed
590 // because we are being called during page unload (when all widgets are destroyed)
591 if(!DialogUnderlay._singleton._destroyed){
592 if(ds.length == 1){
593 // Returning to original page. Hide the underlay.
594 DialogUnderlay._singleton.hide();
595 }else{
596 // Popping back to previous dialog, adjust underlay.
597 domStyle.set(DialogUnderlay._singleton.domNode, 'zIndex', pd.zIndex - 1);
598 DialogUnderlay._singleton.set(pd.underlayAttrs);
599 }
600 }
601
602 // Adjust focus
603 if(dialog.refocus){
604 // If we are returning control to a previous dialog but for some reason
605 // that dialog didn't have a focused field, set focus to first focusable item.
606 // This situation could happen if two dialogs appeared at nearly the same time,
607 // since a dialog doesn't set it's focus until the fade-in is finished.
608 var focus = pd.focus;
609 if(pd.dialog && (!focus || !dom.isDescendant(focus, pd.dialog.domNode))){
610 pd.dialog._getFocusItems(pd.dialog.domNode);
611 focus = pd.dialog._firstFocusItem;
612 }
613
614 if(focus){
615 // Refocus the button that spawned the Dialog. This will fail in corner cases including
616 // page unload on IE, because the dijit/form/Button that launched the Dialog may get destroyed
617 // before this code runs. (#15058)
618 try{
619 focus.focus();
620 }catch(e){}
621 }
622 }
623 }else{
624 // Removing a dialog out of order (#9944, #10705).
625 // Don't need to mess with underlay or z-index or anything.
626 var idx = array.indexOf(array.map(ds, function(elem){return elem.dialog}), dialog);
627 if(idx != -1){
628 ds.splice(idx, 1);
629 }
630 }
631 },
632
633 isTop: function(/*dijit/_WidgetBase*/ dialog){
634 // summary:
635 // Returns true if specified Dialog is the top in the task
636 return ds[ds.length-1].dialog == dialog;
637 }
638 };
639
640 // Stack representing the various active "levels" on the page, starting with the
641 // stuff initially visible on the page (at z-index 0), and then having an entry for
642 // each Dialog shown.
643 // Each element in stack has form {
644 // dialog: dialogWidget,
645 // focus: returnFromGetFocus(),
646 // underlayAttrs: attributes to set on underlay (when this widget is active)
647 // }
648 var ds = Dialog._dialogStack = [
649 {dialog: null, focus: null, underlayAttrs: null} // entry for stuff at z-index: 0
650 ];
651
652 // Back compat w/1.6, remove for 2.0
653 if(has("dijit-legacy-requires")){
654 ready(0, function(){
655 var requires = ["dijit/TooltipDialog"];
656 require(requires); // use indirection so modules not rolled into a build
657 });
658 }
659
660 return Dialog;
661 });