]> git.immae.eu Git - perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git/blame - sources/plugins/floatpanel/plugin.js
Upgrade to 4.5.8
[perso/Immae/Projets/packagist/piedsjaloux-ckeditor-component.git] / sources / plugins / floatpanel / plugin.js
CommitLineData
3332bebe
IB
1/**\r
2 * @license Copyright (c) 2003-2016, CKSource - Frederico Knabben. All rights reserved.\r
3 * For licensing, see LICENSE.md or http://ckeditor.com/license\r
4 */\r
5\r
6CKEDITOR.plugins.add( 'floatpanel', {\r
7 requires: 'panel'\r
8} );\r
9\r
10( function() {\r
11 var panels = {};\r
12\r
13 function getPanel( editor, doc, parentElement, definition, level ) {\r
14 // Generates the panel key: docId-eleId-skinName-langDir[-uiColor][-CSSs][-level]\r
15 var key = CKEDITOR.tools.genKey( doc.getUniqueId(), parentElement.getUniqueId(), editor.lang.dir, editor.uiColor || '', definition.css || '', level || '' ),\r
16 panel = panels[ key ];\r
17\r
18 if ( !panel ) {\r
19 panel = panels[ key ] = new CKEDITOR.ui.panel( doc, definition );\r
20 panel.element = parentElement.append( CKEDITOR.dom.element.createFromHtml( panel.render( editor ), doc ) );\r
21\r
22 panel.element.setStyles( {\r
23 display: 'none',\r
24 position: 'absolute'\r
25 } );\r
26 }\r
27\r
28 return panel;\r
29 }\r
30\r
31 /**\r
32 * Represents a floating panel UI element.\r
33 *\r
34 * It is reused by rich combos, color combos, menus, etc.\r
35 * and it renders its content using {@link CKEDITOR.ui.panel}.\r
36 *\r
37 * @class\r
38 * @todo\r
39 */\r
40 CKEDITOR.ui.floatPanel = CKEDITOR.tools.createClass( {\r
41 /**\r
42 * Creates a floatPanel class instance.\r
43 *\r
44 * @constructor\r
45 * @param {CKEDITOR.editor} editor\r
46 * @param {CKEDITOR.dom.element} parentElement\r
47 * @param {Object} definition Definition of the panel that will be floating.\r
48 * @param {Number} level\r
49 */\r
50 $: function( editor, parentElement, definition, level ) {\r
51 definition.forceIFrame = 1;\r
52\r
53 // In case of editor with floating toolbar append panels that should float\r
54 // to the main UI element.\r
55 if ( definition.toolbarRelated && editor.elementMode == CKEDITOR.ELEMENT_MODE_INLINE )\r
56 parentElement = CKEDITOR.document.getById( 'cke_' + editor.name );\r
57\r
58 var doc = parentElement.getDocument(),\r
59 panel = getPanel( editor, doc, parentElement, definition, level || 0 ),\r
60 element = panel.element,\r
61 iframe = element.getFirst(),\r
62 that = this;\r
63\r
64 // Disable native browser menu. (#4825)\r
65 element.disableContextMenu();\r
66\r
67 this.element = element;\r
68\r
69 this._ = {\r
70 editor: editor,\r
71 // The panel that will be floating.\r
72 panel: panel,\r
73 parentElement: parentElement,\r
74 definition: definition,\r
75 document: doc,\r
76 iframe: iframe,\r
77 children: [],\r
78 dir: editor.lang.dir,\r
79 showBlockParams: null\r
80 };\r
81\r
82 editor.on( 'mode', hide );\r
83 editor.on( 'resize', hide );\r
84\r
85 // When resize of the window is triggered floatpanel should be repositioned according to new dimensions.\r
86 // #11724. Fixes issue with undesired panel hiding on Android and iOS.\r
87 doc.getWindow().on( 'resize', function() {\r
88 this.reposition();\r
89 }, this );\r
90\r
91 // We need a wrapper because events implementation doesn't allow to attach\r
92 // one listener more than once for the same event on the same object.\r
93 // Remember that floatPanel#hide is shared between all instances.\r
94 function hide() {\r
95 that.hide();\r
96 }\r
97 },\r
98\r
99 proto: {\r
100 /**\r
101 * @todo\r
102 */\r
103 addBlock: function( name, block ) {\r
104 return this._.panel.addBlock( name, block );\r
105 },\r
106\r
107 /**\r
108 * @todo\r
109 */\r
110 addListBlock: function( name, multiSelect ) {\r
111 return this._.panel.addListBlock( name, multiSelect );\r
112 },\r
113\r
114 /**\r
115 * @todo\r
116 */\r
117 getBlock: function( name ) {\r
118 return this._.panel.getBlock( name );\r
119 },\r
120\r
121 /**\r
122 * Shows the panel block.\r
123 *\r
124 * @param {String} name\r
125 * @param {CKEDITOR.dom.element} offsetParent Positioned parent.\r
126 * @param {Number} corner\r
127 *\r
128 * * For LTR (left to right) oriented editor:\r
129 * * `1` = top-left\r
130 * * `2` = top-right\r
131 * * `3` = bottom-right\r
132 * * `4` = bottom-left\r
133 * * For RTL (right to left):\r
134 * * `1` = top-right\r
135 * * `2` = top-left\r
136 * * `3` = bottom-left\r
137 * * `4` = bottom-right\r
138 *\r
139 * @param {Number} [offsetX=0]\r
140 * @param {Number} [offsetY=0]\r
141 * @param {Function} [callback] A callback function executed when block positioning is done.\r
142 * @todo what do exactly these params mean (especially corner)?\r
143 */\r
144 showBlock: function( name, offsetParent, corner, offsetX, offsetY, callback ) {\r
145 var panel = this._.panel,\r
146 block = panel.showBlock( name );\r
147\r
148 this._.showBlockParams = [].slice.call( arguments );\r
149 this.allowBlur( false );\r
150\r
151 // Record from where the focus is when open panel.\r
152 var editable = this._.editor.editable();\r
153 this._.returnFocus = editable.hasFocus ? editable : new CKEDITOR.dom.element( CKEDITOR.document.$.activeElement );\r
154 this._.hideTimeout = 0;\r
155\r
156 var element = this.element,\r
157 iframe = this._.iframe,\r
158 // Edge prefers iframe's window to the iframe, just like the rest of the browsers (#13143).\r
159 focused = CKEDITOR.env.ie && !CKEDITOR.env.edge ? iframe : new CKEDITOR.dom.window( iframe.$.contentWindow ),\r
160 doc = element.getDocument(),\r
161 positionedAncestor = this._.parentElement.getPositionedAncestor(),\r
162 position = offsetParent.getDocumentPosition( doc ),\r
163 positionedAncestorPosition = positionedAncestor ? positionedAncestor.getDocumentPosition( doc ) : { x: 0, y: 0 },\r
164 rtl = this._.dir == 'rtl',\r
165 left = position.x + ( offsetX || 0 ) - positionedAncestorPosition.x,\r
166 top = position.y + ( offsetY || 0 ) - positionedAncestorPosition.y;\r
167\r
168 // Floating panels are off by (-1px, 0px) in RTL mode. (#3438)\r
169 if ( rtl && ( corner == 1 || corner == 4 ) )\r
170 left += offsetParent.$.offsetWidth;\r
171 else if ( !rtl && ( corner == 2 || corner == 3 ) )\r
172 left += offsetParent.$.offsetWidth - 1;\r
173\r
174 if ( corner == 3 || corner == 4 )\r
175 top += offsetParent.$.offsetHeight - 1;\r
176\r
177 // Memorize offsetParent by it's ID.\r
178 this._.panel._.offsetParentId = offsetParent.getId();\r
179\r
180 element.setStyles( {\r
181 top: top + 'px',\r
182 left: 0,\r
183 display: ''\r
184 } );\r
185\r
186 // Don't use display or visibility style because we need to\r
187 // calculate the rendering layout later and focus the element.\r
188 element.setOpacity( 0 );\r
189\r
190 // To allow the context menu to decrease back their width\r
191 element.getFirst().removeStyle( 'width' );\r
192\r
193 // Report to focus manager.\r
194 this._.editor.focusManager.add( focused );\r
195\r
196 // Configure the IFrame blur event. Do that only once.\r
197 if ( !this._.blurSet ) {\r
198\r
199 // With addEventListener compatible browsers, we must\r
200 // useCapture when registering the focus/blur events to\r
201 // guarantee they will be firing in all situations. (#3068, #3222 )\r
202 CKEDITOR.event.useCapture = true;\r
203\r
204 focused.on( 'blur', function( ev ) {\r
205 // As we are using capture to register the listener,\r
206 // the blur event may get fired even when focusing\r
207 // inside the window itself, so we must ensure the\r
208 // target is out of it.\r
209 if ( !this.allowBlur() || ev.data.getPhase() != CKEDITOR.EVENT_PHASE_AT_TARGET )\r
210 return;\r
211\r
212 if ( this.visible && !this._.activeChild ) {\r
213 // [iOS] Allow hide to be prevented if touch is bound\r
214 // to any parent of the iframe blur happens before touch (#10714).\r
215 if ( CKEDITOR.env.iOS ) {\r
216 if ( !this._.hideTimeout )\r
217 this._.hideTimeout = CKEDITOR.tools.setTimeout( doHide, 0, this );\r
218 } else {\r
219 doHide.call( this );\r
220 }\r
221 }\r
222\r
223 function doHide() {\r
224 // Panel close is caused by user's navigating away the focus, e.g. click outside the panel.\r
225 // DO NOT restore focus in this case.\r
226 delete this._.returnFocus;\r
227 this.hide();\r
228 }\r
229 }, this );\r
230\r
231 focused.on( 'focus', function() {\r
232 this._.focused = true;\r
233 this.hideChild();\r
234 this.allowBlur( true );\r
235 }, this );\r
236\r
237 // [iOS] if touch is bound to any parent of the iframe blur\r
238 // happens twice before touchstart and before touchend (#10714).\r
239 if ( CKEDITOR.env.iOS ) {\r
240 // Prevent false hiding on blur.\r
241 // We don't need to return focus here because touchend will fire anyway.\r
242 // If user scrolls and pointer gets out of the panel area touchend will also fire.\r
243 focused.on( 'touchstart', function() {\r
244 clearTimeout( this._.hideTimeout );\r
245 }, this );\r
246\r
247 // Set focus back to handle blur and hide panel when needed.\r
248 focused.on( 'touchend', function() {\r
249 this._.hideTimeout = 0;\r
250 this.focus();\r
251 }, this );\r
252 }\r
253\r
254 CKEDITOR.event.useCapture = false;\r
255\r
256 this._.blurSet = 1;\r
257 }\r
258\r
259 panel.onEscape = CKEDITOR.tools.bind( function( keystroke ) {\r
260 if ( this.onEscape && this.onEscape( keystroke ) === false )\r
261 return false;\r
262 }, this );\r
263\r
264 CKEDITOR.tools.setTimeout( function() {\r
265 var panelLoad = CKEDITOR.tools.bind( function() {\r
266 var target = element;\r
267\r
268 // Reset panel width as the new content can be narrower\r
269 // than the old one. (#9355)\r
270 target.removeStyle( 'width' );\r
271\r
272 if ( block.autoSize ) {\r
1096cdef
IB
273 var panelDoc = block.element.getDocument(),\r
274 width = ( ( CKEDITOR.env.webkit || CKEDITOR.env.edge ) ? block.element : panelDoc.getBody() ).$.scrollWidth;\r
3332bebe
IB
275\r
276 // Account for extra height needed due to IE quirks box model bug:\r
277 // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug\r
278 // (#3426)\r
279 if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && width > 0 )\r
280 width += ( target.$.offsetWidth || 0 ) - ( target.$.clientWidth || 0 ) + 3;\r
281\r
282 // Add some extra pixels to improve the appearance.\r
283 width += 10;\r
284\r
285 target.setStyle( 'width', width + 'px' );\r
286\r
287 var height = block.element.$.scrollHeight;\r
288\r
289 // Account for extra height needed due to IE quirks box model bug:\r
290 // http://en.wikipedia.org/wiki/Internet_Explorer_box_model_bug\r
291 // (#3426)\r
292 if ( CKEDITOR.env.ie && CKEDITOR.env.quirks && height > 0 )\r
293 height += ( target.$.offsetHeight || 0 ) - ( target.$.clientHeight || 0 ) + 3;\r
294\r
295 target.setStyle( 'height', height + 'px' );\r
296\r
297 // Fix IE < 8 visibility.\r
298 panel._.currentBlock.element.setStyle( 'display', 'none' ).removeStyle( 'display' );\r
299 } else {\r
300 target.removeStyle( 'height' );\r
301 }\r
302\r
303 // Flip panel layout horizontally in RTL with known width.\r
304 if ( rtl )\r
305 left -= element.$.offsetWidth;\r
306\r
307 // Pop the style now for measurement.\r
308 element.setStyle( 'left', left + 'px' );\r
309\r
310 /* panel layout smartly fit the viewport size. */\r
311 var panelElement = panel.element,\r
312 panelWindow = panelElement.getWindow(),\r
313 rect = element.$.getBoundingClientRect(),\r
314 viewportSize = panelWindow.getViewPaneSize();\r
315\r
316 // Compensation for browsers that dont support "width" and "height".\r
317 var rectWidth = rect.width || rect.right - rect.left,\r
318 rectHeight = rect.height || rect.bottom - rect.top;\r
319\r
320 // Check if default horizontal layout is impossible.\r
321 var spaceAfter = rtl ? rect.right : viewportSize.width - rect.left,\r
322 spaceBefore = rtl ? viewportSize.width - rect.right : rect.left;\r
323\r
324 if ( rtl ) {\r
325 if ( spaceAfter < rectWidth ) {\r
326 // Flip to show on right.\r
327 if ( spaceBefore > rectWidth )\r
328 left += rectWidth;\r
329 // Align to window left.\r
330 else if ( viewportSize.width > rectWidth )\r
331 left = left - rect.left;\r
332 // Align to window right, never cutting the panel at right.\r
333 else\r
334 left = left - rect.right + viewportSize.width;\r
335 }\r
336 } else if ( spaceAfter < rectWidth ) {\r
337 // Flip to show on left.\r
338 if ( spaceBefore > rectWidth )\r
339 left -= rectWidth;\r
340 // Align to window right.\r
341 else if ( viewportSize.width > rectWidth )\r
342 left = left - rect.right + viewportSize.width;\r
343 // Align to window left, never cutting the panel at left.\r
344 else\r
345 left = left - rect.left;\r
346 }\r
347\r
348\r
349 // Check if the default vertical layout is possible.\r
350 var spaceBelow = viewportSize.height - rect.top,\r
351 spaceAbove = rect.top;\r
352\r
353 if ( spaceBelow < rectHeight ) {\r
354 // Flip to show above.\r
355 if ( spaceAbove > rectHeight )\r
356 top -= rectHeight;\r
357 // Align to window bottom.\r
358 else if ( viewportSize.height > rectHeight )\r
359 top = top - rect.bottom + viewportSize.height;\r
360 // Align to top, never cutting the panel at top.\r
361 else\r
362 top = top - rect.top;\r
363 }\r
364\r
365 // If IE is in RTL, we have troubles with absolute\r
366 // position and horizontal scrolls. Here we have a\r
367 // series of hacks to workaround it. (#6146)\r
368 if ( CKEDITOR.env.ie ) {\r
369 var offsetParent = new CKEDITOR.dom.element( element.$.offsetParent ),\r
370 scrollParent = offsetParent;\r
371\r
372 // Quirks returns <body>, but standards returns <html>.\r
373 if ( scrollParent.getName() == 'html' )\r
374 scrollParent = scrollParent.getDocument().getBody();\r
375\r
376 if ( scrollParent.getComputedStyle( 'direction' ) == 'rtl' ) {\r
377 // For IE8, there is not much logic on this, but it works.\r
378 if ( CKEDITOR.env.ie8Compat )\r
379 left -= element.getDocument().getDocumentElement().$.scrollLeft * 2;\r
380 else\r
381 left -= ( offsetParent.$.scrollWidth - offsetParent.$.clientWidth );\r
382 }\r
383 }\r
384\r
385 // Trigger the onHide event of the previously active panel to prevent\r
386 // incorrect styles from being applied (#6170)\r
387 var innerElement = element.getFirst(),\r
388 activePanel;\r
389 if ( ( activePanel = innerElement.getCustomData( 'activePanel' ) ) )\r
390 activePanel.onHide && activePanel.onHide.call( this, 1 );\r
391 innerElement.setCustomData( 'activePanel', this );\r
392\r
393 element.setStyles( {\r
394 top: top + 'px',\r
395 left: left + 'px'\r
396 } );\r
397 element.setOpacity( 1 );\r
398\r
399 callback && callback();\r
400 }, this );\r
401\r
402 panel.isLoaded ? panelLoad() : panel.onLoad = panelLoad;\r
403\r
404 CKEDITOR.tools.setTimeout( function() {\r
405 var scrollTop = CKEDITOR.env.webkit && CKEDITOR.document.getWindow().getScrollPosition().y;\r
406\r
407 // Focus the panel frame first, so blur gets fired.\r
408 this.focus();\r
409\r
410 // Focus the block now.\r
411 block.element.focus();\r
412\r
413 // #10623, #10951 - restore the viewport's scroll position after focusing list element.\r
414 if ( CKEDITOR.env.webkit )\r
415 CKEDITOR.document.getBody().$.scrollTop = scrollTop;\r
416\r
417 // We need this get fired manually because of unfired focus() function.\r
418 this.allowBlur( true );\r
419 this._.editor.fire( 'panelShow', this );\r
420 }, 0, this );\r
421 }, CKEDITOR.env.air ? 200 : 0, this );\r
422 this.visible = 1;\r
423\r
424 if ( this.onShow )\r
425 this.onShow.call( this );\r
426 },\r
427\r
428 /**\r
429 * Repositions the panel with the same parameters that were used in the last {@link #showBlock} call.\r
430 *\r
431 * @since 4.5.4\r
432 */\r
433 reposition: function() {\r
434 var blockParams = this._.showBlockParams;\r
435\r
436 if ( this.visible && this._.showBlockParams ) {\r
437 this.hide();\r
438 this.showBlock.apply( this, blockParams );\r
439 }\r
440 },\r
441\r
442 /**\r
443 * Restores the last focused element or simply focuses the panel window.\r
444 */\r
445 focus: function() {\r
446 // Webkit requires to blur any previous focused page element, in\r
447 // order to properly fire the "focus" event.\r
448 if ( CKEDITOR.env.webkit ) {\r
449 var active = CKEDITOR.document.getActive();\r
450 active && !active.equals( this._.iframe ) && active.$.blur();\r
451 }\r
452\r
453 // Restore last focused element or simply focus panel window.\r
454 var focus = this._.lastFocused || this._.iframe.getFrameDocument().getWindow();\r
455 focus.focus();\r
456 },\r
457\r
458 /**\r
459 * @todo\r
460 */\r
461 blur: function() {\r
462 var doc = this._.iframe.getFrameDocument(),\r
463 active = doc.getActive();\r
464\r
465 active && active.is( 'a' ) && ( this._.lastFocused = active );\r
466 },\r
467\r
468 /**\r
469 * Hides the panel.\r
470 *\r
471 * @todo\r
472 */\r
473 hide: function( returnFocus ) {\r
474 if ( this.visible && ( !this.onHide || this.onHide.call( this ) !== true ) ) {\r
475 this.hideChild();\r
476 // Blur previously focused element. (#6671)\r
477 CKEDITOR.env.gecko && this._.iframe.getFrameDocument().$.activeElement.blur();\r
478 this.element.setStyle( 'display', 'none' );\r
479 this.visible = 0;\r
480 this.element.getFirst().removeCustomData( 'activePanel' );\r
481\r
482 // Return focus properly. (#6247)\r
483 var focusReturn = returnFocus && this._.returnFocus;\r
484 if ( focusReturn ) {\r
485 // Webkit requires focus moved out panel iframe first.\r
486 if ( CKEDITOR.env.webkit && focusReturn.type )\r
487 focusReturn.getWindow().$.focus();\r
488\r
489 focusReturn.focus();\r
490 }\r
491\r
492 delete this._.lastFocused;\r
493 this._.showBlockParams = null;\r
494\r
495 this._.editor.fire( 'panelHide', this );\r
496 }\r
497 },\r
498\r
499 /**\r
500 * @todo\r
501 */\r
502 allowBlur: function( allow ) {\r
503 // Prevent editor from hiding the panel. (#3222)\r
504 var panel = this._.panel;\r
505 if ( allow !== undefined )\r
506 panel.allowBlur = allow;\r
507\r
508 return panel.allowBlur;\r
509 },\r
510\r
511 /**\r
512 * Shows the specified panel as a child of one block of this one.\r
513 *\r
514 * @param {CKEDITOR.ui.floatPanel} panel\r
515 * @param {String} blockName\r
516 * @param {CKEDITOR.dom.element} offsetParent Positioned parent.\r
517 * @param {Number} corner\r
518 *\r
519 * * For LTR (left to right) oriented editor:\r
520 * * `1` = top-left\r
521 * * `2` = top-right\r
522 * * `3` = bottom-right\r
523 * * `4` = bottom-left\r
524 * * For RTL (right to left):\r
525 * * `1` = top-right\r
526 * * `2` = top-left\r
527 * * `3` = bottom-left\r
528 * * `4` = bottom-right\r
529 *\r
530 * @param {Number} [offsetX=0]\r
531 * @param {Number} [offsetY=0]\r
532 * @todo\r
533 */\r
534 showAsChild: function( panel, blockName, offsetParent, corner, offsetX, offsetY ) {\r
535 // Skip reshowing of child which is already visible.\r
536 if ( this._.activeChild == panel && panel._.panel._.offsetParentId == offsetParent.getId() )\r
537 return;\r
538\r
539 this.hideChild();\r
540\r
541 panel.onHide = CKEDITOR.tools.bind( function() {\r
542 // Use a timeout, so we give time for this menu to get\r
543 // potentially focused.\r
544 CKEDITOR.tools.setTimeout( function() {\r
545 if ( !this._.focused )\r
546 this.hide();\r
547 }, 0, this );\r
548 }, this );\r
549\r
550 this._.activeChild = panel;\r
551 this._.focused = false;\r
552\r
553 panel.showBlock( blockName, offsetParent, corner, offsetX, offsetY );\r
554 this.blur();\r
555\r
556 /* #3767 IE: Second level menu may not have borders */\r
557 if ( CKEDITOR.env.ie7Compat || CKEDITOR.env.ie6Compat ) {\r
558 setTimeout( function() {\r
559 panel.element.getChild( 0 ).$.style.cssText += '';\r
560 }, 100 );\r
561 }\r
562 },\r
563\r
564 /**\r
565 * @todo\r
566 */\r
567 hideChild: function( restoreFocus ) {\r
568 var activeChild = this._.activeChild;\r
569\r
570 if ( activeChild ) {\r
571 delete activeChild.onHide;\r
572 delete this._.activeChild;\r
573 activeChild.hide();\r
574\r
575 // At this point focus should be moved back to parent panel.\r
576 restoreFocus && this.focus();\r
577 }\r
578 }\r
579 }\r
580 } );\r
581\r
582 CKEDITOR.on( 'instanceDestroyed', function() {\r
583 var isLastInstance = CKEDITOR.tools.isEmpty( CKEDITOR.instances );\r
584\r
585 for ( var i in panels ) {\r
586 var panel = panels[ i ];\r
587 // Safe to destroy it since there're no more instances.(#4241)\r
588 if ( isLastInstance )\r
589 panel.destroy();\r
590 // Panel might be used by other instances, just hide them.(#4552)\r
591 else\r
592 panel.element.hide();\r
593 }\r
594 // Remove the registration.\r
595 isLastInstance && ( panels = {} );\r
596\r
597 } );\r
598} )();\r