tinymce-rails 4.0.16 → 4.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. checksums.yaml +7 -0
  2. data/app/assets/source/tinymce/tinymce.jquery.js +3060 -2879
  3. data/app/assets/source/tinymce/tinymce.js +1386 -1205
  4. data/lib/tinymce/rails/configuration.rb +15 -1
  5. data/lib/tinymce/rails/helper.rb +4 -4
  6. data/lib/tinymce/rails/version.rb +2 -2
  7. data/vendor/assets/javascripts/tinymce/plugins/autolink/plugin.js +1 -1
  8. data/vendor/assets/javascripts/tinymce/plugins/autoresize/plugin.js +1 -1
  9. data/vendor/assets/javascripts/tinymce/plugins/charmap/plugin.js +1 -1
  10. data/vendor/assets/javascripts/tinymce/plugins/contextmenu/plugin.js +1 -1
  11. data/vendor/assets/javascripts/tinymce/plugins/emoticons/plugin.js +1 -1
  12. data/vendor/assets/javascripts/tinymce/plugins/fullpage/plugin.js +1 -1
  13. data/vendor/assets/javascripts/tinymce/plugins/image/plugin.js +1 -1
  14. data/vendor/assets/javascripts/tinymce/plugins/insertdatetime/plugin.js +1 -1
  15. data/vendor/assets/javascripts/tinymce/plugins/link/plugin.js +1 -1
  16. data/vendor/assets/javascripts/tinymce/plugins/media/plugin.js +1 -1
  17. data/vendor/assets/javascripts/tinymce/plugins/nonbreaking/plugin.js +1 -1
  18. data/vendor/assets/javascripts/tinymce/plugins/paste/plugin.js +1 -1
  19. data/vendor/assets/javascripts/tinymce/plugins/searchreplace/plugin.js +1 -1
  20. data/vendor/assets/javascripts/tinymce/plugins/spellchecker/plugin.js +1 -1
  21. data/vendor/assets/javascripts/tinymce/plugins/table/plugin.js +1 -1
  22. data/vendor/assets/javascripts/tinymce/plugins/textcolor/plugin.js +1 -1
  23. data/vendor/assets/javascripts/tinymce/plugins/visualchars/plugin.js +1 -1
  24. data/vendor/assets/javascripts/tinymce/plugins/wordcount/plugin.js +1 -1
  25. data/vendor/assets/javascripts/tinymce/skins/lightgray/content.inline.min.css +1 -1
  26. data/vendor/assets/javascripts/tinymce/skins/lightgray/content.min.css +1 -1
  27. data/vendor/assets/javascripts/tinymce/skins/lightgray/skin.ie7.min.css +1 -1
  28. data/vendor/assets/javascripts/tinymce/skins/lightgray/skin.min.css +1 -1
  29. data/vendor/assets/javascripts/tinymce/themes/modern/theme.js +1 -1
  30. data/vendor/assets/javascripts/tinymce/tinymce.jquery.js +10 -9
  31. data/vendor/assets/javascripts/tinymce/tinymce.js +10 -10
  32. metadata +13 -18
@@ -1,4 +1,4 @@
1
- // 4.0.16 (2014-01-31)
1
+ // 4.0.18 (2014-02-27)
2
2
 
3
3
  /**
4
4
  * Compiled inline version. (Library mode)
@@ -192,7 +192,7 @@ define("tinymce/dom/EventUtils", [], function() {
192
192
  } else {
193
193
  originalEvent.cancelBubble = true; // IE
194
194
  }
195
- }
195
+ }
196
196
  };
197
197
 
198
198
  // Add stopImmediatePropagation
@@ -232,7 +232,9 @@ define("tinymce/dom/EventUtils", [], function() {
232
232
  }
233
233
 
234
234
  function waitForDomLoaded() {
235
- if (doc.readyState === "complete" || doc.readyState === "interactive") {
235
+ // Check complete or interactive state if there is a body
236
+ // element on some iframes IE 8 will produce a null body
237
+ if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
236
238
  removeEvent(doc, "readystatechange", waitForDomLoaded);
237
239
  readyHandler();
238
240
  }
@@ -4604,7 +4606,7 @@ define("tinymce/dom/Range", [
4604
4606
  return _traverseCommonAncestors(startNode, endNode, how);
4605
4607
  }
4606
4608
 
4607
- function _traverseSameContainer(how) {
4609
+ function _traverseSameContainer(how) {
4608
4610
  var frag, s, sub, n, cnt, sibling, xferNode, start, len;
4609
4611
 
4610
4612
  if (how != DELETE) {
@@ -5392,190 +5394,190 @@ define("tinymce/Env", [], function() {
5392
5394
 
5393
5395
  // Included from: js/tinymce/classes/dom/StyleSheetLoader.js
5394
5396
 
5395
- /**
5396
- * StyleSheetLoader.js
5397
- *
5398
- * Copyright, Moxiecode Systems AB
5399
- * Released under LGPL License.
5400
- *
5401
- * License: http://www.tinymce.com/license
5402
- * Contributing: http://www.tinymce.com/contributing
5403
- */
5404
-
5405
- /**
5406
- * This class handles loading of external stylesheets and fires events when these are loaded.
5407
- *
5408
- * @class tinymce.dom.StyleSheetLoader
5409
- * @private
5410
- */
5411
- define("tinymce/dom/StyleSheetLoader", [], function() {
5412
- "use strict";
5413
-
5414
- return function(document, settings) {
5415
- var idCount = 0, loadedStates = {}, maxLoadTime;
5416
-
5417
- settings = settings || {};
5418
- maxLoadTime = settings.maxLoadTime || 5000;
5419
-
5420
- function appendToHead(node) {
5421
- document.getElementsByTagName('head')[0].appendChild(node);
5422
- }
5423
-
5424
- /**
5425
- * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
5426
- *
5427
- * @method load
5428
- * @param {String} url Url to be loaded.
5429
- * @param {Function} loadedCallback Callback to be executed when loaded.
5430
- * @param {Function} errorCallback Callback to be executed when failed loading.
5431
- */
5432
- function load(url, loadedCallback, errorCallback) {
5433
- var link, style, startTime, state;
5434
-
5435
- function passed() {
5436
- var callbacks = state.passed, i = callbacks.length;
5437
-
5438
- while (i--) {
5439
- callbacks[i]();
5440
- }
5441
-
5442
- state.status = 2;
5443
- state.passed = [];
5444
- state.failed = [];
5445
- }
5446
-
5447
- function failed() {
5448
- var callbacks = state.failed, i = callbacks.length;
5449
-
5450
- while (i--) {
5451
- callbacks[i]();
5452
- }
5453
-
5454
- state.status = 3;
5455
- state.passed = [];
5456
- state.failed = [];
5457
- }
5458
-
5459
- // Sniffs for older WebKit versions that have the link.onload but a broken one
5460
- function isOldWebKit() {
5461
- var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
5462
- return !!(webKitChunks && webKitChunks[1] < 536);
5463
- }
5464
-
5465
- // Calls the waitCallback until the test returns true or the timeout occurs
5466
- function wait(testCallback, waitCallback) {
5467
- if (!testCallback()) {
5468
- // Wait for timeout
5469
- if ((new Date().getTime()) - startTime < maxLoadTime) {
5470
- window.setTimeout(waitCallback, 0);
5471
- } else {
5472
- failed();
5473
- }
5474
- }
5475
- }
5476
-
5477
- // Workaround for WebKit that doesn't properly support the onload event for link elements
5478
- // Or WebKit that fires the onload event before the StyleSheet is added to the document
5479
- function waitForWebKitLinkLoaded() {
5480
- wait(function() {
5481
- var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
5482
-
5483
- while (i--) {
5484
- styleSheet = styleSheets[i];
5485
- owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
5486
- if (owner && owner.id === link.id) {
5487
- passed();
5488
- return true;
5489
- }
5490
- }
5491
- }, waitForWebKitLinkLoaded);
5492
- }
5493
-
5494
- // Workaround for older Geckos that doesn't have any onload event for StyleSheets
5495
- function waitForGeckoLinkLoaded() {
5496
- wait(function() {
5497
- try {
5498
- // Accessing the cssRules will throw an exception until the CSS file is loaded
5499
- var cssRules = style.sheet.cssRules;
5500
- passed();
5501
- return !!cssRules;
5502
- } catch (ex) {
5503
- // Ignore
5504
- }
5505
- }, waitForGeckoLinkLoaded);
5506
- }
5507
-
5508
- if (!loadedStates[url]) {
5509
- state = {
5510
- passed: [],
5511
- failed: []
5512
- };
5513
-
5514
- loadedStates[url] = state;
5515
- } else {
5516
- state = loadedStates[url];
5517
- }
5518
-
5519
- if (loadedCallback) {
5520
- state.passed.push(loadedCallback);
5521
- }
5522
-
5523
- if (errorCallback) {
5524
- state.failed.push(errorCallback);
5525
- }
5526
-
5527
- // Is loading wait for it to pass
5528
- if (state.status == 1) {
5529
- return;
5530
- }
5531
-
5532
- // Has finished loading and was success
5533
- if (state.status == 2) {
5534
- passed();
5535
- return;
5536
- }
5537
-
5538
- // Has finished loading and was a failure
5539
- if (state.status == 3) {
5540
- failed();
5541
- return;
5542
- }
5543
-
5544
- // Start loading
5545
- state.status = 1;
5546
- link = document.createElement('link');
5547
- link.rel = 'stylesheet';
5548
- link.type = 'text/css';
5549
- link.id = 'u' + (idCount++);
5550
- link.async = false;
5551
- link.defer = false;
5552
- startTime = new Date().getTime();
5553
-
5554
- // Feature detect onload on link element and sniff older webkits since it has an broken onload event
5555
- if ("onload" in link && !isOldWebKit()) {
5556
- link.onload = waitForWebKitLinkLoaded;
5557
- link.onerror = failed;
5558
- } else {
5559
- // Sniff for old Firefox that doesn't support the onload event on link elements
5560
- // TODO: Remove this in the future when everyone uses modern browsers
5561
- if (navigator.userAgent.indexOf("Firefox") > 0) {
5562
- style = document.createElement('style');
5563
- style.textContent = '@import "' + url + '"';
5564
- waitForGeckoLinkLoaded();
5565
- appendToHead(style);
5566
- return;
5567
- } else {
5568
- // Use the id owner on older webkits
5569
- waitForWebKitLinkLoaded();
5570
- }
5571
- }
5572
-
5573
- appendToHead(link);
5574
- link.href = url;
5575
- }
5576
-
5577
- this.load = load;
5578
- };
5397
+ /**
5398
+ * StyleSheetLoader.js
5399
+ *
5400
+ * Copyright, Moxiecode Systems AB
5401
+ * Released under LGPL License.
5402
+ *
5403
+ * License: http://www.tinymce.com/license
5404
+ * Contributing: http://www.tinymce.com/contributing
5405
+ */
5406
+
5407
+ /**
5408
+ * This class handles loading of external stylesheets and fires events when these are loaded.
5409
+ *
5410
+ * @class tinymce.dom.StyleSheetLoader
5411
+ * @private
5412
+ */
5413
+ define("tinymce/dom/StyleSheetLoader", [], function() {
5414
+ "use strict";
5415
+
5416
+ return function(document, settings) {
5417
+ var idCount = 0, loadedStates = {}, maxLoadTime;
5418
+
5419
+ settings = settings || {};
5420
+ maxLoadTime = settings.maxLoadTime || 5000;
5421
+
5422
+ function appendToHead(node) {
5423
+ document.getElementsByTagName('head')[0].appendChild(node);
5424
+ }
5425
+
5426
+ /**
5427
+ * Loads the specified css style sheet file and call the loadedCallback once it's finished loading.
5428
+ *
5429
+ * @method load
5430
+ * @param {String} url Url to be loaded.
5431
+ * @param {Function} loadedCallback Callback to be executed when loaded.
5432
+ * @param {Function} errorCallback Callback to be executed when failed loading.
5433
+ */
5434
+ function load(url, loadedCallback, errorCallback) {
5435
+ var link, style, startTime, state;
5436
+
5437
+ function passed() {
5438
+ var callbacks = state.passed, i = callbacks.length;
5439
+
5440
+ while (i--) {
5441
+ callbacks[i]();
5442
+ }
5443
+
5444
+ state.status = 2;
5445
+ state.passed = [];
5446
+ state.failed = [];
5447
+ }
5448
+
5449
+ function failed() {
5450
+ var callbacks = state.failed, i = callbacks.length;
5451
+
5452
+ while (i--) {
5453
+ callbacks[i]();
5454
+ }
5455
+
5456
+ state.status = 3;
5457
+ state.passed = [];
5458
+ state.failed = [];
5459
+ }
5460
+
5461
+ // Sniffs for older WebKit versions that have the link.onload but a broken one
5462
+ function isOldWebKit() {
5463
+ var webKitChunks = navigator.userAgent.match(/WebKit\/(\d*)/);
5464
+ return !!(webKitChunks && webKitChunks[1] < 536);
5465
+ }
5466
+
5467
+ // Calls the waitCallback until the test returns true or the timeout occurs
5468
+ function wait(testCallback, waitCallback) {
5469
+ if (!testCallback()) {
5470
+ // Wait for timeout
5471
+ if ((new Date().getTime()) - startTime < maxLoadTime) {
5472
+ window.setTimeout(waitCallback, 0);
5473
+ } else {
5474
+ failed();
5475
+ }
5476
+ }
5477
+ }
5478
+
5479
+ // Workaround for WebKit that doesn't properly support the onload event for link elements
5480
+ // Or WebKit that fires the onload event before the StyleSheet is added to the document
5481
+ function waitForWebKitLinkLoaded() {
5482
+ wait(function() {
5483
+ var styleSheets = document.styleSheets, styleSheet, i = styleSheets.length, owner;
5484
+
5485
+ while (i--) {
5486
+ styleSheet = styleSheets[i];
5487
+ owner = styleSheet.ownerNode ? styleSheet.ownerNode : styleSheet.owningElement;
5488
+ if (owner && owner.id === link.id) {
5489
+ passed();
5490
+ return true;
5491
+ }
5492
+ }
5493
+ }, waitForWebKitLinkLoaded);
5494
+ }
5495
+
5496
+ // Workaround for older Geckos that doesn't have any onload event for StyleSheets
5497
+ function waitForGeckoLinkLoaded() {
5498
+ wait(function() {
5499
+ try {
5500
+ // Accessing the cssRules will throw an exception until the CSS file is loaded
5501
+ var cssRules = style.sheet.cssRules;
5502
+ passed();
5503
+ return !!cssRules;
5504
+ } catch (ex) {
5505
+ // Ignore
5506
+ }
5507
+ }, waitForGeckoLinkLoaded);
5508
+ }
5509
+
5510
+ if (!loadedStates[url]) {
5511
+ state = {
5512
+ passed: [],
5513
+ failed: []
5514
+ };
5515
+
5516
+ loadedStates[url] = state;
5517
+ } else {
5518
+ state = loadedStates[url];
5519
+ }
5520
+
5521
+ if (loadedCallback) {
5522
+ state.passed.push(loadedCallback);
5523
+ }
5524
+
5525
+ if (errorCallback) {
5526
+ state.failed.push(errorCallback);
5527
+ }
5528
+
5529
+ // Is loading wait for it to pass
5530
+ if (state.status == 1) {
5531
+ return;
5532
+ }
5533
+
5534
+ // Has finished loading and was success
5535
+ if (state.status == 2) {
5536
+ passed();
5537
+ return;
5538
+ }
5539
+
5540
+ // Has finished loading and was a failure
5541
+ if (state.status == 3) {
5542
+ failed();
5543
+ return;
5544
+ }
5545
+
5546
+ // Start loading
5547
+ state.status = 1;
5548
+ link = document.createElement('link');
5549
+ link.rel = 'stylesheet';
5550
+ link.type = 'text/css';
5551
+ link.id = 'u' + (idCount++);
5552
+ link.async = false;
5553
+ link.defer = false;
5554
+ startTime = new Date().getTime();
5555
+
5556
+ // Feature detect onload on link element and sniff older webkits since it has an broken onload event
5557
+ if ("onload" in link && !isOldWebKit()) {
5558
+ link.onload = waitForWebKitLinkLoaded;
5559
+ link.onerror = failed;
5560
+ } else {
5561
+ // Sniff for old Firefox that doesn't support the onload event on link elements
5562
+ // TODO: Remove this in the future when everyone uses modern browsers
5563
+ if (navigator.userAgent.indexOf("Firefox") > 0) {
5564
+ style = document.createElement('style');
5565
+ style.textContent = '@import "' + url + '"';
5566
+ waitForGeckoLinkLoaded();
5567
+ appendToHead(style);
5568
+ return;
5569
+ } else {
5570
+ // Use the id owner on older webkits
5571
+ waitForWebKitLinkLoaded();
5572
+ }
5573
+ }
5574
+
5575
+ appendToHead(link);
5576
+ link.href = url;
5577
+ }
5578
+
5579
+ this.load = load;
5580
+ };
5579
5581
  });
5580
5582
 
5581
5583
  // Included from: js/tinymce/classes/dom/DOMUtils.js
@@ -9052,7 +9054,7 @@ define("tinymce/html/Schema", [
9052
9054
  textBlockElementsMap = createLookupTable('text_block_elements', 'h1 h2 h3 h4 h5 h6 p div address pre form ' +
9053
9055
  'blockquote center dir fieldset header footer article section hgroup aside nav figure');
9054
9056
  blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' +
9055
- 'th tr td li ol ul caption dl dt dd noscript menu isindex samp option ' +
9057
+ 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' +
9056
9058
  'datalist select optgroup', textBlockElementsMap);
9057
9059
 
9058
9060
  each((settings.special || 'script noscript style textarea').split(' '), function(name) {
@@ -9566,7 +9568,7 @@ define("tinymce/html/Schema", [
9566
9568
 
9567
9569
  /**
9568
9570
  * Parses a valid elements string and adds it to the schema. The valid elements
9569
- format is for example "element[attr=default|otherattr]".
9571
+ * format is for example "element[attr=default|otherattr]".
9570
9572
  * Existing rules will be replaced with the ones specified, so this extends the schema.
9571
9573
  *
9572
9574
  * @method addValidElements
@@ -12273,8 +12275,6 @@ define("tinymce/dom/ControlSelection", [
12273
12275
  var handleElm, handlerContainerElm;
12274
12276
 
12275
12277
  function startDrag(e) {
12276
- resizeStarted = true;
12277
-
12278
12278
  startX = e.screenX;
12279
12279
  startY = e.screenY;
12280
12280
  startW = selectedElm.clientWidth;
@@ -12322,12 +12322,18 @@ define("tinymce/dom/ControlSelection", [
12322
12322
  id: 'mceResizeHandle' + name,
12323
12323
  'data-mce-bogus': true,
12324
12324
  'class': 'mce-resizehandle',
12325
- contentEditable: false, // Hides IE move layer cursor
12326
- unSelectabe: true,
12325
+ unselectable: true,
12327
12326
  style: 'cursor:' + name + '-resize; margin:0; padding:0'
12328
12327
  });
12329
12328
 
12329
+ // Hides IE move layer cursor
12330
+ // If we set it on Chrome we get this wounderful bug: #6725
12331
+ if (Env.ie) {
12332
+ handleElm.contentEditable = false;
12333
+ }
12334
+
12330
12335
  dom.bind(handleElm, 'mousedown', function(e) {
12336
+ e.stopImmediatePropagation();
12331
12337
  e.preventDefault();
12332
12338
  startDrag(e);
12333
12339
  });
@@ -12568,6 +12574,485 @@ define("tinymce/dom/ControlSelection", [
12568
12574
  };
12569
12575
  });
12570
12576
 
12577
+ // Included from: js/tinymce/classes/dom/RangeUtils.js
12578
+
12579
+ /**
12580
+ * Range.js
12581
+ *
12582
+ * Copyright, Moxiecode Systems AB
12583
+ * Released under LGPL License.
12584
+ *
12585
+ * License: http://www.tinymce.com/license
12586
+ * Contributing: http://www.tinymce.com/contributing
12587
+ */
12588
+
12589
+ /**
12590
+ * RangeUtils
12591
+ *
12592
+ * @class tinymce.dom.RangeUtils
12593
+ * @private
12594
+ */
12595
+ define("tinymce/dom/RangeUtils", [
12596
+ "tinymce/util/Tools",
12597
+ "tinymce/dom/TreeWalker"
12598
+ ], function(Tools, TreeWalker) {
12599
+ var each = Tools.each;
12600
+
12601
+ function RangeUtils(dom) {
12602
+ /**
12603
+ * Walks the specified range like object and executes the callback for each sibling collection it finds.
12604
+ *
12605
+ * @method walk
12606
+ * @param {Object} rng Range like object.
12607
+ * @param {function} callback Callback function to execute for each sibling collection.
12608
+ */
12609
+ this.walk = function(rng, callback) {
12610
+ var startContainer = rng.startContainer,
12611
+ startOffset = rng.startOffset,
12612
+ endContainer = rng.endContainer,
12613
+ endOffset = rng.endOffset,
12614
+ ancestor, startPoint,
12615
+ endPoint, node, parent, siblings, nodes;
12616
+
12617
+ // Handle table cell selection the table plugin enables
12618
+ // you to fake select table cells and perform formatting actions on them
12619
+ nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
12620
+ if (nodes.length > 0) {
12621
+ each(nodes, function(node) {
12622
+ callback([node]);
12623
+ });
12624
+
12625
+ return;
12626
+ }
12627
+
12628
+ /**
12629
+ * Excludes start/end text node if they are out side the range
12630
+ *
12631
+ * @private
12632
+ * @param {Array} nodes Nodes to exclude items from.
12633
+ * @return {Array} Array with nodes excluding the start/end container if needed.
12634
+ */
12635
+ function exclude(nodes) {
12636
+ var node;
12637
+
12638
+ // First node is excluded
12639
+ node = nodes[0];
12640
+ if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
12641
+ nodes.splice(0, 1);
12642
+ }
12643
+
12644
+ // Last node is excluded
12645
+ node = nodes[nodes.length - 1];
12646
+ if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
12647
+ nodes.splice(nodes.length - 1, 1);
12648
+ }
12649
+
12650
+ return nodes;
12651
+ }
12652
+
12653
+ /**
12654
+ * Collects siblings
12655
+ *
12656
+ * @private
12657
+ * @param {Node} node Node to collect siblings from.
12658
+ * @param {String} name Name of the sibling to check for.
12659
+ * @return {Array} Array of collected siblings.
12660
+ */
12661
+ function collectSiblings(node, name, end_node) {
12662
+ var siblings = [];
12663
+
12664
+ for (; node && node != end_node; node = node[name]) {
12665
+ siblings.push(node);
12666
+ }
12667
+
12668
+ return siblings;
12669
+ }
12670
+
12671
+ /**
12672
+ * Find an end point this is the node just before the common ancestor root.
12673
+ *
12674
+ * @private
12675
+ * @param {Node} node Node to start at.
12676
+ * @param {Node} root Root/ancestor element to stop just before.
12677
+ * @return {Node} Node just before the root element.
12678
+ */
12679
+ function findEndPoint(node, root) {
12680
+ do {
12681
+ if (node.parentNode == root) {
12682
+ return node;
12683
+ }
12684
+
12685
+ node = node.parentNode;
12686
+ } while(node);
12687
+ }
12688
+
12689
+ function walkBoundary(start_node, end_node, next) {
12690
+ var siblingName = next ? 'nextSibling' : 'previousSibling';
12691
+
12692
+ for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
12693
+ parent = node.parentNode;
12694
+ siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
12695
+
12696
+ if (siblings.length) {
12697
+ if (!next) {
12698
+ siblings.reverse();
12699
+ }
12700
+
12701
+ callback(exclude(siblings));
12702
+ }
12703
+ }
12704
+ }
12705
+
12706
+ // If index based start position then resolve it
12707
+ if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
12708
+ startContainer = startContainer.childNodes[startOffset];
12709
+ }
12710
+
12711
+ // If index based end position then resolve it
12712
+ if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
12713
+ endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
12714
+ }
12715
+
12716
+ // Same container
12717
+ if (startContainer == endContainer) {
12718
+ return callback(exclude([startContainer]));
12719
+ }
12720
+
12721
+ // Find common ancestor and end points
12722
+ ancestor = dom.findCommonAncestor(startContainer, endContainer);
12723
+
12724
+ // Process left side
12725
+ for (node = startContainer; node; node = node.parentNode) {
12726
+ if (node === endContainer) {
12727
+ return walkBoundary(startContainer, ancestor, true);
12728
+ }
12729
+
12730
+ if (node === ancestor) {
12731
+ break;
12732
+ }
12733
+ }
12734
+
12735
+ // Process right side
12736
+ for (node = endContainer; node; node = node.parentNode) {
12737
+ if (node === startContainer) {
12738
+ return walkBoundary(endContainer, ancestor);
12739
+ }
12740
+
12741
+ if (node === ancestor) {
12742
+ break;
12743
+ }
12744
+ }
12745
+
12746
+ // Find start/end point
12747
+ startPoint = findEndPoint(startContainer, ancestor) || startContainer;
12748
+ endPoint = findEndPoint(endContainer, ancestor) || endContainer;
12749
+
12750
+ // Walk left leaf
12751
+ walkBoundary(startContainer, startPoint, true);
12752
+
12753
+ // Walk the middle from start to end point
12754
+ siblings = collectSiblings(
12755
+ startPoint == startContainer ? startPoint : startPoint.nextSibling,
12756
+ 'nextSibling',
12757
+ endPoint == endContainer ? endPoint.nextSibling : endPoint
12758
+ );
12759
+
12760
+ if (siblings.length) {
12761
+ callback(exclude(siblings));
12762
+ }
12763
+
12764
+ // Walk right leaf
12765
+ walkBoundary(endContainer, endPoint);
12766
+ };
12767
+
12768
+ /**
12769
+ * Splits the specified range at it's start/end points.
12770
+ *
12771
+ * @private
12772
+ * @param {Range/RangeObject} rng Range to split.
12773
+ * @return {Object} Range position object.
12774
+ */
12775
+ this.split = function(rng) {
12776
+ var startContainer = rng.startContainer,
12777
+ startOffset = rng.startOffset,
12778
+ endContainer = rng.endContainer,
12779
+ endOffset = rng.endOffset;
12780
+
12781
+ function splitText(node, offset) {
12782
+ return node.splitText(offset);
12783
+ }
12784
+
12785
+ // Handle single text node
12786
+ if (startContainer == endContainer && startContainer.nodeType == 3) {
12787
+ if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
12788
+ endContainer = splitText(startContainer, startOffset);
12789
+ startContainer = endContainer.previousSibling;
12790
+
12791
+ if (endOffset > startOffset) {
12792
+ endOffset = endOffset - startOffset;
12793
+ startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
12794
+ endOffset = endContainer.nodeValue.length;
12795
+ startOffset = 0;
12796
+ } else {
12797
+ endOffset = 0;
12798
+ }
12799
+ }
12800
+ } else {
12801
+ // Split startContainer text node if needed
12802
+ if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
12803
+ startContainer = splitText(startContainer, startOffset);
12804
+ startOffset = 0;
12805
+ }
12806
+
12807
+ // Split endContainer text node if needed
12808
+ if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
12809
+ endContainer = splitText(endContainer, endOffset).previousSibling;
12810
+ endOffset = endContainer.nodeValue.length;
12811
+ }
12812
+ }
12813
+
12814
+ return {
12815
+ startContainer: startContainer,
12816
+ startOffset: startOffset,
12817
+ endContainer: endContainer,
12818
+ endOffset: endOffset
12819
+ };
12820
+ };
12821
+
12822
+ /**
12823
+ * Normalizes the specified range by finding the closest best suitable caret location.
12824
+ *
12825
+ * @private
12826
+ * @param {Range} rng Range to normalize.
12827
+ * @return {Boolean} True/false if the specified range was normalized or not.
12828
+ */
12829
+ this.normalize = function(rng) {
12830
+ var normalized, collapsed;
12831
+
12832
+ function normalizeEndPoint(start) {
12833
+ var container, offset, walker, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
12834
+ var directionLeft, isAfterNode;
12835
+
12836
+ function hasBrBeforeAfter(node, left) {
12837
+ var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
12838
+
12839
+ while ((node = walker[left ? 'prev' : 'next']())) {
12840
+ if (node.nodeName === "BR") {
12841
+ return true;
12842
+ }
12843
+ }
12844
+ }
12845
+
12846
+ function isPrevNode(node, name) {
12847
+ return node.previousSibling && node.previousSibling.nodeName == name;
12848
+ }
12849
+
12850
+ // Walks the dom left/right to find a suitable text node to move the endpoint into
12851
+ // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
12852
+ function findTextNodeRelative(left, startNode) {
12853
+ var walker, lastInlineElement, parentBlockContainer;
12854
+
12855
+ startNode = startNode || container;
12856
+ parentBlockContainer = dom.getParent(startNode.parentNode, dom.isBlock) || body;
12857
+
12858
+ // Lean left before the BR element if it's the only BR within a block element. Gecko bug: #6680
12859
+ // This: <p><br>|</p> becomes <p>|<br></p>
12860
+ if (left && startNode.nodeName == 'BR' && isAfterNode && dom.isEmpty(parentBlockContainer)) {
12861
+ container = startNode.parentNode;
12862
+ offset = dom.nodeIndex(startNode);
12863
+ normalized = true;
12864
+ return;
12865
+ }
12866
+
12867
+ // Walk left until we hit a text node we can move to or a block/br/img
12868
+ walker = new TreeWalker(startNode, parentBlockContainer);
12869
+ while ((node = walker[left ? 'prev' : 'next']())) {
12870
+ // Found text node that has a length
12871
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
12872
+ container = node;
12873
+ offset = left ? node.nodeValue.length : 0;
12874
+ normalized = true;
12875
+ return;
12876
+ }
12877
+
12878
+ // Break if we find a block or a BR/IMG/INPUT etc
12879
+ if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
12880
+ return;
12881
+ }
12882
+
12883
+ lastInlineElement = node;
12884
+ }
12885
+
12886
+ // Only fetch the last inline element when in caret mode for now
12887
+ if (collapsed && lastInlineElement) {
12888
+ container = lastInlineElement;
12889
+ normalized = true;
12890
+ offset = 0;
12891
+ }
12892
+ }
12893
+
12894
+ container = rng[(start ? 'start' : 'end') + 'Container'];
12895
+ offset = rng[(start ? 'start' : 'end') + 'Offset'];
12896
+ isAfterNode = container.nodeType == 1 && offset === container.childNodes.length;
12897
+ nonEmptyElementsMap = dom.schema.getNonEmptyElements();
12898
+ directionLeft = start;
12899
+
12900
+ if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
12901
+ directionLeft = false;
12902
+ }
12903
+
12904
+ // If the container is a document move it to the body element
12905
+ if (container.nodeType === 9) {
12906
+ container = dom.getRoot();
12907
+ offset = 0;
12908
+ }
12909
+
12910
+ // If the container is body try move it into the closest text node or position
12911
+ if (container === body) {
12912
+ // If start is before/after a image, table etc
12913
+ if (directionLeft) {
12914
+ node = container.childNodes[offset > 0 ? offset - 1 : 0];
12915
+ if (node) {
12916
+ nodeName = node.nodeName.toLowerCase();
12917
+ if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
12918
+ return;
12919
+ }
12920
+ }
12921
+ }
12922
+
12923
+ // Resolve the index
12924
+ if (container.hasChildNodes()) {
12925
+ offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
12926
+ container = container.childNodes[offset];
12927
+ offset = 0;
12928
+
12929
+ // Don't walk into elements that doesn't have any child nodes like a IMG
12930
+ if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
12931
+ // Walk the DOM to find a text node to place the caret at or a BR
12932
+ node = container;
12933
+ walker = new TreeWalker(container, body);
12934
+
12935
+ do {
12936
+ // Found a text node use that position
12937
+ if (node.nodeType === 3 && node.nodeValue.length > 0) {
12938
+ offset = directionLeft ? 0 : node.nodeValue.length;
12939
+ container = node;
12940
+ normalized = true;
12941
+ break;
12942
+ }
12943
+
12944
+ // Found a BR/IMG element that we can place the caret before
12945
+ if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
12946
+ offset = dom.nodeIndex(node);
12947
+ container = node.parentNode;
12948
+
12949
+ // Put caret after image when moving the end point
12950
+ if (node.nodeName == "IMG" && !directionLeft) {
12951
+ offset++;
12952
+ }
12953
+
12954
+ normalized = true;
12955
+ break;
12956
+ }
12957
+ } while ((node = (directionLeft ? walker.next() : walker.prev())));
12958
+ }
12959
+ }
12960
+ }
12961
+
12962
+ // Lean the caret to the left if possible
12963
+ if (collapsed) {
12964
+ // So this: <b>x</b><i>|x</i>
12965
+ // Becomes: <b>x|</b><i>x</i>
12966
+ // Seems that only gecko has issues with this
12967
+ if (container.nodeType === 3 && offset === 0) {
12968
+ findTextNodeRelative(true);
12969
+ }
12970
+
12971
+ // Lean left into empty inline elements when the caret is before a BR
12972
+ // So this: <i><b></b><i>|<br></i>
12973
+ // Becomes: <i><b>|</b><i><br></i>
12974
+ // Seems that only gecko has issues with this.
12975
+ // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
12976
+ if (container.nodeType === 1) {
12977
+ node = container.childNodes[offset];
12978
+
12979
+ // Offset is after the containers last child
12980
+ // then use the previous child for normalization
12981
+ if (!node) {
12982
+ node = container.childNodes[offset - 1];
12983
+ }
12984
+
12985
+ if (node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
12986
+ !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
12987
+ findTextNodeRelative(true, node);
12988
+ }
12989
+ }
12990
+ }
12991
+
12992
+ // Lean the start of the selection right if possible
12993
+ // So this: x[<b>x]</b>
12994
+ // Becomes: x<b>[x]</b>
12995
+ if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
12996
+ findTextNodeRelative(false);
12997
+ }
12998
+
12999
+ // Set endpoint if it was normalized
13000
+ if (normalized) {
13001
+ rng['set' + (start ? 'Start' : 'End')](container, offset);
13002
+ }
13003
+ }
13004
+
13005
+ collapsed = rng.collapsed;
13006
+
13007
+ normalizeEndPoint(true);
13008
+
13009
+ if (!collapsed) {
13010
+ normalizeEndPoint();
13011
+ }
13012
+
13013
+ // If it was collapsed then make sure it still is
13014
+ if (normalized && collapsed) {
13015
+ rng.collapse(true);
13016
+ }
13017
+
13018
+ return normalized;
13019
+ };
13020
+ }
13021
+
13022
+ /**
13023
+ * Compares two ranges and checks if they are equal.
13024
+ *
13025
+ * @static
13026
+ * @method compareRanges
13027
+ * @param {DOMRange} rng1 First range to compare.
13028
+ * @param {DOMRange} rng2 First range to compare.
13029
+ * @return {Boolean} true/false if the ranges are equal.
13030
+ */
13031
+ RangeUtils.compareRanges = function(rng1, rng2) {
13032
+ if (rng1 && rng2) {
13033
+ // Compare native IE ranges
13034
+ if (rng1.item || rng1.duplicate) {
13035
+ // Both are control ranges and the selected element matches
13036
+ if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
13037
+ return true;
13038
+ }
13039
+
13040
+ // Both are text ranges and the range matches
13041
+ if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
13042
+ return true;
13043
+ }
13044
+ } else {
13045
+ // Compare w3c ranges
13046
+ return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
13047
+ }
13048
+ }
13049
+
13050
+ return false;
13051
+ };
13052
+
13053
+ return RangeUtils;
13054
+ });
13055
+
12571
13056
  // Included from: js/tinymce/classes/dom/Selection.js
12572
13057
 
12573
13058
  /**
@@ -12593,9 +13078,10 @@ define("tinymce/dom/Selection", [
12593
13078
  "tinymce/dom/TreeWalker",
12594
13079
  "tinymce/dom/TridentSelection",
12595
13080
  "tinymce/dom/ControlSelection",
13081
+ "tinymce/dom/RangeUtils",
12596
13082
  "tinymce/Env",
12597
13083
  "tinymce/util/Tools"
12598
- ], function(TreeWalker, TridentSelection, ControlSelection, Env, Tools) {
13084
+ ], function(TreeWalker, TridentSelection, ControlSelection, RangeUtils, Env, Tools) {
12599
13085
  var each = Tools.each, grep = Tools.grep, trim = Tools.trim;
12600
13086
  var isIE = Env.ie, isOpera = Env.opera;
12601
13087
 
@@ -13345,6 +13831,19 @@ define("tinymce/dom/Selection", [
13345
13831
  getRng: function(w3c) {
13346
13832
  var self = this, selection, rng, elm, doc = self.win.document, ieRng;
13347
13833
 
13834
+ function tryCompareBounderyPoints(how, sourceRange, destinationRange) {
13835
+ try {
13836
+ return sourceRange.compareBoundaryPoints(how, destinationRange);
13837
+ } catch (ex) {
13838
+ // Gecko throws wrong document exception if the range points
13839
+ // to nodes that where removed from the dom #6690
13840
+ // Browsers should mutate existing DOMRange instances so that they always point
13841
+ // to something in the document this is not the case in Gecko works fine in IE/WebKit/Blink
13842
+ // For performance reasons just return -1
13843
+ return -1;
13844
+ }
13845
+ }
13846
+
13348
13847
  // Use last rng passed from FocusManager if it's available this enables
13349
13848
  // calls to editor.selection.getStart() to work when caret focus is lost on IE
13350
13849
  if (!w3c && self.lastFocusBookmark) {
@@ -13412,8 +13911,8 @@ define("tinymce/dom/Selection", [
13412
13911
  }
13413
13912
 
13414
13913
  if (self.selectedRange && self.explicitRange) {
13415
- if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 &&
13416
- rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
13914
+ if (tryCompareBounderyPoints(rng.START_TO_START, rng, self.selectedRange) === 0 &&
13915
+ tryCompareBounderyPoints(rng.END_TO_END, rng, self.selectedRange) === 0) {
13417
13916
  // Safari, Opera and Chrome only ever select text which causes the range to change.
13418
13917
  // This lets us use the originally set range if the selection hasn't been changed by the user.
13419
13918
  rng = self.explicitRange;
@@ -13612,7 +14111,7 @@ define("tinymce/dom/Selection", [
13612
14111
  return selectedBlocks;
13613
14112
  },
13614
14113
 
13615
- isForward: function(){
14114
+ isForward: function() {
13616
14115
  var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
13617
14116
 
13618
14117
  // No support for selection direction then always return true
@@ -13632,188 +14131,13 @@ define("tinymce/dom/Selection", [
13632
14131
  },
13633
14132
 
13634
14133
  normalize: function() {
13635
- var self = this, rng, normalized, collapsed;
13636
-
13637
- function normalizeEndPoint(start) {
13638
- var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
13639
- var directionLeft;
13640
-
13641
- function hasBrBeforeAfter(node, left) {
13642
- var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
13643
-
13644
- while ((node = walker[left ? 'prev' : 'next']())) {
13645
- if (node.nodeName === "BR") {
13646
- return true;
13647
- }
13648
- }
13649
- }
13650
-
13651
- function isPrevNode(node, name) {
13652
- return node.previousSibling && node.previousSibling.nodeName == name;
13653
- }
13654
-
13655
- // Walks the dom left/right to find a suitable text node to move the endpoint into
13656
- // It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
13657
- function findTextNodeRelative(left, startNode) {
13658
- var walker, lastInlineElement;
13659
-
13660
- startNode = startNode || container;
13661
- walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
13662
-
13663
- // Walk left until we hit a text node we can move to or a block/br/img
13664
- while ((node = walker[left ? 'prev' : 'next']())) {
13665
- // Found text node that has a length
13666
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
13667
- container = node;
13668
- offset = left ? node.nodeValue.length : 0;
13669
- normalized = true;
13670
- return;
13671
- }
13672
-
13673
- // Break if we find a block or a BR/IMG/INPUT etc
13674
- if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
13675
- return;
13676
- }
13677
-
13678
- lastInlineElement = node;
13679
- }
14134
+ var self = this, rng = self.getRng();
13680
14135
 
13681
- // Only fetch the last inline element when in caret mode for now
13682
- if (collapsed && lastInlineElement) {
13683
- container = lastInlineElement;
13684
- normalized = true;
13685
- offset = 0;
13686
- }
13687
- }
13688
-
13689
- container = rng[(start ? 'start' : 'end') + 'Container'];
13690
- offset = rng[(start ? 'start' : 'end') + 'Offset'];
13691
- nonEmptyElementsMap = dom.schema.getNonEmptyElements();
13692
- directionLeft = start;
13693
-
13694
- if (container.nodeType == 1 && offset > container.childNodes.length - 1) {
13695
- directionLeft = false;
13696
- }
13697
-
13698
- // If the container is a document move it to the body element
13699
- if (container.nodeType === 9) {
13700
- container = dom.getRoot();
13701
- offset = 0;
13702
- }
13703
-
13704
- // If the container is body try move it into the closest text node or position
13705
- if (container === body) {
13706
- // If start is before/after a image, table etc
13707
- if (directionLeft) {
13708
- node = container.childNodes[offset > 0 ? offset - 1 : 0];
13709
- if (node) {
13710
- nodeName = node.nodeName.toLowerCase();
13711
- if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
13712
- return;
13713
- }
13714
- }
13715
- }
13716
-
13717
- // Resolve the index
13718
- if (container.hasChildNodes()) {
13719
- offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
13720
- container = container.childNodes[offset];
13721
- offset = 0;
13722
-
13723
- // Don't walk into elements that doesn't have any child nodes like a IMG
13724
- if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
13725
- // Walk the DOM to find a text node to place the caret at or a BR
13726
- node = container;
13727
- walker = new TreeWalker(container, body);
13728
-
13729
- do {
13730
- // Found a text node use that position
13731
- if (node.nodeType === 3 && node.nodeValue.length > 0) {
13732
- offset = directionLeft ? 0 : node.nodeValue.length;
13733
- container = node;
13734
- normalized = true;
13735
- break;
13736
- }
13737
-
13738
- // Found a BR/IMG element that we can place the caret before
13739
- if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
13740
- offset = dom.nodeIndex(node);
13741
- container = node.parentNode;
13742
-
13743
- // Put caret after image when moving the end point
13744
- if (node.nodeName == "IMG" && !directionLeft) {
13745
- offset++;
13746
- }
13747
-
13748
- normalized = true;
13749
- break;
13750
- }
13751
- } while ((node = (directionLeft ? walker.next() : walker.prev())));
13752
- }
13753
- }
13754
- }
13755
-
13756
- // Lean the caret to the left if possible
13757
- if (collapsed) {
13758
- // So this: <b>x</b><i>|x</i>
13759
- // Becomes: <b>x|</b><i>x</i>
13760
- // Seems that only gecko has issues with this
13761
- if (container.nodeType === 3 && offset === 0) {
13762
- findTextNodeRelative(true);
13763
- }
13764
-
13765
- // Lean left into empty inline elements when the caret is before a BR
13766
- // So this: <i><b></b><i>|<br></i>
13767
- // Becomes: <i><b>|</b><i><br></i>
13768
- // Seems that only gecko has issues with this.
13769
- // Special edge case for <p><a>x</a>|<br></p> since we don't want <p><a>x|</a><br></p>
13770
- if (container.nodeType === 1) {
13771
- node = container.childNodes[offset];
13772
- if(node && node.nodeName === 'BR' && !isPrevNode(node, 'A') &&
13773
- !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
13774
- findTextNodeRelative(true, container.childNodes[offset]);
13775
- }
13776
- }
13777
- }
13778
-
13779
- // Lean the start of the selection right if possible
13780
- // So this: x[<b>x]</b>
13781
- // Becomes: x<b>[x]</b>
13782
- if (directionLeft && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
13783
- findTextNodeRelative(false);
13784
- }
13785
-
13786
- // Set endpoint if it was normalized
13787
- if (normalized) {
13788
- rng['set' + (start ? 'Start' : 'End')](container, offset);
13789
- }
13790
- }
13791
-
13792
- // Normalize only on non IE browsers for now
13793
- if (isIE) {
13794
- return;
13795
- }
13796
-
13797
- rng = self.getRng();
13798
- collapsed = rng.collapsed;
13799
-
13800
- // Normalize the end points
13801
- normalizeEndPoint(true);
13802
-
13803
- if (!collapsed) {
13804
- normalizeEndPoint();
13805
- }
13806
-
13807
- // Set the selection if it was normalized
13808
- if (normalized) {
13809
- // If it was collapsed then make sure it still is
13810
- if (collapsed) {
13811
- rng.collapse(true);
13812
- }
13813
-
13814
- //console.log(self.dom.dumpRng(rng));
14136
+ if (!isIE && new RangeUtils(self.dom).normalize(rng)) {
13815
14137
  self.setRng(rng, self.isForward());
13816
14138
  }
14139
+
14140
+ return rng;
13817
14141
  },
13818
14142
 
13819
14143
  /**
@@ -13991,285 +14315,6 @@ define("tinymce/dom/Selection", [
13991
14315
  return Selection;
13992
14316
  });
13993
14317
 
13994
- // Included from: js/tinymce/classes/dom/RangeUtils.js
13995
-
13996
- /**
13997
- * Range.js
13998
- *
13999
- * Copyright, Moxiecode Systems AB
14000
- * Released under LGPL License.
14001
- *
14002
- * License: http://www.tinymce.com/license
14003
- * Contributing: http://www.tinymce.com/contributing
14004
- */
14005
-
14006
- /**
14007
- * RangeUtils
14008
- *
14009
- * @class tinymce.dom.RangeUtils
14010
- * @private
14011
- */
14012
- define("tinymce/dom/RangeUtils", [
14013
- "tinymce/util/Tools"
14014
- ], function(Tools) {
14015
- var each = Tools.each;
14016
-
14017
- function RangeUtils(dom) {
14018
- /**
14019
- * Walks the specified range like object and executes the callback for each sibling collection it finds.
14020
- *
14021
- * @method walk
14022
- * @param {Object} rng Range like object.
14023
- * @param {function} callback Callback function to execute for each sibling collection.
14024
- */
14025
- this.walk = function(rng, callback) {
14026
- var startContainer = rng.startContainer,
14027
- startOffset = rng.startOffset,
14028
- endContainer = rng.endContainer,
14029
- endOffset = rng.endOffset,
14030
- ancestor, startPoint,
14031
- endPoint, node, parent, siblings, nodes;
14032
-
14033
- // Handle table cell selection the table plugin enables
14034
- // you to fake select table cells and perform formatting actions on them
14035
- nodes = dom.select('td.mce-item-selected,th.mce-item-selected');
14036
- if (nodes.length > 0) {
14037
- each(nodes, function(node) {
14038
- callback([node]);
14039
- });
14040
-
14041
- return;
14042
- }
14043
-
14044
- /**
14045
- * Excludes start/end text node if they are out side the range
14046
- *
14047
- * @private
14048
- * @param {Array} nodes Nodes to exclude items from.
14049
- * @return {Array} Array with nodes excluding the start/end container if needed.
14050
- */
14051
- function exclude(nodes) {
14052
- var node;
14053
-
14054
- // First node is excluded
14055
- node = nodes[0];
14056
- if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
14057
- nodes.splice(0, 1);
14058
- }
14059
-
14060
- // Last node is excluded
14061
- node = nodes[nodes.length - 1];
14062
- if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
14063
- nodes.splice(nodes.length - 1, 1);
14064
- }
14065
-
14066
- return nodes;
14067
- }
14068
-
14069
- /**
14070
- * Collects siblings
14071
- *
14072
- * @private
14073
- * @param {Node} node Node to collect siblings from.
14074
- * @param {String} name Name of the sibling to check for.
14075
- * @return {Array} Array of collected siblings.
14076
- */
14077
- function collectSiblings(node, name, end_node) {
14078
- var siblings = [];
14079
-
14080
- for (; node && node != end_node; node = node[name]) {
14081
- siblings.push(node);
14082
- }
14083
-
14084
- return siblings;
14085
- }
14086
-
14087
- /**
14088
- * Find an end point this is the node just before the common ancestor root.
14089
- *
14090
- * @private
14091
- * @param {Node} node Node to start at.
14092
- * @param {Node} root Root/ancestor element to stop just before.
14093
- * @return {Node} Node just before the root element.
14094
- */
14095
- function findEndPoint(node, root) {
14096
- do {
14097
- if (node.parentNode == root) {
14098
- return node;
14099
- }
14100
-
14101
- node = node.parentNode;
14102
- } while(node);
14103
- }
14104
-
14105
- function walkBoundary(start_node, end_node, next) {
14106
- var siblingName = next ? 'nextSibling' : 'previousSibling';
14107
-
14108
- for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
14109
- parent = node.parentNode;
14110
- siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
14111
-
14112
- if (siblings.length) {
14113
- if (!next) {
14114
- siblings.reverse();
14115
- }
14116
-
14117
- callback(exclude(siblings));
14118
- }
14119
- }
14120
- }
14121
-
14122
- // If index based start position then resolve it
14123
- if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
14124
- startContainer = startContainer.childNodes[startOffset];
14125
- }
14126
-
14127
- // If index based end position then resolve it
14128
- if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
14129
- endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
14130
- }
14131
-
14132
- // Same container
14133
- if (startContainer == endContainer) {
14134
- return callback(exclude([startContainer]));
14135
- }
14136
-
14137
- // Find common ancestor and end points
14138
- ancestor = dom.findCommonAncestor(startContainer, endContainer);
14139
-
14140
- // Process left side
14141
- for (node = startContainer; node; node = node.parentNode) {
14142
- if (node === endContainer) {
14143
- return walkBoundary(startContainer, ancestor, true);
14144
- }
14145
-
14146
- if (node === ancestor) {
14147
- break;
14148
- }
14149
- }
14150
-
14151
- // Process right side
14152
- for (node = endContainer; node; node = node.parentNode) {
14153
- if (node === startContainer) {
14154
- return walkBoundary(endContainer, ancestor);
14155
- }
14156
-
14157
- if (node === ancestor) {
14158
- break;
14159
- }
14160
- }
14161
-
14162
- // Find start/end point
14163
- startPoint = findEndPoint(startContainer, ancestor) || startContainer;
14164
- endPoint = findEndPoint(endContainer, ancestor) || endContainer;
14165
-
14166
- // Walk left leaf
14167
- walkBoundary(startContainer, startPoint, true);
14168
-
14169
- // Walk the middle from start to end point
14170
- siblings = collectSiblings(
14171
- startPoint == startContainer ? startPoint : startPoint.nextSibling,
14172
- 'nextSibling',
14173
- endPoint == endContainer ? endPoint.nextSibling : endPoint
14174
- );
14175
-
14176
- if (siblings.length) {
14177
- callback(exclude(siblings));
14178
- }
14179
-
14180
- // Walk right leaf
14181
- walkBoundary(endContainer, endPoint);
14182
- };
14183
-
14184
- /**
14185
- * Splits the specified range at it's start/end points.
14186
- *
14187
- * @private
14188
- * @param {Range/RangeObject} rng Range to split.
14189
- * @return {Object} Range position object.
14190
- */
14191
- this.split = function(rng) {
14192
- var startContainer = rng.startContainer,
14193
- startOffset = rng.startOffset,
14194
- endContainer = rng.endContainer,
14195
- endOffset = rng.endOffset;
14196
-
14197
- function splitText(node, offset) {
14198
- return node.splitText(offset);
14199
- }
14200
-
14201
- // Handle single text node
14202
- if (startContainer == endContainer && startContainer.nodeType == 3) {
14203
- if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
14204
- endContainer = splitText(startContainer, startOffset);
14205
- startContainer = endContainer.previousSibling;
14206
-
14207
- if (endOffset > startOffset) {
14208
- endOffset = endOffset - startOffset;
14209
- startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
14210
- endOffset = endContainer.nodeValue.length;
14211
- startOffset = 0;
14212
- } else {
14213
- endOffset = 0;
14214
- }
14215
- }
14216
- } else {
14217
- // Split startContainer text node if needed
14218
- if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
14219
- startContainer = splitText(startContainer, startOffset);
14220
- startOffset = 0;
14221
- }
14222
-
14223
- // Split endContainer text node if needed
14224
- if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
14225
- endContainer = splitText(endContainer, endOffset).previousSibling;
14226
- endOffset = endContainer.nodeValue.length;
14227
- }
14228
- }
14229
-
14230
- return {
14231
- startContainer: startContainer,
14232
- startOffset: startOffset,
14233
- endContainer: endContainer,
14234
- endOffset: endOffset
14235
- };
14236
- };
14237
- }
14238
-
14239
- /**
14240
- * Compares two ranges and checks if they are equal.
14241
- *
14242
- * @static
14243
- * @method compareRanges
14244
- * @param {DOMRange} rng1 First range to compare.
14245
- * @param {DOMRange} rng2 First range to compare.
14246
- * @return {Boolean} true/false if the ranges are equal.
14247
- */
14248
- RangeUtils.compareRanges = function(rng1, rng2) {
14249
- if (rng1 && rng2) {
14250
- // Compare native IE ranges
14251
- if (rng1.item || rng1.duplicate) {
14252
- // Both are control ranges and the selected element matches
14253
- if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0)) {
14254
- return true;
14255
- }
14256
-
14257
- // Both are text ranges and the range matches
14258
- if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1)) {
14259
- return true;
14260
- }
14261
- } else {
14262
- // Compare w3c ranges
14263
- return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
14264
- }
14265
- }
14266
-
14267
- return false;
14268
- };
14269
-
14270
- return RangeUtils;
14271
- });
14272
-
14273
14318
  // Included from: js/tinymce/classes/Formatter.js
14274
14319
 
14275
14320
  /**
@@ -14415,7 +14460,7 @@ define("tinymce/Formatter", [
14415
14460
 
14416
14461
  removeformat: [
14417
14462
  {
14418
- selector: 'b,strong,em,i,font,u,strike,sub,sup',
14463
+ selector: 'b,strong,em,i,font,u,strike,sub,sup,dfn,code,samp,kbd,var,cite,mark,q',
14419
14464
  remove: 'all',
14420
14465
  split: true,
14421
14466
  expand: false,
@@ -14830,7 +14875,7 @@ define("tinymce/Formatter", [
14830
14875
  });
14831
14876
 
14832
14877
  // If child was found and of the same type as the current node
14833
- if (child && matchName(child, format)) {
14878
+ if (child && !isBookmarkNode(child) && matchName(child, format)) {
14834
14879
  clone = dom.clone(child, FALSE);
14835
14880
  setElementFormat(clone);
14836
14881
 
@@ -14865,6 +14910,10 @@ define("tinymce/Formatter", [
14865
14910
  each(dom.select(format.inline, node), function(child) {
14866
14911
  var parent;
14867
14912
 
14913
+ if (isBookmarkNode(child)) {
14914
+ return;
14915
+ }
14916
+
14868
14917
  // When wrap_links is set to false we don't want
14869
14918
  // to remove the format on children within links
14870
14919
  if (format.wrap_links === false) {
@@ -16166,7 +16215,7 @@ define("tinymce/Formatter", [
16166
16215
  next = next ? 'nextSibling' : 'previousSibling';
16167
16216
 
16168
16217
  for (node = inc ? node : node[next]; node; node = node[next]) {
16169
- if (node.nodeType == 1 && !isWhiteSpaceNode(node)) {
16218
+ if (node.nodeType == 1 || !isWhiteSpaceNode(node)) {
16170
16219
  return node;
16171
16220
  }
16172
16221
  }
@@ -16284,7 +16333,7 @@ define("tinymce/Formatter", [
16284
16333
  return FALSE;
16285
16334
  }
16286
16335
 
16287
- return TRUE;
16336
+ return !isBookmarkNode(node1) && !isBookmarkNode(node2);
16288
16337
  }
16289
16338
 
16290
16339
  function findElementSibling(node, sibling_name) {
@@ -16731,9 +16780,9 @@ define("tinymce/UndoManager", [
16731
16780
  return trim(editor.getContent({format: 'raw', no_events: 1}).replace(trimContentRegExp, ''));
16732
16781
  }
16733
16782
 
16734
- function addNonTypingUndoLevel() {
16783
+ function addNonTypingUndoLevel(e) {
16735
16784
  self.typing = false;
16736
- self.add();
16785
+ self.add({}, e);
16737
16786
  }
16738
16787
 
16739
16788
  // Add initial undo level when the editor is initialized
@@ -16755,7 +16804,7 @@ define("tinymce/UndoManager", [
16755
16804
  var cmd = e.command;
16756
16805
 
16757
16806
  if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint') {
16758
- self.add();
16807
+ addNonTypingUndoLevel(e);
16759
16808
  }
16760
16809
  });
16761
16810
 
@@ -16763,13 +16812,8 @@ define("tinymce/UndoManager", [
16763
16812
  self.beforeChange();
16764
16813
  });
16765
16814
 
16766
- editor.on('SaveContent ObjectResized', addNonTypingUndoLevel);
16815
+ editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
16767
16816
  editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
16768
- editor.dom.bind(editor.getBody(), 'focusout', function() {
16769
- if (!editor.removed && self.typing) {
16770
- addNonTypingUndoLevel();
16771
- }
16772
- });
16773
16817
 
16774
16818
  editor.on('KeyUp', function(e) {
16775
16819
  var keyCode = e.keyCode;
@@ -16807,7 +16851,7 @@ define("tinymce/UndoManager", [
16807
16851
  // Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
16808
16852
  if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
16809
16853
  if (self.typing) {
16810
- addNonTypingUndoLevel();
16854
+ addNonTypingUndoLevel(e);
16811
16855
  }
16812
16856
 
16813
16857
  return;
@@ -16817,14 +16861,14 @@ define("tinymce/UndoManager", [
16817
16861
  if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
16818
16862
  self.beforeChange();
16819
16863
  self.typing = true;
16820
- self.add();
16864
+ self.add({}, e);
16821
16865
  isFirstTypedCharacter = true;
16822
16866
  }
16823
16867
  });
16824
16868
 
16825
- editor.on('MouseDown', function() {
16869
+ editor.on('MouseDown', function(e) {
16826
16870
  if (self.typing) {
16827
- addNonTypingUndoLevel();
16871
+ addNonTypingUndoLevel(e);
16828
16872
  }
16829
16873
  });
16830
16874
 
@@ -16866,16 +16910,21 @@ define("tinymce/UndoManager", [
16866
16910
  * Adds a new undo level/snapshot to the undo list.
16867
16911
  *
16868
16912
  * @method add
16869
- * @param {Object} l Optional undo level object to add.
16913
+ * @param {Object} level Optional undo level object to add.
16914
+ * @param {DOMEvent} Event Optional event responsible for the creation of the undo level.
16870
16915
  * @return {Object} Undo level that got added or null it a level wasn't needed.
16871
16916
  */
16872
- add: function(level) {
16917
+ add: function(level, event) {
16873
16918
  var i, settings = editor.settings, lastLevel;
16874
16919
 
16875
16920
  level = level || {};
16876
16921
  level.content = getContent();
16877
16922
 
16878
- if (lock || editor.fire('BeforeAddUndo', {level: level}).isDefaultPrevented()) {
16923
+ if (lock || editor.removed) {
16924
+ return null;
16925
+ }
16926
+
16927
+ if (editor.fire('BeforeAddUndo', {level: level, originalEvent: event}).isDefaultPrevented()) {
16879
16928
  return null;
16880
16929
  }
16881
16930
 
@@ -16913,13 +16962,13 @@ define("tinymce/UndoManager", [
16913
16962
  data.push(level);
16914
16963
  index = data.length - 1;
16915
16964
 
16916
- var args = {level: level, lastLevel: lastLevel};
16965
+ var args = {level: level, lastLevel: lastLevel, originalEvent: event};
16917
16966
 
16918
16967
  editor.fire('AddUndo', args);
16919
16968
 
16920
16969
  if (index > 0) {
16921
- editor.fire('change', args);
16922
16970
  editor.isNotDirty = false;
16971
+ editor.fire('change', args);
16923
16972
  }
16924
16973
 
16925
16974
  return level;
@@ -17051,8 +17100,9 @@ define("tinymce/UndoManager", [
17051
17100
  */
17052
17101
  define("tinymce/EnterKey", [
17053
17102
  "tinymce/dom/TreeWalker",
17103
+ "tinymce/dom/RangeUtils",
17054
17104
  "tinymce/Env"
17055
- ], function(TreeWalker, Env) {
17105
+ ], function(TreeWalker, RangeUtils, Env) {
17056
17106
  var isIE = Env.ie && Env.ie < 11;
17057
17107
 
17058
17108
  return function(editor) {
@@ -17060,7 +17110,7 @@ define("tinymce/EnterKey", [
17060
17110
  var undoManager = editor.undoManager, schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
17061
17111
 
17062
17112
  function handleEnterKey(evt) {
17063
- var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
17113
+ var rng, tmpRng, editableRoot, container, offset, parentBlock, documentMode, shiftKey,
17064
17114
  newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
17065
17115
 
17066
17116
  // Returns true if the block can be split into two blocks or not
@@ -17225,7 +17275,7 @@ define("tinymce/EnterKey", [
17225
17275
  // Clone any parent styles
17226
17276
  if (settings.keep_styles !== false) {
17227
17277
  do {
17228
- if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
17278
+ if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U|VAR|CITE|DFN|CODE|MARK|Q|SUP|SUB|SAMP)$/.test(node.nodeName)) {
17229
17279
  // Never clone a caret containers
17230
17280
  if (node.id == '_mce_caret') {
17231
17281
  continue;
@@ -17542,18 +17592,21 @@ define("tinymce/EnterKey", [
17542
17592
  }
17543
17593
  }
17544
17594
 
17545
- // Delete any selected contents
17546
- if (!rng.collapsed) {
17547
- editor.execCommand('Delete');
17548
- return;
17549
- }
17595
+ rng = selection.getRng(true);
17550
17596
 
17551
17597
  // Event is blocked by some other handler for example the lists plugin
17552
17598
  if (evt.isDefaultPrevented()) {
17553
17599
  return;
17554
17600
  }
17555
17601
 
17602
+ // Delete any selected contents
17603
+ if (!rng.collapsed) {
17604
+ editor.execCommand('Delete');
17605
+ return;
17606
+ }
17607
+
17556
17608
  // Setup range items and newBlockName
17609
+ new RangeUtils(dom).normalize(rng);
17557
17610
  container = rng.startContainer;
17558
17611
  offset = rng.startOffset;
17559
17612
  newBlockName = (settings.force_p_newlines ? 'p' : '') || settings.forced_root_block;
@@ -17564,6 +17617,7 @@ define("tinymce/EnterKey", [
17564
17617
  // Resolve node index
17565
17618
  if (container.nodeType == 1 && container.hasChildNodes()) {
17566
17619
  isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17620
+
17567
17621
  container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17568
17622
  if (isAfterLastNodeInContainer && container.nodeType == 3) {
17569
17623
  offset = container.nodeValue.length;
@@ -20070,7 +20124,7 @@ define("tinymce/ui/Control", [
20070
20124
  self.settings = settings = Tools.extend({}, self.Defaults, settings);
20071
20125
 
20072
20126
  // Initial states
20073
- self._id = DomUtils.id();
20127
+ self._id = settings.id || DomUtils.id();
20074
20128
  self._text = self._name = '';
20075
20129
  self._width = self._height = 0;
20076
20130
  self._aria = {role: settings.role};
@@ -21026,7 +21080,7 @@ define("tinymce/ui/Control", [
21026
21080
  * @return {tinymce.ui.Control} Current control instance.
21027
21081
  */
21028
21082
  aria: function(name, value) {
21029
- var self = this, elm = self.getEl();
21083
+ var self = this, elm = self.getEl(self.ariaTarget);
21030
21084
 
21031
21085
  if (typeof(value) === "undefined") {
21032
21086
  return self._aria[name];
@@ -21035,10 +21089,6 @@ define("tinymce/ui/Control", [
21035
21089
  }
21036
21090
 
21037
21091
  if (self._rendered) {
21038
- if (name == 'label') {
21039
- elm.setAttribute('aria-labelledby', self._id);
21040
- }
21041
-
21042
21092
  elm.setAttribute(name == 'role' ? name : 'aria-' + name, value);
21043
21093
  }
21044
21094
 
@@ -21669,6 +21719,399 @@ define("tinymce/ui/Factory", [], function() {
21669
21719
  };
21670
21720
  });
21671
21721
 
21722
+ // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
21723
+
21724
+ /**
21725
+ * KeyboardNavigation.js
21726
+ *
21727
+ * Copyright, Moxiecode Systems AB
21728
+ * Released under LGPL License.
21729
+ *
21730
+ * License: http://www.tinymce.com/license
21731
+ * Contributing: http://www.tinymce.com/contributing
21732
+ */
21733
+
21734
+ /**
21735
+ * This class handles keyboard navigation of controls and elements.
21736
+ *
21737
+ * @class tinymce.ui.KeyboardNavigation
21738
+ */
21739
+ define("tinymce/ui/KeyboardNavigation", [
21740
+ ], function() {
21741
+ "use strict";
21742
+
21743
+ /**
21744
+ * This class handles all keyboard navigation for WAI-ARIA support. Each root container
21745
+ * gets an instance of this class.
21746
+ *
21747
+ * @constructor
21748
+ */
21749
+ return function(settings) {
21750
+ var root = settings.root, focusedElement, focusedControl;
21751
+
21752
+ focusedElement = document.activeElement;
21753
+ focusedControl = root.getParentCtrl(focusedElement);
21754
+
21755
+ /**
21756
+ * Returns the currently focused elements wai aria role of the currently
21757
+ * focused element or specified element.
21758
+ *
21759
+ * @private
21760
+ * @param {Element} elm Optional element to get role from.
21761
+ * @return {String} Role of specified element.
21762
+ */
21763
+ function getRole(elm) {
21764
+ elm = elm || focusedElement;
21765
+
21766
+ return elm && elm.getAttribute('role');
21767
+ }
21768
+
21769
+ /**
21770
+ * Returns the wai role of the parent element of the currently
21771
+ * focused element or specified element.
21772
+ *
21773
+ * @private
21774
+ * @param {Element} elm Optional element to get parent role from.
21775
+ * @return {String} Role of the first parent that has a role.
21776
+ */
21777
+ function getParentRole(elm) {
21778
+ var role, parent = elm || focusedElement;
21779
+
21780
+ while ((parent = parent.parentNode)) {
21781
+ if ((role = getRole(parent))) {
21782
+ return role;
21783
+ }
21784
+ }
21785
+ }
21786
+
21787
+ /**
21788
+ * Returns a wai aria property by name for example aria-selected.
21789
+ *
21790
+ * @private
21791
+ * @param {String} name Name of the aria property to get for example "disabled".
21792
+ * @return {String} Aria property value.
21793
+ */
21794
+ function getAriaProp(name) {
21795
+ var elm = focusedElement;
21796
+
21797
+ if (elm) {
21798
+ return elm.getAttribute('aria-' + name);
21799
+ }
21800
+ }
21801
+
21802
+ /**
21803
+ * Is the element a text input element or not.
21804
+ *
21805
+ * @private
21806
+ * @param {Element} elm Element to check if it's an text input element or not.
21807
+ * @return {Boolean} True/false if the element is a text element or not.
21808
+ */
21809
+ function isTextInputElement(elm) {
21810
+ // Notice: since type can be "email" etc we don't check the type
21811
+ // So all input elements gets treated as text input elements
21812
+ return elm.tagName == "INPUT" || elm.tagName == "TEXTAREA";
21813
+ }
21814
+
21815
+ /**
21816
+ * Returns true/false if the specified element can be focused or not.
21817
+ *
21818
+ * @private
21819
+ * @param {Element} elm DOM element to check if it can be focused or not.
21820
+ * @return {Boolean} True/false if the element can have focus.
21821
+ */
21822
+ function canFocus(elm) {
21823
+ if (isTextInputElement(elm) && !elm.hidden) {
21824
+ return true;
21825
+ }
21826
+
21827
+ if (/^(button|menuitem|checkbox|tab|menuitemcheckbox|option|gridcell)$/.test(getRole(elm))) {
21828
+ return true;
21829
+ }
21830
+
21831
+ return false;
21832
+ }
21833
+
21834
+ /**
21835
+ * Returns an array of focusable visible elements within the specified container element.
21836
+ *
21837
+ * @private
21838
+ * @param {Element} elm DOM element to find focusable elements within.
21839
+ * @return {Array} Array of focusable elements.
21840
+ */
21841
+ function getFocusElements(elm) {
21842
+ var elements = [];
21843
+
21844
+ function collect(elm) {
21845
+ if (elm.nodeType != 1 || elm.style.display == 'none') {
21846
+ return;
21847
+ }
21848
+
21849
+ if (canFocus(elm)) {
21850
+ elements.push(elm);
21851
+ }
21852
+
21853
+ for (var i = 0; i < elm.childNodes.length; i++) {
21854
+ collect(elm.childNodes[i]);
21855
+ }
21856
+ }
21857
+
21858
+ collect(elm || root.getEl());
21859
+
21860
+ return elements;
21861
+ }
21862
+
21863
+ /**
21864
+ * Returns the navigation root control for the specified control. The navigation root
21865
+ * is the control that the keyboard navigation gets scoped to for example a menubar or toolbar group.
21866
+ * It will look for parents of the specified target control or the currenty focused control if this option is omitted.
21867
+ *
21868
+ * @private
21869
+ * @param {tinymce.ui.Control} targetControl Optional target control to find root of.
21870
+ * @return {tinymce.ui.Control} Navigation root control.
21871
+ */
21872
+ function getNavigationRoot(targetControl) {
21873
+ var navigationRoot, controls;
21874
+
21875
+ targetControl = targetControl || focusedControl;
21876
+ controls = targetControl.parents().toArray();
21877
+ controls.unshift(targetControl);
21878
+
21879
+ for (var i = 0; i < controls.length; i++) {
21880
+ navigationRoot = controls[i];
21881
+
21882
+ if (navigationRoot.settings.ariaRoot) {
21883
+ break;
21884
+ }
21885
+ }
21886
+
21887
+ return navigationRoot;
21888
+ }
21889
+
21890
+ /**
21891
+ * Focuses the first item in the specified targetControl element or the last aria index if the
21892
+ * navigation root has the ariaRemember option enabled.
21893
+ *
21894
+ * @private
21895
+ * @param {tinymce.ui.Control} targetControl Target control to focus the first item in.
21896
+ */
21897
+ function focusFirst(targetControl) {
21898
+ var navigationRoot = getNavigationRoot(targetControl);
21899
+ var focusElements = getFocusElements(navigationRoot.getEl());
21900
+
21901
+ if (navigationRoot.settings.ariaRemember && "lastAriaIndex" in navigationRoot) {
21902
+ moveFocusToIndex(navigationRoot.lastAriaIndex, focusElements);
21903
+ } else {
21904
+ moveFocusToIndex(0, focusElements);
21905
+ }
21906
+ }
21907
+
21908
+ /**
21909
+ * Moves the focus to the specified index within the elements list.
21910
+ * This will scope the index to the size of the element list if it changed.
21911
+ *
21912
+ * @private
21913
+ * @param {Number} idx Specified index to move to.
21914
+ * @param {Array} elements Array with dom elements to move focus within.
21915
+ * @return {Number} Input index or a changed index if it was out of range.
21916
+ */
21917
+ function moveFocusToIndex(idx, elements) {
21918
+ if (idx < 0) {
21919
+ idx = elements.length - 1;
21920
+ } else if (idx >= elements.length) {
21921
+ idx = 0;
21922
+ }
21923
+
21924
+ if (elements[idx]) {
21925
+ elements[idx].focus();
21926
+ }
21927
+
21928
+ return idx;
21929
+ }
21930
+
21931
+ /**
21932
+ * Moves the focus forwards or backwards.
21933
+ *
21934
+ * @private
21935
+ * @param {Number} dir Direction to move in positive means forward, negative means backwards.
21936
+ * @param {Array} elements Optional array of elements to move within defaults to the current navigation roots elements.
21937
+ */
21938
+ function moveFocus(dir, elements) {
21939
+ var idx = -1, navigationRoot = getNavigationRoot();
21940
+
21941
+ elements = elements || getFocusElements(navigationRoot.getEl());
21942
+
21943
+ for (var i = 0; i < elements.length; i++) {
21944
+ if (elements[i] === focusedElement) {
21945
+ idx = i;
21946
+ }
21947
+ }
21948
+
21949
+ idx += dir;
21950
+ navigationRoot.lastAriaIndex = moveFocusToIndex(idx, elements);
21951
+ }
21952
+
21953
+ /**
21954
+ * Moves the focus to the left this is called by the left key.
21955
+ *
21956
+ * @private
21957
+ */
21958
+ function left() {
21959
+ var parentRole = getParentRole();
21960
+
21961
+ if (parentRole == "tablist") {
21962
+ moveFocus(-1, getFocusElements(focusedElement.parentNode));
21963
+ } else if (focusedControl.parent().submenu) {
21964
+ cancel();
21965
+ } else {
21966
+ moveFocus(-1);
21967
+ }
21968
+ }
21969
+
21970
+ /**
21971
+ * Moves the focus to the right this is called by the right key.
21972
+ *
21973
+ * @private
21974
+ */
21975
+ function right() {
21976
+ var role = getRole(), parentRole = getParentRole();
21977
+
21978
+ if (parentRole == "tablist") {
21979
+ moveFocus(1, getFocusElements(focusedElement.parentNode));
21980
+ } else if (role == "menuitem" && parentRole == "menu" && getAriaProp('haspopup')) {
21981
+ enter();
21982
+ } else {
21983
+ moveFocus(1);
21984
+ }
21985
+ }
21986
+
21987
+ /**
21988
+ * Moves the focus to the up this is called by the up key.
21989
+ *
21990
+ * @private
21991
+ */
21992
+ function up() {
21993
+ moveFocus(-1);
21994
+ }
21995
+
21996
+ /**
21997
+ * Moves the focus to the up this is called by the down key.
21998
+ *
21999
+ * @private
22000
+ */
22001
+ function down() {
22002
+ var role = getRole(), parentRole = getParentRole();
22003
+
22004
+ if (role == "menuitem" && parentRole == "menubar") {
22005
+ enter();
22006
+ } else if (role == "button" && getAriaProp('haspopup')) {
22007
+ enter({key: 'down'});
22008
+ } else {
22009
+ moveFocus(1);
22010
+ }
22011
+ }
22012
+
22013
+ /**
22014
+ * Moves the focus to the next item or previous item depending on shift key.
22015
+ *
22016
+ * @private
22017
+ * @param {DOMEvent} e DOM event object.
22018
+ */
22019
+ function tab(e) {
22020
+ var parentRole = getParentRole();
22021
+
22022
+ if (parentRole == "tablist") {
22023
+ var elm = getFocusElements(focusedControl.getEl('body'))[0];
22024
+
22025
+ if (elm) {
22026
+ elm.focus();
22027
+ }
22028
+ } else {
22029
+ moveFocus(e.shiftKey ? -1 : 1);
22030
+ }
22031
+ }
22032
+
22033
+ /**
22034
+ * Calls the cancel event on the currently focused control. This is normally done using the Esc key.
22035
+ *
22036
+ * @private
22037
+ */
22038
+ function cancel() {
22039
+ focusedControl.fire('cancel');
22040
+ }
22041
+
22042
+ /**
22043
+ * Calls the click event on the currently focused control. This is normally done using the Enter/Space keys.
22044
+ *
22045
+ * @private
22046
+ * @param {Object} aria Optional aria data to pass along with the enter event.
22047
+ */
22048
+ function enter(aria) {
22049
+ aria = aria || {};
22050
+ focusedControl.fire('click', {target: focusedElement, aria: aria});
22051
+ }
22052
+
22053
+ root.on('keydown', function(e) {
22054
+ function handleNonTabEvent(e, handler) {
22055
+ // Ignore non tab keys for text elements
22056
+ if (isTextInputElement(focusedElement)) {
22057
+ return;
22058
+ }
22059
+
22060
+ if (handler(e) !== false) {
22061
+ e.preventDefault();
22062
+ }
22063
+ }
22064
+
22065
+ if (e.isDefaultPrevented()) {
22066
+ return;
22067
+ }
22068
+
22069
+ switch (e.keyCode) {
22070
+ case 37: // DOM_VK_LEFT
22071
+ handleNonTabEvent(e, left);
22072
+ break;
22073
+
22074
+ case 39: // DOM_VK_RIGHT
22075
+ handleNonTabEvent(e, right);
22076
+ break;
22077
+
22078
+ case 38: // DOM_VK_UP
22079
+ handleNonTabEvent(e, up);
22080
+ break;
22081
+
22082
+ case 40: // DOM_VK_DOWN
22083
+ handleNonTabEvent(e, down);
22084
+ break;
22085
+
22086
+ case 27: // DOM_VK_ESCAPE
22087
+ handleNonTabEvent(e, cancel);
22088
+ break;
22089
+
22090
+ case 14: // DOM_VK_ENTER
22091
+ case 13: // DOM_VK_RETURN
22092
+ case 32: // DOM_VK_SPACE
22093
+ handleNonTabEvent(e, enter);
22094
+ break;
22095
+
22096
+ case 9: // DOM_VK_TAB
22097
+ if (tab(e) !== false) {
22098
+ e.preventDefault();
22099
+ }
22100
+ break;
22101
+ }
22102
+ });
22103
+
22104
+ root.on('focusin', function(e) {
22105
+ focusedElement = e.target;
22106
+ focusedControl = e.control;
22107
+ });
22108
+
22109
+ return {
22110
+ focusFirst: focusFirst
22111
+ };
22112
+ };
22113
+ });
22114
+
21672
22115
  // Included from: js/tinymce/classes/ui/Container.js
21673
22116
 
21674
22117
  /**
@@ -21695,9 +22138,10 @@ define("tinymce/ui/Container", [
21695
22138
  "tinymce/ui/Collection",
21696
22139
  "tinymce/ui/Selector",
21697
22140
  "tinymce/ui/Factory",
22141
+ "tinymce/ui/KeyboardNavigation",
21698
22142
  "tinymce/util/Tools",
21699
22143
  "tinymce/ui/DomUtils"
21700
- ], function(Control, Collection, Selector, Factory, Tools, DomUtils) {
22144
+ ], function(Control, Collection, Selector, Factory, KeyboardNavigation, Tools, DomUtils) {
21701
22145
  "use strict";
21702
22146
 
21703
22147
  var selectorCache = {};
@@ -21788,15 +22232,41 @@ define("tinymce/ui/Container", [
21788
22232
  * for the first control in the container and focus that.
21789
22233
  *
21790
22234
  * @method focus
22235
+ * @param {Boolean} keyboard Optional true/false if the focus was a keyboard focus or not.
21791
22236
  * @return {tinymce.ui.Collection} Current instance.
21792
22237
  */
21793
- focus: function() {
21794
- var self = this;
22238
+ focus: function(keyboard) {
22239
+ var self = this, focusCtrl, keyboardNav, items;
21795
22240
 
21796
- if (self.keyNav) {
21797
- self.keyNav.focusFirst();
21798
- } else {
21799
- self._super();
22241
+ if (keyboard) {
22242
+ keyboardNav = self.keyboardNav || self.parents().eq(-1)[0].keyboardNav;
22243
+
22244
+ if (keyboardNav) {
22245
+ keyboardNav.focusFirst(self);
22246
+ return;
22247
+ }
22248
+ }
22249
+
22250
+ items = self.find('*');
22251
+
22252
+ // TODO: Figure out a better way to auto focus alert dialog buttons
22253
+ if (self.statusbar) {
22254
+ items.add(self.statusbar.items());
22255
+ }
22256
+
22257
+ items.each(function(ctrl) {
22258
+ if (ctrl.settings.autofocus) {
22259
+ focusCtrl = null;
22260
+ return false;
22261
+ }
22262
+
22263
+ if (ctrl.canFocus) {
22264
+ focusCtrl = focusCtrl || ctrl;
22265
+ }
22266
+ });
22267
+
22268
+ if (focusCtrl) {
22269
+ focusCtrl.focus();
21800
22270
  }
21801
22271
 
21802
22272
  return self;
@@ -22059,6 +22529,12 @@ define("tinymce/ui/Container", [
22059
22529
  });
22060
22530
  }
22061
22531
 
22532
+ if (!self.parent()) {
22533
+ self.keyboardNav = new KeyboardNavigation({
22534
+ root: self
22535
+ });
22536
+ }
22537
+
22062
22538
  return self;
22063
22539
  },
22064
22540
 
@@ -22486,7 +22962,7 @@ define("tinymce/ui/Panel", [
22486
22962
  }
22487
22963
 
22488
22964
  return (
22489
- '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
22965
+ '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1" role="group">' +
22490
22966
  (self._preBodyHtml || '') +
22491
22967
  innerHtml +
22492
22968
  '</div>'
@@ -23085,6 +23561,16 @@ define("tinymce/ui/FloatPanel", [
23085
23561
  remove: function() {
23086
23562
  removeVisiblePanel(this);
23087
23563
  this._super();
23564
+ },
23565
+
23566
+ postRender: function() {
23567
+ var self = this;
23568
+
23569
+ if (self.settings.bodyRole) {
23570
+ this.getEl('body').setAttribute('role', self.settings.bodyRole);
23571
+ }
23572
+
23573
+ return self._super();
23088
23574
  }
23089
23575
  });
23090
23576
 
@@ -23100,8 +23586,7 @@ define("tinymce/ui/FloatPanel", [
23100
23586
  while (i--) {
23101
23587
  var panel = visiblePanels[i];
23102
23588
 
23103
- if (panel.settings.autohide) {
23104
- panel.fire('cancel', {}, false);
23589
+ if (panel && panel.settings.autohide) {
23105
23590
  panel.hide();
23106
23591
  visiblePanels.splice(i, 1);
23107
23592
  }
@@ -23129,347 +23614,6 @@ define("tinymce/ui/FloatPanel", [
23129
23614
  return FloatPanel;
23130
23615
  });
23131
23616
 
23132
- // Included from: js/tinymce/classes/ui/KeyboardNavigation.js
23133
-
23134
- /**
23135
- * KeyboardNavigation.js
23136
- *
23137
- * Copyright, Moxiecode Systems AB
23138
- * Released under LGPL License.
23139
- *
23140
- * License: http://www.tinymce.com/license
23141
- * Contributing: http://www.tinymce.com/contributing
23142
- */
23143
-
23144
- /**
23145
- * This class handles keyboard navigation of controls and elements.
23146
- *
23147
- * @class tinymce.ui.KeyboardNavigation
23148
- */
23149
- define("tinymce/ui/KeyboardNavigation", [
23150
- "tinymce/ui/DomUtils"
23151
- ], function(DomUtils) {
23152
- "use strict";
23153
-
23154
- /**
23155
- * Create a new KeyboardNavigation instance to handle the focus for a specific element.
23156
- *
23157
- * @constructor
23158
- * @param {Object} settings the settings object to define how keyboard navigation works.
23159
- *
23160
- * @setting {tinymce.ui.Control} root the root control navigation focus movement is scoped to this root.
23161
- * @setting {Array} items an array containing the items to move focus between. Every object in this array must have an
23162
- * id attribute which maps to the actual DOM element and it must be able to have focus i.e. tabIndex=-1.
23163
- * @setting {Function} onCancel the callback for when the user presses escape or otherwise indicates canceling.
23164
- * @setting {Function} onAction (optional) the action handler to call when the user activates an item.
23165
- * @setting {Boolean} enableLeftRight (optional, default) when true, the up/down arrows move through items.
23166
- * @setting {Boolean} enableUpDown (optional) when true, the up/down arrows move through items.
23167
- * Note for both up/down and left/right explicitly set both enableLeftRight and enableUpDown to true.
23168
- */
23169
- return function(settings) {
23170
- var root = settings.root, enableUpDown = settings.enableUpDown !== false;
23171
- var enableLeftRight = settings.enableLeftRight !== false;
23172
- var items = settings.items, focussedId;
23173
-
23174
- /**
23175
- * Initializes the items array if needed. This will collect items/elements
23176
- * from the specified root control.
23177
- */
23178
- function initItems() {
23179
- if (!items) {
23180
- items = [];
23181
-
23182
- if (root.find) {
23183
- // Root is a container then get child elements using the UI API
23184
- root.find('*').each(function(ctrl) {
23185
- if (ctrl.canFocus) {
23186
- items.push(ctrl.getEl());
23187
- }
23188
- });
23189
- } else {
23190
- // Root is a control/widget then get the child elements of that control
23191
- var elements = root.getEl().getElementsByTagName('*');
23192
- for (var i = 0; i < elements.length; i++) {
23193
- if (elements[i].id && elements[i]) {
23194
- items.push(elements[i]);
23195
- }
23196
- }
23197
- }
23198
- }
23199
- }
23200
-
23201
- /**
23202
- * Returns the currently focused element.
23203
- *
23204
- * @private
23205
- * @return {Element} Currently focused element.
23206
- */
23207
- function getFocusElement() {
23208
- return document.getElementById(focussedId);
23209
- }
23210
-
23211
- /**
23212
- * Returns the currently focused elements wai aria role.
23213
- *
23214
- * @private
23215
- * @param {Element} elm Optional element to get role from.
23216
- * @return {String} Role of specified element.
23217
- */
23218
- function getRole(elm) {
23219
- elm = elm || getFocusElement();
23220
-
23221
- return elm && elm.getAttribute('role');
23222
- }
23223
-
23224
- /**
23225
- * Returns the role of the parent element.
23226
- *
23227
- * @private
23228
- * @param {Element} elm Optional element to get parent role from.
23229
- * @return {String} Role of the first parent that has a role.
23230
- */
23231
- function getParentRole(elm) {
23232
- var role, parent = elm || getFocusElement();
23233
-
23234
- while ((parent = parent.parentNode)) {
23235
- if ((role = getRole(parent))) {
23236
- return role;
23237
- }
23238
- }
23239
- }
23240
-
23241
- /**
23242
- * Returns an wai aria property by name.
23243
- *
23244
- * @private
23245
- * @param {String} name Name of the aria property to get for example "disabled".
23246
- * @return {String} Aria property value.
23247
- */
23248
- function getAriaProp(name) {
23249
- var elm = document.getElementById(focussedId);
23250
-
23251
- if (elm) {
23252
- return elm.getAttribute('aria-' + name);
23253
- }
23254
- }
23255
-
23256
- /**
23257
- * Executes the onAction event callback. This is when the user presses enter/space.
23258
- *
23259
- * @private
23260
- */
23261
- function action() {
23262
- var focusElm = getFocusElement();
23263
-
23264
- if (focusElm && (focusElm.nodeName == "TEXTAREA" || focusElm.type == "text")) {
23265
- return;
23266
- }
23267
-
23268
- if (settings.onAction) {
23269
- settings.onAction(focussedId);
23270
- } else {
23271
- DomUtils.fire(getFocusElement(), 'click', {keyboard: true});
23272
- }
23273
-
23274
- return true;
23275
- }
23276
-
23277
- /**
23278
- * Cancels the current navigation. The same as pressing the Esc key.
23279
- *
23280
- * @method cancel
23281
- */
23282
- function cancel() {
23283
- var focusElm;
23284
-
23285
- if (settings.onCancel) {
23286
- if ((focusElm = getFocusElement())) {
23287
- focusElm.blur();
23288
- }
23289
-
23290
- settings.onCancel();
23291
- } else {
23292
- settings.root.fire('cancel');
23293
- }
23294
- }
23295
-
23296
- /**
23297
- * Moves the focus to the next or previous item. It will wrap to start/end if it can't move.
23298
- *
23299
- * @method moveFocus
23300
- * @param {Number} dir Direction for move -1 or 1.
23301
- */
23302
- function moveFocus(dir) {
23303
- var idx = -1, focusElm, i;
23304
- var visibleItems = [];
23305
-
23306
- function isVisible(elm) {
23307
- var rootElm = root ? root.getEl() : document.body;
23308
-
23309
- while (elm && elm != rootElm) {
23310
- if (elm.style.display == 'none') {
23311
- return false;
23312
- }
23313
-
23314
- elm = elm.parentNode;
23315
- }
23316
-
23317
- return true;
23318
- }
23319
-
23320
- initItems();
23321
-
23322
- // TODO: Optimize this, will be slow on lots of items
23323
- i = visibleItems.length;
23324
- for (i = 0; i < items.length; i++) {
23325
- if (isVisible(items[i])) {
23326
- visibleItems.push(items[i]);
23327
- }
23328
- }
23329
-
23330
- i = visibleItems.length;
23331
- while (i--) {
23332
- if (visibleItems[i].id === focussedId) {
23333
- idx = i;
23334
- break;
23335
- }
23336
- }
23337
-
23338
- idx += dir;
23339
- if (idx < 0) {
23340
- idx = visibleItems.length - 1;
23341
- } else if (idx >= visibleItems.length) {
23342
- idx = 0;
23343
- }
23344
-
23345
- focusElm = visibleItems[idx];
23346
- focusElm.focus();
23347
- focussedId = focusElm.id;
23348
-
23349
- if (settings.actOnFocus) {
23350
- action();
23351
- }
23352
- }
23353
-
23354
- /**
23355
- * Moves focus to the first item or the last focused item if root is a toolbar.
23356
- *
23357
- * @method focusFirst
23358
- * @return {[type]} [description]
23359
- */
23360
- function focusFirst() {
23361
- var i, rootRole;
23362
-
23363
- rootRole = getRole(settings.root.getEl());
23364
- initItems();
23365
-
23366
- i = items.length;
23367
- while (i--) {
23368
- if (rootRole == 'toolbar' && items[i].id === focussedId) {
23369
- items[i].focus();
23370
- return;
23371
- }
23372
- }
23373
-
23374
- items[0].focus();
23375
- }
23376
-
23377
- // Handle accessible keys
23378
- root.on('keydown', function(e) {
23379
- var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
23380
- var DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32, DOM_VK_TAB = 9;
23381
- var preventDefault;
23382
-
23383
- switch (e.keyCode) {
23384
- case DOM_VK_LEFT:
23385
- if (enableLeftRight) {
23386
- if (settings.leftAction) {
23387
- settings.leftAction();
23388
- } else {
23389
- moveFocus(-1);
23390
- }
23391
-
23392
- preventDefault = true;
23393
- }
23394
- break;
23395
-
23396
- case DOM_VK_RIGHT:
23397
- if (enableLeftRight) {
23398
- if (getRole() == 'menuitem' && getParentRole() == 'menu') {
23399
- if (getAriaProp('haspopup')) {
23400
- action();
23401
- }
23402
- } else {
23403
- moveFocus(1);
23404
- }
23405
-
23406
- preventDefault = true;
23407
- }
23408
- break;
23409
-
23410
- case DOM_VK_UP:
23411
- if (enableUpDown) {
23412
- moveFocus(-1);
23413
- preventDefault = true;
23414
- }
23415
- break;
23416
-
23417
- case DOM_VK_DOWN:
23418
- if (enableUpDown) {
23419
- if (getRole() == 'menuitem' && getParentRole() == 'menubar') {
23420
- action();
23421
- } else if (getRole() == 'button' && getAriaProp('haspopup')) {
23422
- action();
23423
- } else {
23424
- moveFocus(1);
23425
- }
23426
-
23427
- preventDefault = true;
23428
- }
23429
- break;
23430
-
23431
- case DOM_VK_TAB:
23432
- preventDefault = true;
23433
-
23434
- if (e.shiftKey) {
23435
- moveFocus(-1);
23436
- } else {
23437
- moveFocus(1);
23438
- }
23439
- break;
23440
-
23441
- case DOM_VK_ESCAPE:
23442
- preventDefault = true;
23443
- cancel();
23444
- break;
23445
-
23446
- case DOM_VK_ENTER:
23447
- case DOM_VK_RETURN:
23448
- case DOM_VK_SPACE:
23449
- preventDefault = action();
23450
- break;
23451
- }
23452
-
23453
- if (preventDefault) {
23454
- e.stopPropagation();
23455
- e.preventDefault();
23456
- }
23457
- });
23458
-
23459
- // Init on focus in
23460
- root.on('focusin', function(e) {
23461
- initItems();
23462
- focussedId = e.target.id;
23463
- });
23464
-
23465
- return {
23466
- moveFocus: moveFocus,
23467
- focusFirst: focusFirst,
23468
- cancel: cancel
23469
- };
23470
- };
23471
- });
23472
-
23473
23617
  // Included from: js/tinymce/classes/ui/Window.js
23474
23618
 
23475
23619
  /**
@@ -23493,9 +23637,8 @@ define("tinymce/ui/Window", [
23493
23637
  "tinymce/ui/FloatPanel",
23494
23638
  "tinymce/ui/Panel",
23495
23639
  "tinymce/ui/DomUtils",
23496
- "tinymce/ui/KeyboardNavigation",
23497
23640
  "tinymce/ui/DragHelper"
23498
- ], function(FloatPanel, Panel, DomUtils, KeyboardNavigation, DragHelper) {
23641
+ ], function(FloatPanel, Panel, DomUtils, DragHelper) {
23499
23642
  "use strict";
23500
23643
 
23501
23644
  var Window = FloatPanel.extend({
@@ -23560,6 +23703,11 @@ define("tinymce/ui/Window", [
23560
23703
  }
23561
23704
  });
23562
23705
 
23706
+ self.on('cancel', function() {
23707
+ self.close();
23708
+ });
23709
+
23710
+ self.aria('describedby', self.describedBy || self._id + '-none');
23563
23711
  self.aria('label', settings.title);
23564
23712
  self._fullscreen = false;
23565
23713
  },
@@ -23667,7 +23815,7 @@ define("tinymce/ui/Window", [
23667
23815
  if (settings.title) {
23668
23816
  headerHtml = (
23669
23817
  '<div id="' + id + '-head" class="' + prefix + 'window-head">' +
23670
- '<div class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
23818
+ '<div id="' + id + '-title" class="' + prefix + 'title">' + self.encode(settings.title) + '</div>' +
23671
23819
  '<button type="button" class="' + prefix + 'close" aria-hidden="true">&times;</button>' +
23672
23820
  '<div id="' + id + '-dragh" class="' + prefix + 'dragh"></div>' +
23673
23821
  '</div>'
@@ -23687,12 +23835,14 @@ define("tinymce/ui/Window", [
23687
23835
  }
23688
23836
 
23689
23837
  return (
23690
- '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
23691
- headerHtml +
23692
- '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
23693
- html +
23838
+ '<div id="' + id + '" class="' + self.classes() + '" hideFocus="1">' +
23839
+ '<div class="' + self.classPrefix + 'reset" role="application">' +
23840
+ headerHtml +
23841
+ '<div id="' + id + '-body" class="' + self.classes('body') + '">' +
23842
+ html +
23843
+ '</div>' +
23844
+ footerHtml +
23694
23845
  '</div>' +
23695
- footerHtml +
23696
23846
  '</div>'
23697
23847
  );
23698
23848
  },
@@ -23770,59 +23920,19 @@ define("tinymce/ui/Window", [
23770
23920
  * @method postRender
23771
23921
  */
23772
23922
  postRender: function() {
23773
- var self = this, items = [], focusCtrl, autoFocusFound, startPos;
23923
+ var self = this, startPos;
23774
23924
 
23775
23925
  setTimeout(function() {
23776
23926
  self.addClass('in');
23777
23927
  }, 0);
23778
23928
 
23779
- self.keyboardNavigation = new KeyboardNavigation({
23780
- root: self,
23781
- enableLeftRight: false,
23782
- enableUpDown: false,
23783
- items: items,
23784
- onCancel: function() {
23785
- self.close();
23786
- }
23787
- });
23788
-
23789
- self.find('*').each(function(ctrl) {
23790
- if (ctrl.canFocus) {
23791
- autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
23792
- focusCtrl = focusCtrl || ctrl;
23793
-
23794
- // TODO: Figure out a better way
23795
- if (ctrl.subinput) {
23796
- items.push(ctrl.getEl('inp'));
23797
-
23798
- if (ctrl.getEl('open')) {
23799
- items.push(ctrl.getEl('open'));
23800
- }
23801
- } else {
23802
- items.push(ctrl.getEl());
23803
- }
23804
- }
23805
- });
23806
-
23807
- if (self.statusbar) {
23808
- self.statusbar.find('*').each(function(ctrl) {
23809
- if (ctrl.canFocus) {
23810
- autoFocusFound = autoFocusFound || ctrl.settings.autofocus;
23811
- focusCtrl = focusCtrl || ctrl;
23812
- items.push(ctrl.getEl());
23813
- }
23814
- });
23815
- }
23816
-
23817
23929
  self._super();
23818
23930
 
23819
23931
  if (self.statusbar) {
23820
23932
  self.statusbar.postRender();
23821
23933
  }
23822
23934
 
23823
- if (!autoFocusFound && focusCtrl) {
23824
- focusCtrl.focus();
23825
- }
23935
+ self.focus();
23826
23936
 
23827
23937
  this.dragHelper = new DragHelper(self._id + '-dragh', {
23828
23938
  start: function() {
@@ -24031,6 +24141,7 @@ define("tinymce/ui/MessageBox", [
24031
24141
  align: "center",
24032
24142
  buttons: buttons,
24033
24143
  title: settings.title,
24144
+ role: 'alertdialog',
24034
24145
  items: {
24035
24146
  type: "label",
24036
24147
  multiline: true,
@@ -24038,7 +24149,13 @@ define("tinymce/ui/MessageBox", [
24038
24149
  maxHeight: 200,
24039
24150
  text: settings.text
24040
24151
  },
24041
- onClose: settings.onClose
24152
+ onPostRender: function() {
24153
+ this.aria('describedby', this.items()[0]._id);
24154
+ },
24155
+ onClose: settings.onClose,
24156
+ onCancel: function() {
24157
+ callback(false);
24158
+ }
24042
24159
  }).renderTo(document.body).reflow();
24043
24160
  },
24044
24161
 
@@ -24145,7 +24262,7 @@ define("tinymce/WindowManager", [
24145
24262
  * @option {Number} height Height in pixels.
24146
24263
  * @option {Boolean} resizable Specifies whether the popup window is resizable or not.
24147
24264
  * @option {Boolean} maximizable Specifies whether the popup window has a "maximize" button and can get maximized or not.
24148
- * @option {String/bool} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
24265
+ * @option {String/Boolean} scrollbars Specifies whether the popup window can have scrollbars if required (i.e. content
24149
24266
  * larger than the popup size specified).
24150
24267
  */
24151
24268
  self.open = function(args, params) {
@@ -24238,6 +24355,8 @@ define("tinymce/WindowManager", [
24238
24355
  MessageBox.alert(message, function() {
24239
24356
  if (callback) {
24240
24357
  callback.call(scope || this);
24358
+ } else {
24359
+ editor.focus();
24241
24360
  }
24242
24361
  });
24243
24362
  };
@@ -24850,8 +24969,8 @@ define("tinymce/util/Quirks", [
24850
24969
  return;
24851
24970
  }
24852
24971
 
24853
- // Enable display: none in area and add a specific class that hides all BR elements in PRE to
24854
- // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
24972
+ // Enable display: none in area and add a specific class that hides all BR elements in PRE to
24973
+ // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
24855
24974
  setEditorCommandState('RespectVisibilityInDesign', true);
24856
24975
  editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
24857
24976
  dom.addClass(editor.getBody(), 'mceHideBrInPre');
@@ -25208,7 +25327,7 @@ define("tinymce/util/Quirks", [
25208
25327
  dom.bind(doc, 'mouseup', endSelection);
25209
25328
  dom.bind(doc, 'mousemove', selectionChange);
25210
25329
 
25211
- dom.win.focus();
25330
+ dom.getRoot().focus();
25212
25331
  startRng.select();
25213
25332
  }
25214
25333
  }
@@ -25272,8 +25391,8 @@ define("tinymce/util/Quirks", [
25272
25391
  editor.contentStyles.push('body {min-height: 150px}');
25273
25392
  editor.on('click', function(e) {
25274
25393
  if (e.target.nodeName == 'HTML') {
25275
- editor.execCommand('SelectAll');
25276
- editor.selection.collapse(true);
25394
+ editor.getBody().focus();
25395
+ editor.selection.normalize();
25277
25396
  editor.nodeChanged();
25278
25397
  }
25279
25398
  });
@@ -25302,6 +25421,21 @@ define("tinymce/util/Quirks", [
25302
25421
  setEditorCommandState("AutoUrlDetect", false);
25303
25422
  }
25304
25423
 
25424
+ /**
25425
+ * IE 11 has a fantastic bug where it will produce two trailing BR elements to iframe bodies when
25426
+ * the iframe is hidden by display: none. This workaround solves this by switching
25427
+ * on designMode on the whole document.
25428
+ *
25429
+ * Example this: <body>text</body> becomes <body>text<br><br></body>
25430
+ */
25431
+ function doubleTrailingBrElements() {
25432
+ if (!editor.inline) {
25433
+ editor.on('init', function() {
25434
+ editor.getDoc().designMode = 'on';
25435
+ });
25436
+ }
25437
+ }
25438
+
25305
25439
  // All browsers
25306
25440
  disableBackspaceIntoATable();
25307
25441
  removeBlockQuoteOnBackSpace();
@@ -25339,6 +25473,7 @@ define("tinymce/util/Quirks", [
25339
25473
 
25340
25474
  if (Env.ie >= 11) {
25341
25475
  bodyHeight();
25476
+ doubleTrailingBrElements();
25342
25477
  }
25343
25478
 
25344
25479
  if (Env.ie) {
@@ -26984,7 +27119,7 @@ define("tinymce/Editor", [
26984
27119
  *
26985
27120
  * setup: function(ed) {
26986
27121
  * ed.addMenuItem('example', {
26987
- * title: 'My menu item',
27122
+ * text: 'My menu item',
26988
27123
  * context: 'tools',
26989
27124
  * onclick: function() {
26990
27125
  * ed.insertContent('Hello world!!');
@@ -27359,7 +27494,10 @@ define("tinymce/Editor", [
27359
27494
  html = args.content;
27360
27495
 
27361
27496
  if (!/TEXTAREA|INPUT/i.test(elm.nodeName)) {
27362
- elm.innerHTML = html;
27497
+ // Update DIV element when not in inline mode
27498
+ if (!self.inline) {
27499
+ elm.innerHTML = html;
27500
+ }
27363
27501
 
27364
27502
  // Update hidden form element
27365
27503
  if ((form = DOM.getParent(self.id, 'form'))) {
@@ -27702,7 +27840,7 @@ define("tinymce/Editor", [
27702
27840
  case 'A':
27703
27841
  if (!dom.getAttrib(elm, 'href', false)) {
27704
27842
  value = dom.getAttrib(elm, 'name') || elm.id;
27705
- cls = 'mce-item-anchor';
27843
+ cls = settings.visual_anchor_class || 'mce-item-anchor';
27706
27844
 
27707
27845
  if (value) {
27708
27846
  if (self.hasVisual) {
@@ -27921,7 +28059,7 @@ define("tinymce/util/I18n", [], function() {
27921
28059
  * Property gets set to true if a RTL language pack was loaded.
27922
28060
  *
27923
28061
  * @property rtl
27924
- * @type {Boolean}
28062
+ * @type Boolean
27925
28063
  */
27926
28064
  rtl: false,
27927
28065
 
@@ -28069,14 +28207,24 @@ define("tinymce/FocusManager", [
28069
28207
  editor.on('init', function() {
28070
28208
  // On IE take selection snapshot onbeforedeactivate
28071
28209
  if ("onbeforedeactivate" in document && Env.ie < 11) {
28210
+ // Gets fired when the editor is about to be blurred but also when the selection
28211
+ // is moved into a table cell so we need to add the range as a pending range then
28212
+ // use that pending range on the blur event of the editor body
28072
28213
  editor.dom.bind(editor.getBody(), 'beforedeactivate', function() {
28073
28214
  try {
28074
- editor.lastRng = editor.selection.getRng();
28215
+ editor.pendingRng = editor.selection.getRng();
28075
28216
  } catch (ex) {
28076
28217
  // IE throws "Unexcpected call to method or property access" some times so lets ignore it
28077
28218
  }
28219
+ });
28078
28220
 
28079
- editor.selection.lastFocusBookmark = createBookmark(editor.lastRng);
28221
+ // Set the pending range as the current last range if the blur event occurs
28222
+ editor.dom.bind(editor.getBody(), 'blur', function() {
28223
+ if (editor.pendingRng) {
28224
+ editor.lastRng = editor.pendingRng;
28225
+ editor.selection.lastFocusBookmark = createBookmark(editor.lastRng);
28226
+ editor.pendingRng = null;
28227
+ }
28080
28228
  });
28081
28229
  } else if (editor.inline || Env.ie > 10) {
28082
28230
  // On other browsers take snapshot on nodechange in inline mode since they have Ghost selections for iframes
@@ -28142,7 +28290,7 @@ define("tinymce/FocusManager", [
28142
28290
  editorManager.activeEditor = editor;
28143
28291
  editorManager.focusedEditor = editor;
28144
28292
  editor.fire('focus', {blurredEditor: focusedEditor});
28145
- editor.focus(false);
28293
+ editor.focus(true);
28146
28294
  }
28147
28295
 
28148
28296
  editor.lastRng = null;
@@ -28172,7 +28320,6 @@ define("tinymce/FocusManager", [
28172
28320
  var activeEditor = editorManager.activeEditor;
28173
28321
 
28174
28322
  if (activeEditor && e.target.ownerDocument == document) {
28175
-
28176
28323
  // Check to make sure we have a valid selection
28177
28324
  if (activeEditor.selection) {
28178
28325
  activeEditor.selection.lastFocusBookmark = createBookmark(activeEditor.lastRng);
@@ -28254,7 +28401,7 @@ define("tinymce/EditorManager", [
28254
28401
  * @property minorVersion
28255
28402
  * @type String
28256
28403
  */
28257
- minorVersion : '0.16',
28404
+ minorVersion : '0.18',
28258
28405
 
28259
28406
  /**
28260
28407
  * Release date of TinyMCE build.
@@ -28262,7 +28409,7 @@ define("tinymce/EditorManager", [
28262
28409
  * @property releaseDate
28263
28410
  * @type String
28264
28411
  */
28265
- releaseDate: '2014-01-31',
28412
+ releaseDate: '2014-02-27',
28266
28413
 
28267
28414
  /**
28268
28415
  * Collection of editor instances.
@@ -29846,10 +29993,9 @@ define("tinymce/ui/Widget", [
29846
29993
  self.on('mouseleave mousedown click', function() {
29847
29994
  self.tooltip().hide();
29848
29995
  });
29849
-
29850
29996
  }
29851
29997
 
29852
- self.aria('label', settings.tooltip);
29998
+ self.aria('label', settings.ariaLabel || settings.tooltip);
29853
29999
  },
29854
30000
 
29855
30001
  /**
@@ -29859,11 +30005,9 @@ define("tinymce/ui/Widget", [
29859
30005
  * @return {tinymce.ui.Tooltip} Tooltip instance.
29860
30006
  */
29861
30007
  tooltip: function() {
29862
- var self = this;
29863
-
29864
30008
  if (!tooltip) {
29865
30009
  tooltip = new Tooltip({type: 'tooltip'});
29866
- tooltip.renderTo(self.getContainerElm());
30010
+ tooltip.renderTo();
29867
30011
  }
29868
30012
 
29869
30013
  return tooltip;
@@ -29923,9 +30067,7 @@ define("tinymce/ui/Widget", [
29923
30067
  }
29924
30068
 
29925
30069
  if (settings.autofocus) {
29926
- setTimeout(function() {
29927
- self.focus();
29928
- }, 0);
30070
+ self.focus();
29929
30071
  }
29930
30072
  },
29931
30073
 
@@ -30081,7 +30223,7 @@ define("tinymce/ui/Button", [
30081
30223
  icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + icon : '';
30082
30224
 
30083
30225
  return (
30084
- '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
30226
+ '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
30085
30227
  '<button role="presentation" type="button" tabindex="-1">' +
30086
30228
  (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
30087
30229
  (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') +
@@ -30130,7 +30272,7 @@ define("tinymce/ui/ButtonGroup", [
30130
30272
  return Container.extend({
30131
30273
  Defaults: {
30132
30274
  defaultType: 'button',
30133
- role: 'toolbar'
30275
+ role: 'group'
30134
30276
  },
30135
30277
 
30136
30278
  /**
@@ -30326,12 +30468,19 @@ define("tinymce/ui/PanelButton", [
30326
30468
  };
30327
30469
  }
30328
30470
 
30471
+ panelSettings.role = panelSettings.role || 'dialog';
30329
30472
  panelSettings.popover = true;
30330
30473
  panelSettings.autohide = true;
30474
+ panelSettings.ariaRoot = true;
30331
30475
 
30332
30476
  self.panel = new FloatPanel(panelSettings).on('hide', function() {
30333
30477
  self.active(false);
30478
+ }).on('cancel', function(e) {
30479
+ e.stopPropagation();
30480
+ self.focus();
30481
+ self.hidePanel();
30334
30482
  }).parent(self).renderTo(self.getContainerElm());
30483
+
30335
30484
  self.panel.fire('show');
30336
30485
  self.panel.reflow();
30337
30486
  } else {
@@ -30362,12 +30511,15 @@ define("tinymce/ui/PanelButton", [
30362
30511
  postRender: function() {
30363
30512
  var self = this;
30364
30513
 
30514
+ self.aria('haspopup', true);
30515
+
30365
30516
  self.on('click', function(e) {
30366
30517
  if (e.control === self) {
30367
30518
  if (self.panel && self.panel.visible()) {
30368
30519
  self.hidePanel();
30369
30520
  } else {
30370
30521
  self.showPanel();
30522
+ self.panel.focus(!!e.aria);
30371
30523
  }
30372
30524
  }
30373
30525
  });
@@ -30447,7 +30599,7 @@ define("tinymce/ui/ColorButton", [
30447
30599
  var image = self.settings.image ? ' style="background-image: url(\'' + self.settings.image + '\')"' : '';
30448
30600
 
30449
30601
  return (
30450
- '<div id="' + id + '" class="' + self.classes() + '">' +
30602
+ '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1" aria-haspopup="true">' +
30451
30603
  '<button role="presentation" hidefocus type="button" tabindex="-1">' +
30452
30604
  (icon ? '<i class="' + icon + '"' + image + '></i>' : '') +
30453
30605
  '<span id="' + id + '-preview" class="' + prefix + 'preview"></span>' +
@@ -30469,6 +30621,10 @@ define("tinymce/ui/ColorButton", [
30469
30621
  var self = this, onClickHandler = self.settings.onclick;
30470
30622
 
30471
30623
  self.on('click', function(e) {
30624
+ if (e.aria && e.aria.key == 'down') {
30625
+ return;
30626
+ }
30627
+
30472
30628
  if (e.control == self && !DOM.getParent(e.target, '.' + self.classPrefix + 'open')) {
30473
30629
  e.stopImmediatePropagation();
30474
30630
  onClickHandler.call(self, e);
@@ -30524,6 +30680,7 @@ define("tinymce/ui/ComboBox", [
30524
30680
  self._super(settings);
30525
30681
  self.addClass('combobox');
30526
30682
  self.subinput = true;
30683
+ self.ariaTarget = 'inp'; // TODO: Figure out a better way
30527
30684
 
30528
30685
  settings = self.settings;
30529
30686
  settings.menu = settings.menu || settings.values;
@@ -30533,16 +30690,16 @@ define("tinymce/ui/ComboBox", [
30533
30690
  }
30534
30691
 
30535
30692
  self.on('click', function(e) {
30536
- var elm = e.target;
30693
+ var elm = e.target, root = self.getEl();
30537
30694
 
30538
- while (elm) {
30695
+ while (elm && elm != root) {
30539
30696
  if (elm.id && elm.id.indexOf('-open') != -1) {
30540
30697
  self.fire('action');
30541
30698
 
30542
30699
  if (settings.menu) {
30543
30700
  self.showMenu();
30544
30701
 
30545
- if (e.keyboard) {
30702
+ if (e.aria) {
30546
30703
  self.menu.items()[0].focus();
30547
30704
  }
30548
30705
  }
@@ -30795,7 +30952,7 @@ define("tinymce/ui/ComboBox", [
30795
30952
 
30796
30953
  if (icon || text) {
30797
30954
  openBtnHtml = (
30798
- '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1">' +
30955
+ '<div id="' + id + '-open" class="' + prefix + 'btn ' + prefix + 'open" tabIndex="-1" role="button">' +
30799
30956
  '<button id="' + id + '-action" type="button" hidefocus tabindex="-1">' +
30800
30957
  (icon != 'caret' ? '<i class="' + icon + '"></i>' : '<i class="' + prefix + 'caret"></i>') +
30801
30958
  (text ? (icon ? ' ' : '') + text : '') +
@@ -30837,9 +30994,8 @@ define("tinymce/ui/ComboBox", [
30837
30994
  * @extends tinymce.ui.Widget
30838
30995
  */
30839
30996
  define("tinymce/ui/Path", [
30840
- "tinymce/ui/Widget",
30841
- "tinymce/ui/KeyboardNavigation"
30842
- ], function(Widget, KeyboardNavigation) {
30997
+ "tinymce/ui/Widget"
30998
+ ], function(Widget) {
30843
30999
  "use strict";
30844
31000
 
30845
31001
  return Widget.extend({
@@ -30879,12 +31035,7 @@ define("tinymce/ui/Path", [
30879
31035
  focus: function() {
30880
31036
  var self = this;
30881
31037
 
30882
- self.keyNav = new KeyboardNavigation({
30883
- root: self,
30884
- enableLeftRight: true
30885
- });
30886
-
30887
- self.keyNav.focusFirst();
31038
+ self.getEl().firstChild.focus();
30888
31039
 
30889
31040
  return self;
30890
31041
  },
@@ -30953,7 +31104,7 @@ define("tinymce/ui/Path", [
30953
31104
  html += (
30954
31105
  (i > 0 ? '<div class="'+ prefix + 'divider" aria-hidden="true"> ' + self.settings.delimiter + ' </div>' : '') +
30955
31106
  '<div role="button" class="' + prefix + 'path-item' + (i == l - 1 ? ' ' + prefix + 'last' : '') + '" data-index="' +
30956
- i + '" tabindex="-1" id="' + self._id + '-' + i +'">' + parts[i].name + '</div>'
31107
+ i + '" tabindex="-1" id="' + self._id + '-' + i +'" aria-level="' + i + '">' + parts[i].name + '</div>'
30957
31108
  );
30958
31109
  }
30959
31110
 
@@ -31181,11 +31332,12 @@ define("tinymce/ui/Form", [
31181
31332
  autoResize: "overflow",
31182
31333
  defaults: {flex: 1},
31183
31334
  items: [
31184
- {type: 'label', text: label, flex: 0, forId: ctrl._id, disabled: ctrl.disabled()}
31335
+ {type: 'label', id: ctrl._id + '-l', text: label, flex: 0, forId: ctrl._id, disabled: ctrl.disabled()}
31185
31336
  ]
31186
31337
  });
31187
31338
 
31188
31339
  formItem.type = 'formitem';
31340
+ ctrl.aria('labelledby', ctrl._id + '-l');
31189
31341
 
31190
31342
  if (typeof(ctrl.settings.flex) == "undefined") {
31191
31343
  ctrl.settings.flex = 1;
@@ -32852,9 +33004,8 @@ define("tinymce/ui/Label", [
32852
33004
  * @extends tinymce.ui.Container
32853
33005
  */
32854
33006
  define("tinymce/ui/Toolbar", [
32855
- "tinymce/ui/Container",
32856
- "tinymce/ui/KeyboardNavigation"
32857
- ], function(Container, KeyboardNavigation) {
33007
+ "tinymce/ui/Container"
33008
+ ], function(Container) {
32858
33009
  "use strict";
32859
33010
 
32860
33011
  return Container.extend({
@@ -32886,11 +33037,6 @@ define("tinymce/ui/Toolbar", [
32886
33037
 
32887
33038
  self.items().addClass('toolbar-item');
32888
33039
 
32889
- self.keyNav = new KeyboardNavigation({
32890
- root: self,
32891
- enableLeftRight: true
32892
- });
32893
-
32894
33040
  return self._super();
32895
33041
  }
32896
33042
  });
@@ -32924,6 +33070,7 @@ define("tinymce/ui/MenuBar", [
32924
33070
  Defaults: {
32925
33071
  role: 'menubar',
32926
33072
  containerCls: 'menubar',
33073
+ ariaRoot: true,
32927
33074
  defaults: {
32928
33075
  type: 'menubutton'
32929
33076
  }
@@ -33018,22 +33165,29 @@ define("tinymce/ui/MenuButton", [
33018
33165
  menu.type = menu.type || 'menu';
33019
33166
  }
33020
33167
 
33021
- self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
33168
+ self.menu = Factory.create(menu).parent(self).renderTo();
33022
33169
  self.fire('createmenu');
33023
33170
  self.menu.reflow();
33024
33171
  self.menu.on('cancel', function(e) {
33025
- if (e.control === self.menu) {
33172
+ if (e.control.parent() === self.menu) {
33173
+ e.stopPropagation();
33026
33174
  self.focus();
33175
+ self.hideMenu();
33027
33176
  }
33028
33177
  });
33029
33178
 
33179
+ // Move focus to button when a menu item is selected/clicked
33180
+ self.menu.on('select', function() {
33181
+ self.focus();
33182
+ });
33183
+
33030
33184
  self.menu.on('show hide', function(e) {
33031
33185
  if (e.control == self.menu) {
33032
33186
  self.activeMenu(e.type == 'show');
33033
33187
  }
33034
- }).fire('show');
33035
33188
 
33036
- self.aria('expanded', true);
33189
+ self.aria('expanded', e.type == 'show');
33190
+ }).fire('show');
33037
33191
  }
33038
33192
 
33039
33193
  self.menu.show();
@@ -33057,7 +33211,6 @@ define("tinymce/ui/MenuButton", [
33057
33211
  });
33058
33212
 
33059
33213
  self.menu.hide();
33060
- self.aria('expanded', false);
33061
33214
  }
33062
33215
  },
33063
33216
 
@@ -33083,7 +33236,7 @@ define("tinymce/ui/MenuButton", [
33083
33236
  self.aria('role', self.parent() instanceof MenuBar ? 'menuitem' : 'button');
33084
33237
 
33085
33238
  return (
33086
- '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
33239
+ '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1" aria-labelledby="' + id + '">' +
33087
33240
  '<button id="' + id + '-open" role="presentation" type="button" tabindex="-1">' +
33088
33241
  (icon ? '<i class="' + icon + '"></i>' : '') +
33089
33242
  '<span>' + (self._text ? (icon ? '\u00a0' : '') + self.encode(self._text) : '') + '</span>' +
@@ -33105,7 +33258,7 @@ define("tinymce/ui/MenuButton", [
33105
33258
  if (e.control === self && isChildOf(e.target, self.getEl())) {
33106
33259
  self.showMenu();
33107
33260
 
33108
- if (e.keyboard) {
33261
+ if (e.aria) {
33109
33262
  self.menu.items()[0].focus();
33110
33263
  }
33111
33264
  }
@@ -33215,9 +33368,16 @@ define("tinymce/ui/ListBox", [
33215
33368
  if (selected) {
33216
33369
  selectedText = selectedText || values[i].text;
33217
33370
  self._value = values[i].value;
33371
+ break;
33218
33372
  }
33219
33373
  }
33220
33374
 
33375
+ // Default with first item
33376
+ if (!selected && values.length > 0) {
33377
+ selectedText = values[0].text;
33378
+ self._value = values[0].value;
33379
+ }
33380
+
33221
33381
  settings.menu = values;
33222
33382
  }
33223
33383
 
@@ -33356,13 +33516,11 @@ define("tinymce/ui/MenuItem", [
33356
33516
  if (self._text === '-' || self._text === '|') {
33357
33517
  self.addClass('menu-item-sep');
33358
33518
  self.aria('role', 'separator');
33359
- self.canFocus = false;
33360
33519
  self._text = '-';
33361
33520
  }
33362
33521
 
33363
33522
  if (settings.selectable) {
33364
33523
  self.aria('role', 'menuitemcheckbox');
33365
- self.aria('checked', true);
33366
33524
  self.addClass('menu-item-checkbox');
33367
33525
  settings.icon = 'selected';
33368
33526
  }
@@ -33375,24 +33533,6 @@ define("tinymce/ui/MenuItem", [
33375
33533
  e.preventDefault();
33376
33534
  });
33377
33535
 
33378
- self.on('mouseenter click', function(e) {
33379
- if (e.control === self) {
33380
- if (!settings.menu && e.type === 'click') {
33381
- self.parent().hideAll();
33382
- self.fire('cancel');
33383
- self.fire('select');
33384
- } else {
33385
- self.showMenu();
33386
-
33387
- if (e.keyboard) {
33388
- setTimeout(function() {
33389
- self.menu.items()[0].focus();
33390
- }, 0);
33391
- }
33392
- }
33393
- }
33394
- });
33395
-
33396
33536
  if (settings.menu) {
33397
33537
  self.aria('haspopup', true);
33398
33538
  }
@@ -33442,11 +33582,13 @@ define("tinymce/ui/MenuItem", [
33442
33582
  menu.itemDefaults = parent.settings.itemDefaults;
33443
33583
  }
33444
33584
 
33445
- menu = self.menu = Factory.create(menu).parent(self).renderTo(self.getContainerElm());
33585
+ menu = self.menu = Factory.create(menu).parent(self).renderTo();
33446
33586
  menu.reflow();
33447
33587
  menu.fire('show');
33448
- menu.on('cancel', function() {
33588
+ menu.on('cancel', function(e) {
33589
+ e.stopPropagation();
33449
33590
  self.focus();
33591
+ menu.hide();
33450
33592
  });
33451
33593
 
33452
33594
  menu.on('hide', function(e) {
@@ -33454,6 +33596,8 @@ define("tinymce/ui/MenuItem", [
33454
33596
  self.removeClass('selected');
33455
33597
  }
33456
33598
  });
33599
+
33600
+ menu.submenu = true;
33457
33601
  } else {
33458
33602
  menu.show();
33459
33603
  }
@@ -33535,8 +33679,7 @@ define("tinymce/ui/MenuItem", [
33535
33679
  '<div id="' + id + '" class="' + self.classes() + '" tabindex="-1">' +
33536
33680
  (text !== '-' ? '<i class="' + icon + '"' + image + '></i>&nbsp;' : '') +
33537
33681
  (text !== '-' ? '<span id="' + id + '-text" class="' + prefix + 'text">' + text + '</span>' : '') +
33538
- (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' +
33539
- shortcut + '</div>' : '') +
33682
+ (shortcut ? '<div id="' + id + '-shortcut" class="' + prefix + 'menu-shortcut">' + shortcut + '</div>' : '') +
33540
33683
  (settings.menu ? '<div class="' + prefix + 'caret"></div>' : '') +
33541
33684
  '</div>'
33542
33685
  );
@@ -33562,7 +33705,32 @@ define("tinymce/ui/MenuItem", [
33562
33705
  }
33563
33706
  }
33564
33707
 
33565
- return self._super();
33708
+ self.on('mouseenter click', function(e) {
33709
+ if (e.control === self) {
33710
+ if (!settings.menu && e.type === 'click') {
33711
+ self.fire('select');
33712
+ self.parent().hideAll();
33713
+ } else {
33714
+ self.showMenu();
33715
+
33716
+ if (e.aria) {
33717
+ self.menu.focus(true);
33718
+ }
33719
+ }
33720
+ }
33721
+ });
33722
+
33723
+ self._super();
33724
+
33725
+ return self;
33726
+ },
33727
+
33728
+ active: function(state) {
33729
+ if (typeof(state) != "undefined") {
33730
+ this.aria('checked', state);
33731
+ }
33732
+
33733
+ return this._super(state);
33566
33734
  },
33567
33735
 
33568
33736
  /**
@@ -33601,10 +33769,9 @@ define("tinymce/ui/MenuItem", [
33601
33769
  */
33602
33770
  define("tinymce/ui/Menu", [
33603
33771
  "tinymce/ui/FloatPanel",
33604
- "tinymce/ui/KeyboardNavigation",
33605
33772
  "tinymce/ui/MenuItem",
33606
33773
  "tinymce/util/Tools"
33607
- ], function(FloatPanel, KeyboardNavigation, MenuItem, Tools) {
33774
+ ], function(FloatPanel, MenuItem, Tools) {
33608
33775
  "use strict";
33609
33776
 
33610
33777
  var Menu = FloatPanel.extend({
@@ -33612,7 +33779,9 @@ define("tinymce/ui/Menu", [
33612
33779
  defaultType: 'menuitem',
33613
33780
  border: 1,
33614
33781
  layout: 'stack',
33615
- role: 'menu'
33782
+ role: 'application',
33783
+ bodyRole: 'menu',
33784
+ ariaRoot: true
33616
33785
  },
33617
33786
 
33618
33787
  /**
@@ -33637,23 +33806,6 @@ define("tinymce/ui/Menu", [
33637
33806
 
33638
33807
  self._super(settings);
33639
33808
  self.addClass('menu');
33640
-
33641
- self.keyNav = new KeyboardNavigation({
33642
- root: self,
33643
- enableUpDown: true,
33644
- enableLeftRight: true,
33645
-
33646
- leftAction: function() {
33647
- if (self.parent() instanceof MenuItem) {
33648
- self.keyNav.cancel();
33649
- }
33650
- },
33651
-
33652
- onCancel: function() {
33653
- self.fire('cancel', {}, false);
33654
- self.hide();
33655
- }
33656
- });
33657
33809
  },
33658
33810
 
33659
33811
  /**
@@ -33681,7 +33833,6 @@ define("tinymce/ui/Menu", [
33681
33833
  var self = this;
33682
33834
 
33683
33835
  self.hideAll();
33684
- self.fire('cancel');
33685
33836
  self.fire('select');
33686
33837
  },
33687
33838
 
@@ -33697,7 +33848,25 @@ define("tinymce/ui/Menu", [
33697
33848
 
33698
33849
  return self._super();
33699
33850
  },
33851
+ /*
33852
+ getContainerElm: function() {
33853
+ var doc = document, id = this.classPrefix + 'menucontainer';
33700
33854
 
33855
+ var elm = doc.getElementById(id);
33856
+ if (!elm) {
33857
+ elm = doc.createElement('div');
33858
+ elm.id = id;
33859
+ elm.setAttribute('role', 'application');
33860
+ elm.className = this.classPrefix + '-reset';
33861
+ elm.style.position = 'absolute';
33862
+ elm.style.top = elm.style.left = '0';
33863
+ elm.style.overflow = 'visible';
33864
+ doc.body.appendChild(elm);
33865
+ }
33866
+
33867
+ return elm;
33868
+ },
33869
+ */
33701
33870
  /**
33702
33871
  * Invoked before the menu is rendered.
33703
33872
  *
@@ -33911,7 +34080,7 @@ define("tinymce/ui/SplitButton", [
33911
34080
  return MenuButton.extend({
33912
34081
  Defaults: {
33913
34082
  classes: "widget btn splitbtn",
33914
- role: "splitbutton"
34083
+ role: "button"
33915
34084
  },
33916
34085
 
33917
34086
  /**
@@ -33961,7 +34130,7 @@ define("tinymce/ui/SplitButton", [
33961
34130
  var icon = self.settings.icon ? prefix + 'ico ' + prefix + 'i-' + self.settings.icon : '';
33962
34131
 
33963
34132
  return (
33964
- '<div id="' + id + '" class="' + self.classes() + '">' +
34133
+ '<div id="' + id + '" class="' + self.classes() + '" role="button" tabindex="-1">' +
33965
34134
  '<button type="button" hidefocus tabindex="-1">' +
33966
34135
  (icon ? '<i class="' + icon + '"></i>' : '') +
33967
34136
  (self._text ? (icon ? ' ' : '') + self._text : '') +
@@ -33989,7 +34158,7 @@ define("tinymce/ui/SplitButton", [
33989
34158
  if (e.control == this) {
33990
34159
  // Find clicks that is on the main button
33991
34160
  while (node) {
33992
- if (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1) {
34161
+ if ((e.aria && e.aria.key != 'down') || (node.nodeName == 'BUTTON' && node.className.indexOf('open') == -1)) {
33993
34162
  e.stopImmediatePropagation();
33994
34163
  onClickHandler.call(this, e);
33995
34164
  return;
@@ -34084,13 +34253,19 @@ define("tinymce/ui/TabPanel", [
34084
34253
  * @param {Number} idx Index of the tab to activate.
34085
34254
  */
34086
34255
  activateTab: function(idx) {
34256
+ var activeTabElm;
34257
+
34087
34258
  if (this.activeTabId) {
34088
- DomUtils.removeClass(this.getEl(this.activeTabId), this.classPrefix + 'active');
34259
+ activeTabElm = this.getEl(this.activeTabId);
34260
+ DomUtils.removeClass(activeTabElm, this.classPrefix + 'active');
34261
+ activeTabElm.setAttribute('aria-selected', "false");
34089
34262
  }
34090
34263
 
34091
34264
  this.activeTabId = 't' + idx;
34092
34265
 
34093
- DomUtils.addClass(this.getEl('t' + idx), this.classPrefix + 'active');
34266
+ activeTabElm = this.getEl('t' + idx);
34267
+ activeTabElm.setAttribute('aria-selected', "true");
34268
+ DomUtils.addClass(activeTabElm, this.classPrefix + 'active');
34094
34269
 
34095
34270
  if (idx != this.lastIdx) {
34096
34271
  this.items()[this.lastIdx].hide();
@@ -34114,8 +34289,14 @@ define("tinymce/ui/TabPanel", [
34114
34289
  layout.preRender(self);
34115
34290
 
34116
34291
  self.items().each(function(ctrl, i) {
34292
+ var id = self._id + '-t' + i;
34293
+
34294
+ ctrl.aria('role', 'tabpanel');
34295
+ ctrl.aria('labelledby', id);
34296
+
34117
34297
  tabsHtml += (
34118
- '<div id="' + self._id + '-t' + i + '" class="' + prefix + 'tab" unselectable="on">' +
34298
+ '<div id="' + id + '" class="' + prefix + 'tab" '+
34299
+ 'unselectable="on" role="tab" aria-controls="' + ctrl._id + '" aria-selected="false" tabIndex="-1">' +
34119
34300
  self.encode(ctrl.settings.title) +
34120
34301
  '</div>'
34121
34302
  );
@@ -34123,7 +34304,7 @@ define("tinymce/ui/TabPanel", [
34123
34304
 
34124
34305
  return (
34125
34306
  '<div id="' + self._id + '" class="' + self.classes() + '" hideFocus="1" tabIndex="-1">' +
34126
- '<div id="' + self._id + '-head" class="' + prefix + 'tabs">' +
34307
+ '<div id="' + self._id + '-head" class="' + prefix + 'tabs" role="tablist">' +
34127
34308
  tabsHtml +
34128
34309
  '</div>' +
34129
34310
  '<div id="' + self._id + '-body" class="' + self.classes('body') + '">' +
@@ -34501,5 +34682,5 @@ define("tinymce/ui/Throbber", [
34501
34682
  };
34502
34683
  });
34503
34684
 
34504
- expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/Selection","tinymce/dom/RangeUtils","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/KeyboardNavigation","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
34685
+ expose(["tinymce/dom/EventUtils","tinymce/dom/Sizzle","tinymce/dom/DomQuery","tinymce/html/Styles","tinymce/dom/TreeWalker","tinymce/util/Tools","tinymce/dom/Range","tinymce/html/Entities","tinymce/Env","tinymce/dom/StyleSheetLoader","tinymce/dom/DOMUtils","tinymce/dom/ScriptLoader","tinymce/AddOnManager","tinymce/html/Node","tinymce/html/Schema","tinymce/html/SaxParser","tinymce/html/DomParser","tinymce/html/Writer","tinymce/html/Serializer","tinymce/dom/Serializer","tinymce/dom/TridentSelection","tinymce/util/VK","tinymce/dom/ControlSelection","tinymce/dom/RangeUtils","tinymce/dom/Selection","tinymce/Formatter","tinymce/UndoManager","tinymce/EnterKey","tinymce/ForceBlocks","tinymce/EditorCommands","tinymce/util/URI","tinymce/util/Class","tinymce/ui/Selector","tinymce/ui/Collection","tinymce/ui/DomUtils","tinymce/ui/Control","tinymce/ui/Factory","tinymce/ui/KeyboardNavigation","tinymce/ui/Container","tinymce/ui/DragHelper","tinymce/ui/Scrollable","tinymce/ui/Panel","tinymce/ui/Movable","tinymce/ui/Resizable","tinymce/ui/FloatPanel","tinymce/ui/Window","tinymce/ui/MessageBox","tinymce/WindowManager","tinymce/util/Quirks","tinymce/util/Observable","tinymce/Shortcuts","tinymce/Editor","tinymce/util/I18n","tinymce/FocusManager","tinymce/EditorManager","tinymce/LegacyInput","tinymce/util/XHR","tinymce/util/JSON","tinymce/util/JSONRequest","tinymce/util/JSONP","tinymce/util/LocalStorage","tinymce/Compat","tinymce/ui/Layout","tinymce/ui/AbsoluteLayout","tinymce/ui/Tooltip","tinymce/ui/Widget","tinymce/ui/Button","tinymce/ui/ButtonGroup","tinymce/ui/Checkbox","tinymce/ui/PanelButton","tinymce/ui/ColorButton","tinymce/ui/ComboBox","tinymce/ui/Path","tinymce/ui/ElementPath","tinymce/ui/FormItem","tinymce/ui/Form","tinymce/ui/FieldSet","tinymce/ui/FilePicker","tinymce/ui/FitLayout","tinymce/ui/FlexLayout","tinymce/ui/FlowLayout","tinymce/ui/FormatControls","tinymce/ui/GridLayout","tinymce/ui/Iframe","tinymce/ui/Label","tinymce/ui/Toolbar","tinymce/ui/MenuBar","tinymce/ui/MenuButton","tinymce/ui/ListBox","tinymce/ui/MenuItem","tinymce/ui/Menu","tinymce/ui/Radio","tinymce/ui/ResizeHandle","tinymce/ui/Spacer","tinymce/ui/SplitButton","tinymce/ui/StackLayout","tinymce/ui/TabPanel","tinymce/ui/TextBox","tinymce/ui/Throbber"]);
34505
34686
  })(this);