Update Polymer and pull in iron-list
[chromium-blink-merge.git] / third_party / polymer / v1_0 / components-chromium / paper-dialog-behavior / paper-dialog-behavior-extracted.js
blob099e60760a1105e5e7a266d5a72cd544876bd6fb
3 /**
4 Use `Polymer.PaperDialogBehavior` and `paper-dialog-common.css` to implement a Material Design
5 dialog.
7 For example, if `<paper-dialog-impl>` implements this behavior:
9     <paper-dialog-impl>
10         <h2>Header</h2>
11         <div>Dialog body</div>
12         <div class="buttons">
13             <paper-button dialog-dismiss>Cancel</paper-button>
14             <paper-button dialog-confirm>Accept</paper-button>
15         </div>
16     </paper-dialog-impl>
18 `paper-dialog-common.css` provide styles for a header, content area, and an action area for buttons.
19 Use the `<h2>` tag for the header and the `buttons` class for the action area. You can use the
20 `paper-dialog-scrollable` element (in its own repository) if you need a scrolling content area.
22 Use the `dialog-dismiss` and `dialog-confirm` attributes on interactive controls to close the
23 dialog. If the user dismisses the dialog with `dialog-confirm`, the `closingReason` will update
24 to include `confirmed: true`.
26 ### Styling
28 The following custom properties and mixins are available for styling.
30 Custom property | Description | Default
31 ----------------|-------------|----------
32 `--paper-dialog-background-color` | Dialog background color                     | `--primary-background-color`
33 `--paper-dialog-color`            | Dialog foreground color                     | `--primary-text-color`
34 `--paper-dialog`                  | Mixin applied to the dialog                 | `{}`
35 `--paper-dialog-title`            | Mixin applied to the title (`<h2>`) element | `{}`
36 `--paper-dialog-button-color`     | Button area foreground color                | `--default-primary-color`
38 ### Accessibility
40 This element has `role="dialog"` by default. Depending on the context, it may be more appropriate
41 to override this attribute with `role="alertdialog"`.
43 If `modal` is set, the element will set `aria-modal` and prevent the focus from exiting the element.
44 It will also ensure that focus remains in the dialog.
46 The `aria-labelledby` attribute will be set to the header element, if one exists.
48 @hero hero.svg
49 @demo demo/index.html
50 @polymerBehavior Polymer.PaperDialogBehavior
53   Polymer.PaperDialogBehaviorImpl = {
55     hostAttributes: {
56       'role': 'dialog',
57       'tabindex': '-1'
58     },
60     properties: {
62       /**
63        * If `modal` is true, this implies `no-cancel-on-outside-click` and `with-backdrop`.
64        */
65       modal: {
66         observer: '_modalChanged',
67         type: Boolean,
68         value: false
69       },
71       /** @type {?Node} */
72       _lastFocusedElement: {
73         type: Object
74       },
76       _boundOnFocus: {
77         type: Function,
78         value: function() {
79           return this._onFocus.bind(this);
80         }
81       },
83       _boundOnBackdropClick: {
84         type: Function,
85         value: function() {
86           return this._onBackdropClick.bind(this);
87         }
88       }
90     },
92     listeners: {
93       'click': '_onDialogClick',
94       'iron-overlay-opened': '_onIronOverlayOpened',
95       'iron-overlay-closed': '_onIronOverlayClosed'
96     },
98     attached: function() {
99       this._observer = this._observe(this);
100       this._updateAriaLabelledBy();
101     },
103     detached: function() {
104       if (this._observer) {
105         this._observer.disconnect();
106       }
107     },
109     _observe: function(node) {
110       var observer = new MutationObserver(function() {
111         this._updateAriaLabelledBy();
112       }.bind(this));
113       observer.observe(node, {
114         childList: true,
115         subtree: true
116       });
117       return observer;
118     },
120     _modalChanged: function() {
121       if (this.modal) {
122         this.setAttribute('aria-modal', 'true');
123       } else {
124         this.setAttribute('aria-modal', 'false');
125       }
126       // modal implies noCancelOnOutsideClick and withBackdrop if true, don't overwrite
127       // those properties otherwise.
128       if (this.modal) {
129         this.noCancelOnOutsideClick = true;
130         this.withBackdrop = true;
131       }
132     },
134     _updateAriaLabelledBy: function() {
135       var header = Polymer.dom(this).querySelector('h2');
136       if (!header) {
137         this.removeAttribute('aria-labelledby');
138         return;
139       }
140       var headerId = header.getAttribute('id');
141       if (headerId && this.getAttribute('aria-labelledby') === headerId) {
142         return;
143       }
144       // set aria-describedBy to the header element
145       var labelledById;
146       if (headerId) {
147         labelledById = headerId;
148       } else {
149         labelledById = 'paper-dialog-header-' + new Date().getUTCMilliseconds();
150         header.setAttribute('id', labelledById);
151       }
152       this.setAttribute('aria-labelledby', labelledById);
153     },
155     _updateClosingReasonConfirmed: function(confirmed) {
156       this.closingReason = this.closingReason || {};
157       this.closingReason.confirmed = confirmed;
158     },
160     _onDialogClick: function(event) {
161       var target = event.target;
162       while (target && target !== this) {
163         if (target.hasAttribute) {
164           if (target.hasAttribute('dialog-dismiss')) {
165             this._updateClosingReasonConfirmed(false);
166             this.close();
167             break;
168           } else if (target.hasAttribute('dialog-confirm')) {
169             this._updateClosingReasonConfirmed(true);
170             this.close();
171             break;
172           }
173         }
174         target = target.parentNode;
175       }
176     },
178     _onIronOverlayOpened: function() {
179       if (this.modal) {
180         document.body.addEventListener('focus', this._boundOnFocus, true);
181         this.backdropElement.addEventListener('click', this._boundOnBackdropClick);
182       }
183     },
185     _onIronOverlayClosed: function() {
186       document.body.removeEventListener('focus', this._boundOnFocus, true);
187       this.backdropElement.removeEventListener('click', this._boundOnBackdropClick);
188     },
190     _onFocus: function(event) {
191       if (this.modal) {
192         var target = event.target;
193         while (target && target !== this && target !== document.body) {
194           target = target.parentNode;
195         }
196         if (target) {
197           if (target === document.body) {
198             if (this._lastFocusedElement) {
199               this._lastFocusedElement.focus();
200             } else {
201               this._focusNode.focus();
202             }
203           } else {
204             this._lastFocusedElement = event.target;
205           }
206         }
207       }
208     },
210     _onBackdropClick: function() {
211       if (this.modal) {
212         if (this._lastFocusedElement) {
213           this._lastFocusedElement.focus();
214         } else {
215           this._focusNode.focus();
216         }
217       }
218     }
220   };
222   /** @polymerBehavior */
223   Polymer.PaperDialogBehavior = [Polymer.IronOverlayBehavior, Polymer.PaperDialogBehaviorImpl];