]>
Commit | Line | Data |
---|---|---|
c63493c8 IB |
1 | /**\r |
2 | * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.\r | |
3 | * For licensing, see LICENSE.md or http://ckeditor.com/license\r | |
4 | */\r | |
5 | \r | |
6 | ( function() {\r | |
7 | var meta = {\r | |
8 | editorFocus: false,\r | |
9 | modes: { wysiwyg: 1, source: 1 }\r | |
10 | };\r | |
11 | \r | |
12 | var blurCommand = {\r | |
13 | exec: function( editor ) {\r | |
14 | editor.container.focusNext( true, editor.tabIndex );\r | |
15 | }\r | |
16 | };\r | |
17 | \r | |
18 | var blurBackCommand = {\r | |
19 | exec: function( editor ) {\r | |
20 | editor.container.focusPrevious( true, editor.tabIndex );\r | |
21 | }\r | |
22 | };\r | |
23 | \r | |
24 | function selectNextCellCommand( backward ) {\r | |
25 | return {\r | |
26 | editorFocus: false,\r | |
27 | canUndo: false,\r | |
28 | modes: { wysiwyg: 1 },\r | |
29 | exec: function( editor ) {\r | |
30 | if ( editor.editable().hasFocus ) {\r | |
31 | var sel = editor.getSelection(),\r | |
32 | path = new CKEDITOR.dom.elementPath( sel.getCommonAncestor(), sel.root ),\r | |
33 | cell;\r | |
34 | \r | |
35 | if ( ( cell = path.contains( { td: 1, th: 1 }, 1 ) ) ) {\r | |
36 | var resultRange = editor.createRange(),\r | |
37 | next = CKEDITOR.tools.tryThese( function() {\r | |
38 | var row = cell.getParent(),\r | |
39 | next = row.$.cells[ cell.$.cellIndex + ( backward ? -1 : 1 ) ];\r | |
40 | \r | |
41 | // Invalid any empty value.\r | |
42 | next.parentNode.parentNode;\r | |
43 | return next;\r | |
44 | }, function() {\r | |
45 | var row = cell.getParent(),\r | |
46 | table = row.getAscendant( 'table' ),\r | |
47 | nextRow = table.$.rows[ row.$.rowIndex + ( backward ? -1 : 1 ) ];\r | |
48 | \r | |
49 | return nextRow.cells[ backward ? nextRow.cells.length - 1 : 0 ];\r | |
50 | } );\r | |
51 | \r | |
52 | // Clone one more row at the end of table and select the first newly established cell.\r | |
53 | if ( !( next || backward ) ) {\r | |
54 | var table = cell.getAscendant( 'table' ).$,\r | |
55 | cells = cell.getParent().$.cells;\r | |
56 | \r | |
57 | var newRow = new CKEDITOR.dom.element( table.insertRow( -1 ), editor.document );\r | |
58 | \r | |
59 | for ( var i = 0, count = cells.length; i < count; i++ ) {\r | |
60 | var newCell = newRow.append( new CKEDITOR.dom.element( cells[ i ], editor.document ).clone( false, false ) );\r | |
61 | newCell.appendBogus();\r | |
62 | }\r | |
63 | \r | |
64 | resultRange.moveToElementEditStart( newRow );\r | |
65 | } else if ( next ) {\r | |
66 | next = new CKEDITOR.dom.element( next );\r | |
67 | resultRange.moveToElementEditStart( next );\r | |
68 | // Avoid selecting empty block makes the cursor blind.\r | |
69 | if ( !( resultRange.checkStartOfBlock() && resultRange.checkEndOfBlock() ) )\r | |
70 | resultRange.selectNodeContents( next );\r | |
71 | } else {\r | |
72 | return true;\r | |
73 | }\r | |
74 | \r | |
75 | resultRange.select( true );\r | |
76 | return true;\r | |
77 | }\r | |
78 | }\r | |
79 | \r | |
80 | return false;\r | |
81 | }\r | |
82 | };\r | |
83 | }\r | |
84 | \r | |
85 | CKEDITOR.plugins.add( 'tab', {\r | |
86 | init: function( editor ) {\r | |
87 | var tabTools = editor.config.enableTabKeyTools !== false,\r | |
88 | tabSpaces = editor.config.tabSpaces || 0,\r | |
89 | tabText = '';\r | |
90 | \r | |
91 | while ( tabSpaces-- )\r | |
92 | tabText += '\xa0';\r | |
93 | \r | |
94 | if ( tabText ) {\r | |
95 | editor.on( 'key', function( ev ) {\r | |
96 | // TAB.\r | |
97 | if ( ev.data.keyCode == 9 ) {\r | |
98 | editor.insertText( tabText );\r | |
99 | ev.cancel();\r | |
100 | }\r | |
101 | } );\r | |
102 | }\r | |
103 | \r | |
104 | if ( tabTools ) {\r | |
105 | editor.on( 'key', function( ev ) {\r | |
106 | if ( ev.data.keyCode == 9 && editor.execCommand( 'selectNextCell' ) || // TAB\r | |
107 | ev.data.keyCode == ( CKEDITOR.SHIFT + 9 ) && editor.execCommand( 'selectPreviousCell' ) ) // SHIFT+TAB\r | |
108 | ev.cancel();\r | |
109 | } );\r | |
110 | }\r | |
111 | \r | |
112 | editor.addCommand( 'blur', CKEDITOR.tools.extend( blurCommand, meta ) );\r | |
113 | editor.addCommand( 'blurBack', CKEDITOR.tools.extend( blurBackCommand, meta ) );\r | |
114 | editor.addCommand( 'selectNextCell', selectNextCellCommand() );\r | |
115 | editor.addCommand( 'selectPreviousCell', selectNextCellCommand( true ) );\r | |
116 | }\r | |
117 | } );\r | |
118 | } )();\r | |
119 | \r | |
120 | /**\r | |
121 | * Moves the UI focus to the element following this element in the tabindex order.\r | |
122 | *\r | |
123 | * var element = CKEDITOR.document.getById( 'example' );\r | |
124 | * element.focusNext();\r | |
125 | *\r | |
126 | * @param {Boolean} [ignoreChildren=false]\r | |
127 | * @param {Number} [indexToUse]\r | |
128 | * @member CKEDITOR.dom.element\r | |
129 | */\r | |
130 | CKEDITOR.dom.element.prototype.focusNext = function( ignoreChildren, indexToUse ) {\r | |
131 | var curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ),\r | |
132 | passedCurrent, enteredCurrent, elected, electedTabIndex, element, elementTabIndex;\r | |
133 | \r | |
134 | if ( curTabIndex <= 0 ) {\r | |
135 | // If this element has tabindex <= 0 then we must simply look for any\r | |
136 | // element following it containing tabindex=0.\r | |
137 | \r | |
138 | element = this.getNextSourceNode( ignoreChildren, CKEDITOR.NODE_ELEMENT );\r | |
139 | \r | |
140 | while ( element ) {\r | |
141 | if ( element.isVisible() && element.getTabIndex() === 0 ) {\r | |
142 | elected = element;\r | |
143 | break;\r | |
144 | }\r | |
145 | \r | |
146 | element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT );\r | |
147 | }\r | |
148 | } else {\r | |
149 | // If this element has tabindex > 0 then we must look for:\r | |
150 | // 1. An element following this element with the same tabindex.\r | |
151 | // 2. The first element in source other with the lowest tabindex\r | |
152 | // that is higher than this element tabindex.\r | |
153 | // 3. The first element with tabindex=0.\r | |
154 | \r | |
155 | element = this.getDocument().getBody().getFirst();\r | |
156 | \r | |
157 | while ( ( element = element.getNextSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) ) {\r | |
158 | if ( !passedCurrent ) {\r | |
159 | if ( !enteredCurrent && element.equals( this ) ) {\r | |
160 | enteredCurrent = true;\r | |
161 | \r | |
162 | // Ignore this element, if required.\r | |
163 | if ( ignoreChildren ) {\r | |
164 | if ( !( element = element.getNextSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )\r | |
165 | break;\r | |
166 | passedCurrent = 1;\r | |
167 | }\r | |
168 | } else if ( enteredCurrent && !this.contains( element ) ) {\r | |
169 | passedCurrent = 1;\r | |
170 | }\r | |
171 | }\r | |
172 | \r | |
173 | if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )\r | |
174 | continue;\r | |
175 | \r | |
176 | if ( passedCurrent && elementTabIndex == curTabIndex ) {\r | |
177 | elected = element;\r | |
178 | break;\r | |
179 | }\r | |
180 | \r | |
181 | if ( elementTabIndex > curTabIndex && ( !elected || !electedTabIndex || elementTabIndex < electedTabIndex ) ) {\r | |
182 | elected = element;\r | |
183 | electedTabIndex = elementTabIndex;\r | |
184 | } else if ( !elected && elementTabIndex === 0 ) {\r | |
185 | elected = element;\r | |
186 | electedTabIndex = elementTabIndex;\r | |
187 | }\r | |
188 | }\r | |
189 | }\r | |
190 | \r | |
191 | if ( elected )\r | |
192 | elected.focus();\r | |
193 | };\r | |
194 | \r | |
195 | /**\r | |
196 | * Moves the UI focus to the element before this element in the tabindex order.\r | |
197 | *\r | |
198 | * var element = CKEDITOR.document.getById( 'example' );\r | |
199 | * element.focusPrevious();\r | |
200 | *\r | |
201 | * @param {Boolean} [ignoreChildren=false]\r | |
202 | * @param {Number} [indexToUse]\r | |
203 | * @member CKEDITOR.dom.element\r | |
204 | */\r | |
205 | CKEDITOR.dom.element.prototype.focusPrevious = function( ignoreChildren, indexToUse ) {\r | |
206 | var curTabIndex = ( indexToUse === undefined ? this.getTabIndex() : indexToUse ),\r | |
207 | passedCurrent, enteredCurrent, elected,\r | |
208 | electedTabIndex = 0,\r | |
209 | elementTabIndex;\r | |
210 | \r | |
211 | var element = this.getDocument().getBody().getLast();\r | |
212 | \r | |
213 | while ( ( element = element.getPreviousSourceNode( false, CKEDITOR.NODE_ELEMENT ) ) ) {\r | |
214 | if ( !passedCurrent ) {\r | |
215 | if ( !enteredCurrent && element.equals( this ) ) {\r | |
216 | enteredCurrent = true;\r | |
217 | \r | |
218 | // Ignore this element, if required.\r | |
219 | if ( ignoreChildren ) {\r | |
220 | if ( !( element = element.getPreviousSourceNode( true, CKEDITOR.NODE_ELEMENT ) ) )\r | |
221 | break;\r | |
222 | passedCurrent = 1;\r | |
223 | }\r | |
224 | } else if ( enteredCurrent && !this.contains( element ) ) {\r | |
225 | passedCurrent = 1;\r | |
226 | }\r | |
227 | }\r | |
228 | \r | |
229 | if ( !element.isVisible() || ( elementTabIndex = element.getTabIndex() ) < 0 )\r | |
230 | continue;\r | |
231 | \r | |
232 | if ( curTabIndex <= 0 ) {\r | |
233 | // If this element has tabindex <= 0 then we must look for:\r | |
234 | // 1. An element before this one containing tabindex=0.\r | |
235 | // 2. The last element with the highest tabindex.\r | |
236 | \r | |
237 | if ( passedCurrent && elementTabIndex === 0 ) {\r | |
238 | elected = element;\r | |
239 | break;\r | |
240 | }\r | |
241 | \r | |
242 | if ( elementTabIndex > electedTabIndex ) {\r | |
243 | elected = element;\r | |
244 | electedTabIndex = elementTabIndex;\r | |
245 | }\r | |
246 | } else {\r | |
247 | // If this element has tabindex > 0 we must look for:\r | |
248 | // 1. An element preceeding this one, with the same tabindex.\r | |
249 | // 2. The last element in source other with the highest tabindex\r | |
250 | // that is lower than this element tabindex.\r | |
251 | \r | |
252 | if ( passedCurrent && elementTabIndex == curTabIndex ) {\r | |
253 | elected = element;\r | |
254 | break;\r | |
255 | }\r | |
256 | \r | |
257 | if ( elementTabIndex < curTabIndex && ( !elected || elementTabIndex > electedTabIndex ) ) {\r | |
258 | elected = element;\r | |
259 | electedTabIndex = elementTabIndex;\r | |
260 | }\r | |
261 | }\r | |
262 | }\r | |
263 | \r | |
264 | if ( elected )\r | |
265 | elected.focus();\r | |
266 | };\r | |
267 | \r | |
268 | /**\r | |
269 | * Intructs the editor to add a number of spaces (` `) to the text when\r | |
270 | * hitting the <kbd>Tab</kbd> key. If set to zero, the <kbd>Tab</kbd> key will be used to move the\r | |
271 | * cursor focus to the next element in the page, out of the editor focus.\r | |
272 | *\r | |
273 | * config.tabSpaces = 4;\r | |
274 | *\r | |
275 | * @cfg {Number} [tabSpaces=0]\r | |
276 | * @member CKEDITOR.config\r | |
277 | */\r | |
278 | \r | |
279 | /**\r | |
280 | * Allow context-sensitive <kbd>Tab</kbd> key behaviors, including the following scenarios:\r | |
281 | *\r | |
282 | * When selection is anchored inside **table cells**:\r | |
283 | *\r | |
284 | * * If <kbd>Tab</kbd> is pressed, select the content of the "next" cell. If in the last\r | |
285 | * cell in the table, add a new row to it and focus its first cell.\r | |
286 | * * If <kbd>Shift+Tab</kbd> is pressed, select the content of the "previous" cell.\r | |
287 | * Do nothing when it is in the first cell.\r | |
288 | *\r | |
289 | * Example:\r | |
290 | *\r | |
291 | * config.enableTabKeyTools = false;\r | |
292 | *\r | |
293 | * @cfg {Boolean} [enableTabKeyTools=true]\r | |
294 | * @member CKEDITOR.config\r | |
295 | */\r | |
296 | \r | |
297 | // If the <kbd>Tab</kbd> key is not supposed to be enabled for navigation, the following\r | |
298 | // settings could be used alternatively:\r | |
299 | // config.keystrokes.push(\r | |
300 | // [ CKEDITOR.ALT + 38 /*Arrow Up*/, 'selectPreviousCell' ],\r | |
301 | // [ CKEDITOR.ALT + 40 /*Arrow Down*/, 'selectNextCell' ]\r | |
302 | // );\r |