diff --git a/CHANGES.md b/CHANGES.md index e9238818adf..c0f654cd621 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ New Features: * [#3256](https://github.com/ckeditor/ckeditor-dev/issues/3256): Triple-clicking in last table cell and deleting content no longer pulls content below into table. * [#3118](https://github.com/ckeditor/ckeditor-dev/issues/3118): Selecting a paragraph with triple-click and applying heading applies heading only to selected paragraph. * [#3161](https://github.com/ckeditor/ckeditor-dev/issues/3161): Double click on a `span` element containing one word only creates correct selection including clicked `span` only. +* [#3359](https://github.com/ckeditor/ckeditor-dev/issues/3359): Improved [dialog](https://ckeditor.com/cke4/addon/dialog) positioning and behaviour when browser window is resized, dialog resized or moved. Fixed Issues: @@ -27,8 +28,10 @@ Fixed Issues: * [#3101](https://github.com/ckeditor/ckeditor-dev/issues/3101): Fixed: [`CKEDITOR.dom.range#_getTableElement()`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_dom_range.html#method-_getTableElement) returns `null` instead of a table element for edge cases. * [#3287](https://github.com/ckeditor/ckeditor-dev/issues/3287): Fixed: [`CKEDITOR.tools.promise`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_promise.html) incorrectly initialized if an AMD loader is present. * [#3379](https://github.com/ckeditor/ckeditor-dev/issues/3379): Fixed: Incorrect [`CKEDITOR.editor#getData`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_editor.html#method-getData) call when inserting content into the editor. -* [#3136](https://github.com/ckeditor/ckeditor-dev/issues/3136) [Firefox] Fixed: Clicking on [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) items removes native table selection. +* [#3136](https://github.com/ckeditor/ckeditor-dev/issues/3136): [Firefox] Fixed: Clicking on [Balloon Toolbar](https://ckeditor.com/cke4/addon/balloontoolbar) items removes native table selection. * [#3381](https://github.com/ckeditor/ckeditor-dev/issues/3381): [IE8] Fixed: [`CKEDITOR.tools.object.keys`](https://ckeditor.com/docs/ckeditor4/latest/api/CKEDITOR_tools_object.html#method-keys) does not accept non-objects. +* [#2395](https://github.com/ckeditor/ckeditor-dev/issues/2395): [Android] Fixed: Focused input in [dialog](https://ckeditor.com/cke4/addon/dialog) is scrolled out of the viewport when soft keyboard appears. +* [#453](https://github.com/ckeditor/ckeditor-dev/issues/453): Fixed: [Link](https://ckeditor.com/cke4/addon/link) dialog has invalid width when editor is maximized and browser window resized. API Changes: diff --git a/core/dom/element.js b/core/dom/element.js index f1975473fcd..97f39d8249f 100644 --- a/core/dom/element.js +++ b/core/dom/element.js @@ -682,6 +682,19 @@ CKEDITOR.dom.element.clearMarkers = function( database, element, removeFromDatab return new CKEDITOR.dom.nodeList( this.$.childNodes ); }, + /** + * Gets the elements `clientWidth` and `clientHeight`. + * + * @since 4.13.0 + * @returns {Object} An object containing width and height values. + */ + getClientSize: function() { + return { + width: this.$.clientWidth, + height: this.$.clientHeight + }; + }, + /** * Gets the current computed value of one of the element CSS style * properties. diff --git a/plugins/dialog/plugin.js b/plugins/dialog/plugin.js index c2d034b5f49..eee9fe04731 100644 --- a/plugins/dialog/plugin.js +++ b/plugins/dialog/plugin.js @@ -135,31 +135,34 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; input && input.removeAttribute( 'aria-invalid' ); } - var templateSource = ''; + var useFlex = !CKEDITOR.env.ie || CKEDITOR.env.edge, + templateSource = ''; function buildDialog( editor ) { var element = CKEDITOR.dom.element.createFromHtml( CKEDITOR.addTemplate( 'dialog', templateSource ).output( { @@ -256,6 +259,11 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; contents: {}, buttons: {}, accessKeyMap: {}, + // Default value is 0.5 which means centered dialog. + viewportRatio: { + width: 0.5, + height: 0.5 + }, // Initialize the tab and page map. tabs: {}, @@ -281,11 +289,14 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; // Set the startup styles for the dialog, avoiding it enlarging the // page size on the dialog creation. var startStyles = { - position: CKEDITOR.env.ie6Compat ? 'absolute' : 'fixed', top: 0, visibility: 'hidden' }; + if ( CKEDITOR.env.ie6Compat ) { + startStyles.position = 'absolute'; + } + startStyles[ dir == 'rtl' ? 'right' : 'left' ] = 0; this.parts.dialog.setStyles( startStyles ); @@ -727,35 +738,36 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; * @param {Number} width The width of the dialog in pixels. * @param {Number} height The height of the dialog in pixels. */ - resize: ( function() { - return function( width, height ) { - if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) - return; + resize: function( width, height ) { + if ( this._.contentSize && this._.contentSize.width == width && this._.contentSize.height == height ) + return; - CKEDITOR.dialog.fire( 'resize', { - dialog: this, - width: width, - height: height - }, this._.editor ); - - this.fire( 'resize', { - width: width, - height: height - }, this._.editor ); - - var contents = this.parts.contents; - contents.setStyles( { - width: width + 'px', - height: height + 'px' - } ); + CKEDITOR.dialog.fire( 'resize', { + dialog: this, + width: width, + height: height + }, this._.editor ); + + this.fire( 'resize', { + width: width, + height: height + }, this._.editor ); + + var contents = this.parts.contents; + contents.setStyles( { + width: width + 'px', + height: height + 'px' + } ); - // Update dialog position when dimension get changed in RTL. - if ( this._.editor.lang.dir == 'rtl' && this._.position ) - this._.position.x = CKEDITOR.document.getWindow().getViewPaneSize().width - this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 ); + // Update dialog position when dimension get changed in RTL. + if ( this._.editor.lang.dir == 'rtl' && this._.position ) { + var containerWidth = this.parts.dialog.getParent().getClientSize().width; - this._.contentSize = { width: width, height: height }; - }; - } )(), + this._.position.x = containerWidth - this._.contentSize.width - parseInt( this._.element.getFirst().getStyle( 'right' ), 10 ); + } + + this._.contentSize = { width: width, height: height }; + }, /** * Gets the current size of the dialog in pixels. @@ -782,11 +794,7 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; * @param {Boolean} save Flag indicate whether the dialog position should be remembered on next open up. */ move: function( x, y, save ) { - - // The dialog may be fixed positioned or absolute positioned. Ask the - // browser what is the current situation first. var element = this._.element.getFirst(), rtl = this._.editor.lang.dir == 'rtl'; - var isFixed = element.getComputedStyle( 'position' ) == 'fixed'; // (https://dev.ckeditor.com/ticket/8888) In some cases of a very small viewport, dialog is incorrectly // positioned in IE7. It also happens that it remains sticky and user cannot @@ -796,26 +804,32 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; // go back to see that dialog position is automagically fixed. No events, // no style change - pure magic. This is a IE7 rendering issue, which can be // fixed with dummy style redraw on each move. - if ( CKEDITOR.env.ie ) + if ( CKEDITOR.env.ie ) { element.setStyle( 'zoom', '100%' ); + } - if ( isFixed && this._.position && this._.position.x == x && this._.position.y == y ) - return; + var containerSize = this.parts.dialog.getParent().getClientSize(), + dialogSize = this.getSize(), + ratios = this._.viewportRatio, + freeSpace = { + width: Math.max( containerSize.width - dialogSize.width, 0 ), + height: Math.max( containerSize.height - dialogSize.height, 0 ) + }; + + if ( this._.position && this._.position.x == x && this._.position.y == y ) { + // If position didn't change window might have been resized. + x = Math.floor( freeSpace.width * ratios.width ); + y = Math.floor( freeSpace.height * ratios.height ); + } else { + updateRatios( this, x, y ); + } // Save the current position. this._.position = { x: x, y: y }; - // If not fixed positioned, add scroll position to the coordinates. - if ( !isFixed ) { - var scrollPosition = CKEDITOR.document.getWindow().getScrollPosition(); - x += scrollPosition.x; - y += scrollPosition.y; - } - // Translate coordinate for RTL. if ( rtl ) { - var dialogSize = this.getSize(), viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(); - x = viewPaneSize.width - dialogSize.width - x; + x = freeSpace.width - x; } var styles = { 'top': ( y > 0 ? y : 0 ) + 'px' }; @@ -848,10 +862,12 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; // Insert the dialog's element to the root document. var element = this._.element; var definition = this.definition; - if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) + + if ( !( element.getParent() && element.getParent().equals( CKEDITOR.document.getBody() ) ) ) { element.appendTo( CKEDITOR.document.getBody() ); - else - element.setStyle( 'display', 'block' ); + } else { + element.setStyle( 'display', useFlex ? 'flex' : 'block' ); + } // First, set the dialog to an appropriate size. this.resize( @@ -954,23 +970,33 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; */ layout: function() { var el = this.parts.dialog; - var dialogSize = this.getSize(); - var win = CKEDITOR.document.getWindow(), - viewSize = win.getViewPaneSize(); - var posX = ( viewSize.width - dialogSize.width ) / 2, + if ( !this._.moved && useFlex ) { + return; + } + + var dialogSize = this.getSize(), + win = CKEDITOR.document.getWindow(), + viewSize = win.getViewPaneSize(), + posX, posY; + + if ( this._.moved && this._.position ) { + posX = this._.position.x; + posY = this._.position.y; + } else { + posX = ( viewSize.width - dialogSize.width ) / 2; posY = ( viewSize.height - dialogSize.height ) / 2; + } - // Switch to absolute position when viewport is smaller than dialog size. if ( !CKEDITOR.env.ie6Compat ) { - if ( dialogSize.height + ( posY > 0 ? posY : 0 ) > viewSize.height || dialogSize.width + ( posX > 0 ? posX : 0 ) > viewSize.width ) { - el.setStyle( 'position', 'absolute' ); - } else { - el.setStyle( 'position', 'fixed' ); - } + el.setStyle( 'position', 'absolute' ); + el.removeStyle( 'margin' ); } - this.move( this._.moved ? this._.position.x : posX, this._.moved ? this._.position.y : posY ); + posX = Math.floor( posX ); + posY = Math.floor( posY ); + + this.move( posX, posY ); }, /** @@ -1159,6 +1185,7 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; // Create the HTML for the tab and the content block. var page = CKEDITOR.dom.element.createFromHtml( pageHtml.join( '' ) ); page.setAttribute( 'role', 'tabpanel' ); + page.setStyle( 'min-height', '100%' ); var env = CKEDITOR.env; var tabId = 'cke_' + contents.id + '_' + CKEDITOR.tools.getNextNumber(), @@ -1925,7 +1952,7 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; function mouseMoveHandler( evt ) { var dialogSize = dialog.getSize(), - viewPaneSize = CKEDITOR.document.getWindow().getViewPaneSize(), + containerSize = dialog.parts.dialog.getParent().getClientSize(), x = evt.data.$.screenX, y = evt.data.$.screenY, dx = x - lastCoords.x, @@ -1936,19 +1963,24 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; abstractDialogCoords.x += dx; abstractDialogCoords.y += dy; - if ( abstractDialogCoords.x + margins[ 3 ] < magnetDistance ) + if ( abstractDialogCoords.x + margins[ 3 ] < magnetDistance ) { realX = -margins[ 3 ]; - else if ( abstractDialogCoords.x - margins[ 1 ] > viewPaneSize.width - dialogSize.width - magnetDistance ) - realX = viewPaneSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[ 1 ] ); - else + } else if ( abstractDialogCoords.x - margins[ 1 ] > containerSize.width - dialogSize.width - magnetDistance ) { + realX = containerSize.width - dialogSize.width + ( editor.lang.dir == 'rtl' ? 0 : margins[ 1 ] ); + } else { realX = abstractDialogCoords.x; + } - if ( abstractDialogCoords.y + margins[ 0 ] < magnetDistance ) + if ( abstractDialogCoords.y + margins[ 0 ] < magnetDistance ) { realY = -margins[ 0 ]; - else if ( abstractDialogCoords.y - margins[ 2 ] > viewPaneSize.height - dialogSize.height - magnetDistance ) - realY = viewPaneSize.height - dialogSize.height + margins[ 2 ]; - else + } else if ( abstractDialogCoords.y - margins[ 2 ] > containerSize.height - dialogSize.height - magnetDistance ) { + realY = containerSize.height - dialogSize.height + margins[ 2 ]; + } else { realY = abstractDialogCoords.y; + } + + realX = Math.floor( realX ); + realY = Math.floor( realY ); dialog.move( realX, realY, 1 ); @@ -1967,6 +1999,17 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; } dialog.parts.title.on( 'mousedown', function( evt ) { + if ( !dialog._.moved ) { + var container = dialog._.element, + element = container.getFirst(); + + element.setStyle( 'position', 'absolute' ); + container.removeStyle( 'display' ); + + dialog._.moved = true; + dialog.layout(); + } + lastCoords = { x: evt.data.$.screenX, y: evt.data.$.screenY }; CKEDITOR.document.on( 'mousemove', mouseMoveHandler ); @@ -1997,17 +2040,19 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; startSize = dialog.getSize(); var content = dialog.parts.contents, - iframeDialog = content.$.getElementsByTagName( 'iframe' ).length; + iframeDialog = content.$.getElementsByTagName( 'iframe' ).length, + isBorderBox = !( CKEDITOR.env.gecko || CKEDITOR.env.ie && CKEDITOR.env.quirks ); // Shim to help capturing "mousemove" over iframe. if ( iframeDialog ) { - dialogCover = CKEDITOR.dom.element.createFromHtml( '
' ); + dialogCover = CKEDITOR.dom.element.createFromHtml( '
' ); content.append( dialogCover ); } // Calculate the offset between content and chrome size. - wrapperHeight = startSize.height - dialog.parts.contents.getSize( 'height', !( CKEDITOR.env.gecko || CKEDITOR.env.ie && CKEDITOR.env.quirks ) ); - wrapperWidth = startSize.width - dialog.parts.contents.getSize( 'width', 1 ); + // Use size of current tab panel because we can't rely on size of contents container (#3144). + wrapperHeight = startSize.height - dialog.parts.contents.getFirst( isVisible ).getSize( 'height', isBorderBox ); + wrapperWidth = startSize.width - dialog.parts.contents.getFirst( isVisible ).getSize( 'width', 1 ); origin = { x: $event.screenX, y: $event.screenY }; @@ -2023,6 +2068,10 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; } $event.preventDefault && $event.preventDefault(); + + function isVisible( el ) { + return el.isVisible(); + } } ); // Prepend the grip to the dialog. @@ -2056,24 +2105,41 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; internalWidth = width + dx * ( dialog._.moved ? 1 : 2 ), internalHeight = height + dy * ( dialog._.moved ? 1 : 2 ), element = dialog._.element.getFirst(), - right = rtl && element.getComputedStyle( 'right' ), + right = rtl && parseInt( element.getComputedStyle( 'right' ) ), position = dialog.getPosition(); - if ( position.y + internalHeight > viewSize.height ) + position.x = position.x || 0; + position.y = position.y || 0; + + if ( position.y + internalHeight > viewSize.height ) { internalHeight = viewSize.height - position.y; + } - if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width ) + if ( ( rtl ? right : position.x ) + internalWidth > viewSize.width ) { internalWidth = viewSize.width - ( rtl ? right : position.x ); + } + + internalHeight = Math.floor( internalHeight ); + internalWidth = Math.floor( internalWidth ); // Make sure the dialog will not be resized to the wrong side when it's in the leftmost position for RTL. - if ( ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) ) + if ( resizable == CKEDITOR.DIALOG_RESIZE_WIDTH || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) { width = Math.max( def.minWidth || 0, internalWidth - wrapperWidth ); + } - if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) + if ( resizable == CKEDITOR.DIALOG_RESIZE_HEIGHT || resizable == CKEDITOR.DIALOG_RESIZE_BOTH ) { height = Math.max( def.minHeight || 0, internalHeight - wrapperHeight ); + } dialog.resize( width, height ); + if ( dialog._.moved ) { + var x = dialog._.position.x, + y = dialog._.position.y; + + updateRatios( dialog, x, y ); + } + if ( !dialog._.moved ) dialog.layout(); @@ -2097,9 +2163,22 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; } } - var resizeCover; - // Caching resuable covers and allowing only one cover - // on screen. + function updateRatios( dialog, x, y ) { + var containerSize = dialog.parts.dialog.getParent().getClientSize(), + dialogSize = dialog.getSize(), + ratios = dialog._.viewportRatio, + freeSpace = { + width: Math.max( containerSize.width - dialogSize.width, 0 ), + height: Math.max( containerSize.height - dialogSize.height, 0 ) + }; + + ratios.width = freeSpace.width ? ( x / freeSpace.width ) : ratios.width; + ratios.height = freeSpace.height ? ( y / freeSpace.height ) : ratios.height; + + dialog._.viewportRatio = ratios; + } + + // Caching reusable covers and allowing only one cover on screen. var covers = {}, currentCover; @@ -2108,8 +2187,7 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; } function showCover( editor ) { - var win = CKEDITOR.document.getWindow(), - config = editor.config, + var config = editor.config, skinName = ( CKEDITOR.skinName || editor.config.skin ), backgroundColorStyle = config.dialog_backgroundCoverColor || ( skinName == 'moono-lisa' ? 'black' : 'white' ), backgroundCoverOpacity = config.dialog_backgroundCoverOpacity, @@ -2117,11 +2195,14 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; coverKey = CKEDITOR.tools.genKey( backgroundColorStyle, backgroundCoverOpacity, baseFloatZIndex ), coverElement = covers[ coverKey ]; + CKEDITOR.document.getBody().addClass( 'cke_dialog_open' ); + if ( !coverElement ) { var html = [ '
' ]; @@ -2174,72 +2255,19 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; editor.focusManager.add( coverElement ); currentCover = coverElement; - var resizeFunc = function() { - var size = win.getViewPaneSize(); - coverElement.setStyles( { - width: size.width + 'px', - height: size.height + 'px' - } ); - }; - - var scrollFunc = function() { - var pos = win.getScrollPosition(), - cursor = CKEDITOR.dialog._.currentTop; - coverElement.setStyles( { - left: pos.x + 'px', - top: pos.y + 'px' - } ); - if ( cursor ) { - do { - var dialogPos = cursor.getPosition(); - cursor.move( dialogPos.x, dialogPos.y ); - } while ( ( cursor = cursor._.parentDialog ) ); - } - }; - - resizeCover = resizeFunc; - win.on( 'resize', resizeFunc ); - resizeFunc(); // Using Safari/Mac, focus must be kept where it is (https://dev.ckeditor.com/ticket/7027) if ( !( CKEDITOR.env.mac && CKEDITOR.env.webkit ) ) coverElement.focus(); - - if ( CKEDITOR.env.ie6Compat ) { - // IE BUG: win.$.onscroll assignment doesn't work.. it must be window.onscroll. - // So we need to invent a really funny way to make it work. - var myScrollHandler = function() { - scrollFunc(); - myScrollHandler.prevScrollHandler.apply( this, arguments ); - }; - win.$.setTimeout( function() { - myScrollHandler.prevScrollHandler = window.onscroll || - function() {}; - window.onscroll = myScrollHandler; - }, 0 ); - scrollFunc(); - } } function hideCover( editor ) { + CKEDITOR.document.getBody().removeClass( 'cke_dialog_open' ); if ( !currentCover ) return; editor.focusManager.remove( currentCover ); - var win = CKEDITOR.document.getWindow(); currentCover.hide(); - - // Remove the current cover reference once the cover is removed (#589). - currentCover = null; - win.removeListener( 'resize', resizeCover ); - - if ( CKEDITOR.env.ie6Compat ) { - win.$.setTimeout( function() { - var prevScrollHandler = window.onscroll && window.onscroll.prevScrollHandler; - window.onscroll = prevScrollHandler || null; - }, 0 ); - } - resizeCover = null; } function removeCovers() { @@ -3349,9 +3377,16 @@ CKEDITOR.DIALOG_STATE_BUSY = 2; } ); } )(); +var stylesLoaded = false; + CKEDITOR.plugins.add( 'dialog', { requires: 'dialogui', init: function( editor ) { + if ( !stylesLoaded ) { + CKEDITOR.document.appendStyleSheet( this.path + 'styles/dialog.css' ); + stylesLoaded = true; + } + editor.on( 'doubleclick', function( evt ) { if ( evt.data.dialog ) editor.openDialog( evt.data.dialog ); diff --git a/plugins/dialog/styles/dialog.css b/plugins/dialog/styles/dialog.css new file mode 100644 index 00000000000..add83eb2258 --- /dev/null +++ b/plugins/dialog/styles/dialog.css @@ -0,0 +1,18 @@ +.cke_dialog_open { + overflow: hidden; +} + +.cke_dialog_container { + position: fixed; + overflow-y: auto; + overflow-x: auto; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 10010; +} + +.cke_dialog_body { + position: relative; +} diff --git a/plugins/floatpanel/plugin.js b/plugins/floatpanel/plugin.js index b1fc8cae791..85a890eaf80 100644 --- a/plugins/floatpanel/plugin.js +++ b/plugins/floatpanel/plugin.js @@ -367,15 +367,15 @@ CKEDITOR.plugins.add( 'floatpanel', { // position and horizontal scrolls. Here we have a // series of hacks to workaround it. (https://dev.ckeditor.com/ticket/6146) if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { - var offsetParent = new CKEDITOR.dom.element( element.$.offsetParent ), + var offsetParent = element.$.offsetParent && new CKEDITOR.dom.element( element.$.offsetParent ), scrollParent = offsetParent; // Quirks returns , but standards returns . - if ( scrollParent.getName() == 'html' ) { + if ( scrollParent && scrollParent.getName() == 'html' ) { scrollParent = scrollParent.getDocument().getBody(); } - if ( scrollParent.getComputedStyle( 'direction' ) == 'rtl' ) { + if ( scrollParent && scrollParent.getComputedStyle( 'direction' ) == 'rtl' ) { // For IE8, there is not much logic on this, but it works. if ( CKEDITOR.env.ie8Compat ) { left -= element.getDocument().getDocumentElement().$.scrollLeft * 2; diff --git a/tests/core/dom/element/element.js b/tests/core/dom/element/element.js index 91e52fa6b9d..879e2098a37 100644 --- a/tests/core/dom/element/element.js +++ b/tests/core/dom/element/element.js @@ -1230,6 +1230,20 @@ bender.test( appendDomObjectTests( assert.areSame( String( leftMouseButton ), link.getAttribute( 'data-button' ), 'Proper event data was passed' ); + }, + + 'test getClientSize': function() { + var element = new CKEDITOR.dom.element( 'div' ); + + element.$ = { + clientWidth: 100, + clientHeight: 100 + }; + + var size = element.getClientSize(); + + assert.areSame( 100, size.width ); + assert.areSame( 100, size.height ); } } ) ); diff --git a/tests/plugins/dialog/manual/inputfocus.html b/tests/plugins/dialog/manual/inputfocus.html new file mode 100644 index 00000000000..3b198cbe022 --- /dev/null +++ b/tests/plugins/dialog/manual/inputfocus.html @@ -0,0 +1,21 @@ + +
+

Left to right editor:

+ +

Right to left editor:

+ +
+ + diff --git a/tests/plugins/dialog/manual/inputfocus.md b/tests/plugins/dialog/manual/inputfocus.md new file mode 100644 index 00000000000..3dcbc8391b7 --- /dev/null +++ b/tests/plugins/dialog/manual/inputfocus.md @@ -0,0 +1,32 @@ +@bender-tags: 2395, bug, 4.13.0 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, link + +1. Read expected. +1. Scroll down until editor is visible. +1. Press link button. +1. Resize browser window. +1. Resize dialog window. +1. Drag dialog window. +1. Close and reopen dialog. + +## Expected + +- When dialog is shown scrollbars are invisible. +- When resizing browser window dialog is still horizontally and vertically centered. +- When window is too small to fit dialog window scrollbars should appear allowing to scroll dialog. +- Dialog window can be resized and dragged around. +- When closing and reopening dialog it has same size and position as it had before when it was closed. + +### Note +When you reposition dialog it won't be centered when resizing browser window. Repositioned dialog will move in a way that it will have same percentage of free space on each side. E.g. Consider there is when there is free space around dialog: +- on the left 10px +- on the right 90px + +Then when window width is increased so that there will be 200px of free space around dialog now there will be: +- on the left 20px +- on the right 180px + +If instead window width is decreased so there is 50px free space then there will be: +- on the left 5px +- on the right 45px diff --git a/tests/plugins/dialog/manual/inputfocusmobile.html b/tests/plugins/dialog/manual/inputfocusmobile.html new file mode 100644 index 00000000000..4887cca642d --- /dev/null +++ b/tests/plugins/dialog/manual/inputfocusmobile.html @@ -0,0 +1,26 @@ + + + + + +
+

Left to right editor:

+ +

Right to left editor:

+ +
+ + + diff --git a/tests/plugins/dialog/manual/inputfocusmobile.md b/tests/plugins/dialog/manual/inputfocusmobile.md new file mode 100644 index 00000000000..7d2f69f2fe0 --- /dev/null +++ b/tests/plugins/dialog/manual/inputfocusmobile.md @@ -0,0 +1,17 @@ +@bender-tags: 2395, bug, 4.13.0 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, link + +1. Scroll down until editor is visible and press link button. +1. Focus other input in dialog. + +## Expected: + +- After each step page should be scrolled in a way caret is visible. + +## Unexpected + +- Caret is outside of current viewport. + +## Note +When testing with device which has small screen you should hide test steps with `X` button, so editor has enough space. diff --git a/tests/plugins/dialog/manual/skins/kama.html b/tests/plugins/dialog/manual/skins/kama.html new file mode 100644 index 00000000000..eb377a35504 --- /dev/null +++ b/tests/plugins/dialog/manual/skins/kama.html @@ -0,0 +1,7 @@ + + + diff --git a/tests/plugins/dialog/manual/skins/kama.md b/tests/plugins/dialog/manual/skins/kama.md new file mode 100644 index 00000000000..262e3f58b43 --- /dev/null +++ b/tests/plugins/dialog/manual/skins/kama.md @@ -0,0 +1,5 @@ +@bender-tags: 2395, bug, 4.13.0 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, link + +Press link button and play around with dialog window to see if it's styling looks correct with no visible glitches. diff --git a/tests/plugins/dialog/manual/skins/moono.html b/tests/plugins/dialog/manual/skins/moono.html new file mode 100644 index 00000000000..3dab26144ec --- /dev/null +++ b/tests/plugins/dialog/manual/skins/moono.html @@ -0,0 +1,7 @@ + + + diff --git a/tests/plugins/dialog/manual/skins/moono.md b/tests/plugins/dialog/manual/skins/moono.md new file mode 100644 index 00000000000..e9ae8aa3446 --- /dev/null +++ b/tests/plugins/dialog/manual/skins/moono.md @@ -0,0 +1,5 @@ +@bender-tags: 2395, bug, 4.13.0 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, link + +Play around with dialog window to see if it's styling looks correct with no visible glitches. diff --git a/tests/plugins/dialog/manual/skins/moonolisa.html b/tests/plugins/dialog/manual/skins/moonolisa.html new file mode 100644 index 00000000000..1db119f7615 --- /dev/null +++ b/tests/plugins/dialog/manual/skins/moonolisa.html @@ -0,0 +1,5 @@ + + + diff --git a/tests/plugins/dialog/manual/skins/moonolisa.md b/tests/plugins/dialog/manual/skins/moonolisa.md new file mode 100644 index 00000000000..e9ae8aa3446 --- /dev/null +++ b/tests/plugins/dialog/manual/skins/moonolisa.md @@ -0,0 +1,5 @@ +@bender-tags: 2395, bug, 4.13.0 +@bender-ui: collapsed +@bender-ckeditor-plugins: wysiwygarea, toolbar, link + +Play around with dialog window to see if it's styling looks correct with no visible glitches. diff --git a/tests/plugins/dialog/positioning.js b/tests/plugins/dialog/positioning.js new file mode 100644 index 00000000000..40da612cac6 --- /dev/null +++ b/tests/plugins/dialog/positioning.js @@ -0,0 +1,226 @@ +/* bender-tags: editor */ +/* bender-ckeditor-plugins: dialog, link, toolbar */ + +( function() { + 'use strict'; + + bender.editor = {}; + + bender.test( { + setUp: function() { + // Make sure page is scrollable on vertical screens. + CKEDITOR.document.getBody().appendHtml( '
' ); + }, + tearDown: function() { + var dialog = CKEDITOR.dialog.getCurrent(); + if ( dialog ) { + dialog.hide(); + } + }, + + // (#2395). + 'test body has hidden scrollbars when dialog is opened': function() { + if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { + assert.ignore(); + } + var bot = this.editorBot, + body = CKEDITOR.document.getBody(); + + bot.dialog( 'link', function( dialog ) { + assert.isTrue( body.hasClass( 'cke_dialog_open' ), 'Body should have proper class.' ); + + dialog.hide(); + + assert.isFalse( body.hasClass( 'cke_dialog_open' ), 'Body shouldn\'t have class.' ); + } ); + }, + + // (#2395). + 'test dialog is initially centered': function() { + if ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) { + assert.ignore(); + } + this.editorBot.dialog( 'link', function( dialog ) { + var container = dialog._.element, + element = container.getFirst(); + + assert.areEqual( container.getStyle( 'display' ), 'flex', 'Dialog container should have `display:flex`.' ); + assert.areEqual( element.getStyle( 'position' ), '', 'Dialog element should\'t have position style.' ); + assert.areEqual( element.getStyle( 'margin' ), 'auto', 'Dialog element should have `margin:auto`.' ); + } ); + }, + + // (#2395). + 'test dialog cover styles': function() { + this.editorBot.dialog( 'link', function() { + var cover = CKEDITOR.document.findOne( '.cke_dialog_background_cover' ); + + assert.areEqual( cover.getStyle( 'width' ), '100%', 'Dialog element should have `width:100%`.' ); + assert.areEqual( cover.getStyle( 'height' ), '100%', 'Dialog element should have `height:100%`.' ); + } ); + }, + + // When drag starts, dialog becomes centered with `position:absolute`, then it moves together with mouse (#2395). + // + // A - mouse cursor + // + // Drag start: + // +----------------------------------+ + // | Editor viewport | + // | | + // | +-------------A--+ | + // | | | | + // | | Dialog | | + // | | | | + // | +----------------+ | + // | | + // +----------------------------------+ + // + // Drag end: + // +----------------------------------+ + // | Editor viewport | + // | | + // | | + // | +-------------A--+| + // | | || + // | | Dialog || + // | | || + // | +----------------+| + // +----------------------------------+ + // + 'test dialog move': function() { + var viewPaneSize = { + width: 1000, + height: 1000 + }, + stubs = mockWindowGetViewPaneSize( viewPaneSize ); + + this.editorBot.dialog( 'link', function( dialog ) { + var element = dialog._.element.getFirst(), + dialogSize = dialog.getSize(), + expectedX = Math.floor( ( viewPaneSize.width - dialogSize.width ) / 2 ), + expectedY = Math.floor( ( viewPaneSize.height - dialogSize.height ) / 2 ); + + dialog.parts.title.fire( 'mousedown', { + $: { + screenX: 0, + screenY: 0 + }, + preventDefault: function() {} + } ); + + var actualX = parseInt( element.getStyle( 'left' ), 10 ), + actualY = parseInt( element.getStyle( 'top' ), 10 ), + elementStyle = element.getStyle( 'position' ); + + CKEDITOR.document.fire( 'mousemove', { + $: { + screenX: 100, + screenY: 100 + }, + preventDefault: function() {} + } ); + + for ( var key in stubs ) { + stubs[ key ].restore(); + } + + assert.areEqual( 'absolute', elementStyle, 'Dialog element should have `position:absolute`.' ); + assert.areEqual( expectedX, actualX, 'Dialog should be horizontally centered.' ); + assert.areEqual( expectedY, actualY, 'Dialog should be vertically centered.' ); + + actualX = parseInt( element.getStyle( 'left' ), 10 ); + actualY = parseInt( element.getStyle( 'top' ), 10 ); + + assert.areEqual( 100, actualX - expectedX, 'Dialog should be moved by 100px to the right.' ); + assert.areEqual( 100, actualY - expectedY, 'Dialog should be moved by 100px down.' ); + + CKEDITOR.document.fire( 'mouseup' ); + } ); + }, + + // (#2395) + 'test dialog resize': function() { + // Mobile browsers doesn't support dialog resize. + if ( bender.tools.env.mobile ) { + assert.ignore(); + } + var viewPaneSize = { + width: 1000, + height: 1000 + }, + stubs = mockWindowGetViewPaneSize( viewPaneSize ); + + this.editorBot.dialog( 'link', function( dialog ) { + var dialogSize = dialog.getSize(), + resizer = dialog._.element.findOne( '.cke_resizer' ), + evt = { + screenX: 0, + screenY: 0 + }, + sizeChange; + + // IE8 doesn't pass fake evt when manually calling resizer.$.onmousedown. + if ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) { + var str = resizer.$.onmousedown.toString(); + str = str.match( /[(](.*?),/ )[ 1 ]; + + CKEDITOR.tools.callFunction( Number( str ), evt ); + } else { + resizer.$.onmousedown( evt ); + } + + CKEDITOR.document.fire( 'mousemove', { + $: { + screenX: 10, + screenY: 10 + }, + preventDefault: function() {} + } ); + + CKEDITOR.document.fire( 'mouseup' ); + + sizeChange = { + width: dialog.getSize().width - dialogSize.width, + height: dialog.getSize().height - dialogSize.height + }; + + for ( var key in stubs ) { + stubs[ key ].restore(); + } + + // When resizing some floats might be rounded. + assert.isNumberInRange( 10, sizeChange.width - 1, sizeChange.width ); + assert.isNumberInRange( 10, sizeChange.height - 1, sizeChange.height ); + } ); + } + } ); + + function mockWindowGetViewPaneSize( viewPaneSize ) { + var window = CKEDITOR.document.getWindow(), + stubs = { + getWindow: sinon.stub( CKEDITOR.document, 'getWindow' ), + getViewPane: sinon.stub( window, 'getViewPaneSize' ), + getContainerSize: mockDialogContainerSize( viewPaneSize ) + }; + + stubs.getWindow.returns( window ); + stubs.getViewPane.returns( viewPaneSize ); + + return stubs; + } + + function mockDialogContainerSize( sizes ) { + var originalMethod = CKEDITOR.dom.element.prototype.getClientSize; + + CKEDITOR.dom.element.prototype.getClientSize = function() { + return this.hasClass( 'cke_dialog_container' ) ? sizes : originalMethod.call( this ); + }; + + return { + restore: function() { + CKEDITOR.dom.element.prototype.getClientSize = originalMethod; + } + }; + } +} )();