datatables.net 3.0.0-beta.1 → 3.0.0

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.
package/js/dataTables.mjs CHANGED
@@ -1,4 +1,4 @@
1
- /*! DataTables 3.0.0-beta.1
1
+ /*! DataTables
2
2
  * Copyright (c) SpryMedia Ltd - datatables.net/license
3
3
  */
4
4
 
@@ -46,9 +46,11 @@ const reRegexCharacters = new RegExp('(\\' +
46
46
  // implementations differ between browsers.
47
47
  const reDate = /^\d{2,4}[./-]\d{1,2}[./-]\d{1,2}([T ]{1}\d{1,2}[:.]\d{2}([.:]\d{2})?)?$/;
48
48
  const reNewLines = /[\r\n\u2028]/g;
49
+ const isoTimezone = /[T\s]\d{2}.*?(Z|[+-]\d{2}(?::?\d{2})?)$/;
49
50
 
50
51
  var regex = /*#__PURE__*/Object.freeze({
51
52
  __proto__: null,
53
+ isoTimezone: isoTimezone,
52
54
  reDate: reDate,
53
55
  reFormattedNumeric: reFormattedNumeric,
54
56
  reHtml: reHtml,
@@ -520,17 +522,29 @@ function ajax(optionsIn) {
520
522
  if (options.contentType && !(options.data instanceof FormData)) {
521
523
  xhr.setRequestHeader('Content-Type', options.contentType);
522
524
  }
525
+ // Add a X-Request-With header, as jQuery does so and some server-side
526
+ // platforms look for it. Only for same domain though.
527
+ if (options.headers &&
528
+ !options.headers['X-Requested-With'] &&
529
+ !isCrossDomain(options.url)) {
530
+ options.headers['X-Requested-With'] = 'XMLHttpRequest';
531
+ }
523
532
  each(options.headers, (key, val) => {
524
533
  xhr.setRequestHeader(key, val);
525
534
  });
526
535
  if (options.data instanceof FormData) {
527
536
  sendData = options.data;
528
537
  }
529
- else if (method !== 'GET' && options.data && typeof options.data !== 'string') {
530
- sendData = serialize(options.data, options.traditional);
531
- sendData = convertSpaces(sendData, options);
532
- // So beforeSend matches how jQuery behaves
533
- options.data = sendData;
538
+ else if (method !== 'GET' && options.data) {
539
+ if (typeof options.data === 'string') {
540
+ sendData = options.data;
541
+ }
542
+ else {
543
+ sendData = serialize(options.data, options.traditional);
544
+ sendData = convertSpaces(sendData, options);
545
+ // So beforeSend matches how jQuery behaves
546
+ options.data = sendData;
547
+ }
534
548
  }
535
549
  xhr.onreadystatechange = function () {
536
550
  if (xhr.readyState != 4) {
@@ -580,6 +594,7 @@ function ajax(optionsIn) {
580
594
  if (options.beforeSend) {
581
595
  if (options.beforeSend.call(options, xhr, options) === false) {
582
596
  xhr.abort();
597
+ return xhr;
583
598
  }
584
599
  }
585
600
  xhr.send(sendData);
@@ -617,6 +632,17 @@ function convertSpaces(sendData, options) {
617
632
  ? sendData.replace(/%20/g, '+')
618
633
  : sendData;
619
634
  }
635
+ /**
636
+ * Determine if a url is a cross domain request or not
637
+ *
638
+ * @param url URL to check
639
+ * @returns True if cross domain, false otherwise
640
+ */
641
+ function isCrossDomain(url) {
642
+ // Use the current page as the base to handle relative URLs correctly
643
+ const target = new URL(url, window.location.origin);
644
+ return target.origin !== window.location.origin;
645
+ }
620
646
  /**
621
647
  * Get the HTTP method from the Ajax request options
622
648
  *
@@ -755,7 +781,7 @@ function allUnique(src) {
755
781
  * @returns Flattened array
756
782
  */
757
783
  function flatten(out, val) {
758
- if (Array.isArray(val)) {
784
+ if (Array.isArray(val) || arrayLike(val)) {
759
785
  for (var i = 0; i < val.length; i++) {
760
786
  flatten(out, val[i]);
761
787
  }
@@ -1298,7 +1324,7 @@ var timer = /*#__PURE__*/Object.freeze({
1298
1324
  * @returns true if this version of DataTables is greater or equal to the
1299
1325
  * required version, or false if this version of DataTales is not suitable
1300
1326
  */
1301
- function check(version1, version2) {
1327
+ function check$1(version1, version2) {
1302
1328
  let dt = external('datatable');
1303
1329
  var parts1 = version2 ? version2.split('.') : dt.ext.version.split('.');
1304
1330
  var parts2 = version1.split('.');
@@ -1318,7 +1344,7 @@ function check(version1, version2) {
1318
1344
 
1319
1345
  var version = /*#__PURE__*/Object.freeze({
1320
1346
  __proto__: null,
1321
- check: check
1347
+ check: check$1
1322
1348
  });
1323
1349
 
1324
1350
  // Note that the aliased properties are for compatibility with DataTables 2-
@@ -1536,9 +1562,12 @@ function parseEventName(original) {
1536
1562
  isFocus = true;
1537
1563
  }
1538
1564
  else if (name === 'blur') {
1539
- name = 'blurout';
1565
+ name = 'focusout';
1540
1566
  isFocus = true;
1541
1567
  }
1568
+ else if (name === 'ready') {
1569
+ name = 'DOMContentLoaded';
1570
+ }
1542
1571
  return {
1543
1572
  eventName: name,
1544
1573
  isFocus,
@@ -1575,6 +1604,14 @@ function add(el, nameFull, handler, selector, one) {
1575
1604
  if (!eventName) {
1576
1605
  return;
1577
1606
  }
1607
+ // Special handling for the "ready" event - it will trigger when the content
1608
+ // is ready, but also if it is already ready, when added.
1609
+ if (el === document && eventName === 'DOMContentLoaded' && nameFull.includes('ready')) {
1610
+ if (document.readyState === 'complete') {
1611
+ handler(new Event('DOMContentLoaded'));
1612
+ return;
1613
+ }
1614
+ }
1578
1615
  // Create a function that will be the actual event handler, and performs any
1579
1616
  // logic we need, such as delegate handling and adding properties.
1580
1617
  let wrapped = function (event) {
@@ -1600,6 +1637,11 @@ function add(el, nameFull, handler, selector, one) {
1600
1637
  if (!dTarget) {
1601
1638
  return;
1602
1639
  }
1640
+ if (isHover &&
1641
+ event.relatedTarget &&
1642
+ dTarget.contains(event.relatedTarget)) {
1643
+ return;
1644
+ }
1603
1645
  callScope = dTarget;
1604
1646
  }
1605
1647
  // Set the properties that jQuery adds to the event object
@@ -1657,7 +1699,7 @@ function remove(el, nameFull, handler, selector) {
1657
1699
  wrapped.delegateSelector === selector &&
1658
1700
  wrapped.original === handler);
1659
1701
  }
1660
- if (eventName && selector) {
1702
+ else if (eventName && selector) {
1661
1703
  removeEvents = stored.filter(wrapped => wrapped.type === eventName &&
1662
1704
  wrapped.delegateSelector === selector);
1663
1705
  }
@@ -1872,8 +1914,7 @@ class Dom {
1872
1914
  }
1873
1915
  }
1874
1916
  if (sort) {
1875
- Array.prototype.sort.call(this, documentOrder);
1876
- // this.sort(documentOrder);
1917
+ this.sort();
1877
1918
  }
1878
1919
  return this;
1879
1920
  }
@@ -1889,29 +1930,33 @@ class Dom {
1889
1930
  if (!content) {
1890
1931
  return this;
1891
1932
  }
1892
- if (Array.isArray(content)) {
1893
- content.forEach(c => this.append(c));
1894
- return this;
1933
+ if (!arrayLike(content)) {
1934
+ content = [content];
1895
1935
  }
1896
- return this.each(el => {
1897
- if (content instanceof Dom) {
1898
- content.each(item => {
1899
- el.append(item);
1900
- });
1901
- }
1902
- else if (typeof content === 'string') {
1903
- el.insertAdjacentHTML('beforeend', content);
1904
- }
1905
- else if (arrayLike(content)) {
1906
- // Allow for a jQuery object being passed
1907
- let arrayLike = content;
1908
- for (let i = 0; i < arrayLike.length; i++) {
1909
- el.append(arrayLike[i]);
1936
+ // Generate a flat array of the content to be added, with nulls removed
1937
+ // this means it will be an array of nodes and / or strings
1938
+ let flatContent = flatten([], content).filter(c => !!c);
1939
+ /// Got a string somewhere in it, so need to use insertAdjacentHTML
1940
+ if (flatContent.find(val => typeof val === 'string')) {
1941
+ return this.each(el => {
1942
+ for (let i = 0; i < flatContent.length; i++) {
1943
+ if (typeof flatContent[i] === 'string') {
1944
+ el.insertAdjacentHTML('beforeend', flatContent[i]);
1945
+ }
1946
+ else {
1947
+ el.append(flatContent[i]);
1948
+ }
1910
1949
  }
1950
+ });
1951
+ }
1952
+ // Otherwise we can use a document fragment for a single mutation
1953
+ // on the main document
1954
+ return this.each(el => {
1955
+ let fragment = new DocumentFragment();
1956
+ for (let i = 0; i < flatContent.length; i++) {
1957
+ fragment.append(flatContent[i]);
1911
1958
  }
1912
- else {
1913
- el.append(content);
1914
- }
1959
+ el.append(fragment);
1915
1960
  });
1916
1961
  }
1917
1962
  /**
@@ -2078,9 +2123,7 @@ class Dom {
2078
2123
  css(rule, value) {
2079
2124
  // String getter
2080
2125
  if (typeof rule === 'string' && value === undefined) {
2081
- return this.length
2082
- ? getComputedStyle(this[0])[rule]
2083
- : null;
2126
+ return this.length ? getComputedStyle(this[0])[rule] : null;
2084
2127
  }
2085
2128
  return this.each(el => {
2086
2129
  if (typeof rule === 'string') {
@@ -2108,11 +2151,11 @@ class Dom {
2108
2151
  return this.length ? dataConvert(this[0].dataset[name]) : null;
2109
2152
  }
2110
2153
  if (typeof name === 'string') {
2111
- this.each(el => el.dataset[name] = JSON.stringify(value));
2154
+ this.each(el => (el.dataset[name] = JSON.stringify(value)));
2112
2155
  }
2113
2156
  else {
2114
2157
  each(name, (key, val) => {
2115
- this.each(el => el.dataset[key] = JSON.stringify(val));
2158
+ this.each(el => (el.dataset[key] = JSON.stringify(val)));
2116
2159
  });
2117
2160
  }
2118
2161
  return this;
@@ -2664,6 +2707,18 @@ class Dom {
2664
2707
  el.style.display = 'block';
2665
2708
  });
2666
2709
  }
2710
+ /**
2711
+ * Sort the DOM elements into document order.
2712
+ *
2713
+ * This is normally not needed as elements selected with a DOM selector are
2714
+ * automatically sorted in document order. However, in the case of elements
2715
+ * being added as an array, their order will be retained. In such as case
2716
+ * you might wish to sort them in document order.
2717
+ */
2718
+ sort() {
2719
+ Array.prototype.sort.call(this, documentOrder);
2720
+ return this;
2721
+ }
2667
2722
  text(txt) {
2668
2723
  if (txt === undefined) {
2669
2724
  return this.count() ? this[0].textContent : null;
@@ -3824,7 +3879,7 @@ const ext = {
3824
3879
  * Software version
3825
3880
  * @type string
3826
3881
  */
3827
- version: '3.0.0-beta.1'
3882
+ version: '3.0.0'
3828
3883
  };
3829
3884
  //
3830
3885
  // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
@@ -3839,7 +3894,7 @@ Object.assign(ext, {
3839
3894
  oStdClasses: ext.classes,
3840
3895
  oPagination: ext.pager,
3841
3896
  sVersion: ext.version,
3842
- fnVersionCheck: check
3897
+ fnVersionCheck: check$1
3843
3898
  });
3844
3899
 
3845
3900
  /**
@@ -4068,7 +4123,7 @@ function arrayApply(arr, data) {
4068
4123
  function listener(that, name, src) {
4069
4124
  let srcArr = Array.isArray(src) ? src : [src];
4070
4125
  for (var i = 0; i < srcArr.length; i++) {
4071
- that.on(name + '.dt', srcArr[i]);
4126
+ that.on(name + '.dt.DT', srcArr[i]);
4072
4127
  }
4073
4128
  }
4074
4129
  /**
@@ -4117,7 +4172,7 @@ function __mldObj(d, format, locale) {
4117
4172
  var dt;
4118
4173
  resolveWindowLibs();
4119
4174
  if (__moment) {
4120
- dt = __moment.utc(d, format, locale, true);
4175
+ dt = __moment(d, format, locale, true);
4121
4176
  if (!dt.isValid()) {
4122
4177
  return null;
4123
4178
  }
@@ -4183,10 +4238,12 @@ function __mlHelper(localeString) {
4183
4238
  // gives milliseconds epoch
4184
4239
  return d.valueOf();
4185
4240
  }
4186
- },
4187
- className: 'dt-right'
4241
+ }
4188
4242
  });
4189
4243
  }
4244
+ if (!store.className[typeName]) {
4245
+ store.className[typeName] = 'dt-right';
4246
+ }
4190
4247
  return function (d, type) {
4191
4248
  // Allow for a default value
4192
4249
  if (d === null || d === undefined) {
@@ -4220,6 +4277,15 @@ function __mlHelper(localeString) {
4220
4277
  !(d instanceof Date)) {
4221
4278
  return d;
4222
4279
  }
4280
+ // Determine if there is a timezone. If there is, we want to reuse
4281
+ // it for the output, so the timezone doesn't change between the
4282
+ // input and output.
4283
+ let options = {};
4284
+ let tzMatch = typeof d === 'string' ? d.match(util.regex.isoTimezone) : null;
4285
+ if (tzMatch) {
4286
+ options.timeZone = tzMatch[1] === 'Z' ? 'UTC' : tzMatch[1];
4287
+ }
4288
+ // Get a Date object (Luxon, moment or Date)
4223
4289
  var dt = __mldObj(d, from, locale);
4224
4290
  if (dt === null) {
4225
4291
  return d;
@@ -4228,7 +4294,7 @@ function __mlHelper(localeString) {
4228
4294
  return dt;
4229
4295
  }
4230
4296
  var formatted = to === null
4231
- ? __mld(dt, 'toDate', 'toJSDate', '')[localeString](navigator.language, { timeZone: 'UTC' })
4297
+ ? __mld(dt, 'toDate', 'toJSDate', '')[localeString](navigator.language, options)
4232
4298
  : __mld(dt, 'format', 'toFormat', 'toISOString', to);
4233
4299
  // XSS protection
4234
4300
  return type === 'display' ? util.escapeHtml(formatted) : formatted;
@@ -4278,10 +4344,12 @@ function datetime(format, locale) {
4278
4344
  pre: function (d) {
4279
4345
  return __mldObj(d, format, locale) || 0;
4280
4346
  }
4281
- },
4282
- className: 'dt-right'
4347
+ }
4283
4348
  });
4284
4349
  }
4350
+ if (!store.className[typeName]) {
4351
+ store.className[typeName] = 'dt-right';
4352
+ }
4285
4353
  }
4286
4354
  /**
4287
4355
  * Helpers for `columns.render`.
@@ -4950,6 +5018,7 @@ function invalidate(settings, rowIdx, src, colIdx) {
4950
5018
  // Update DataTables special `DT_*` attributes for the row
4951
5019
  rowAttributes(settings, row);
4952
5020
  }
5021
+ callbackFire(settings, null, 'rowInvalidate', [settings, rowIdx, colIdx], false);
4953
5022
  }
4954
5023
  /**
4955
5024
  * Get the cells and data for a given row - from a <tr> element
@@ -5295,9 +5364,9 @@ function getWideStrings(settings, colIdx) {
5295
5364
  // Don't want script, dialog or template tags in the width
5296
5365
  // calculations as they are hidden content
5297
5366
  cellString = cellString
5298
- .replace(/<script[\s\S]*?<\/script>/gi, ' ')
5299
- .replace(/<dialog[\s\S]*?<\/dialog>/gi, ' ')
5300
- .replace(/<template[\s\S]*?<\/template>/gi, ' ');
5367
+ .replace(/<script[\s\S]*?<\/script(?:\s[^>]*)?>/gi, ' ')
5368
+ .replace(/<dialog[\s\S]*?<\/dialog(?:\s[^>]*)?>/gi, ' ')
5369
+ .replace(/<template[\s\S]*?<\/template(?:\s[^>]*)?>/gi, ' ');
5301
5370
  var noHtml = util.string
5302
5371
  .stripHtml(cellString, ' ')
5303
5372
  .replace(/&nbsp;/g, ' ');
@@ -5384,8 +5453,8 @@ function featureTable(settings) {
5384
5453
  let captionSide = caption
5385
5454
  ? caption._captionSide
5386
5455
  : null;
5387
- let headerClone = table.clone(false);
5388
- let footerClone = table.clone(false);
5456
+ let tableCloneHeader = table.clone(false);
5457
+ let tableCloneFooter = table.clone(false);
5389
5458
  let footer = table.children('tfoot');
5390
5459
  let size = function (s) {
5391
5460
  return !s ? '100%' : stringToCss(s);
@@ -5406,11 +5475,10 @@ function featureTable(settings) {
5406
5475
  * table - scroll foot table
5407
5476
  * tfoot - tfoot
5408
5477
  */
5409
- let scroller = Dom
5410
- .c('div')
5478
+ let scroller = Dom.c('div')
5411
5479
  .classAdd(classes.container)
5412
- .append(Dom
5413
- .c('div')
5480
+ .attr('role', 'table')
5481
+ .append(Dom.c('div')
5414
5482
  .classAdd(classes.header.self)
5415
5483
  .css({
5416
5484
  overflow: 'hidden',
@@ -5418,40 +5486,41 @@ function featureTable(settings) {
5418
5486
  border: '0',
5419
5487
  width: scrollX ? size(scrollX) : '100%'
5420
5488
  })
5421
- .append(Dom
5422
- .c('div')
5489
+ .attr('role', 'none')
5490
+ .append(Dom.c('div')
5423
5491
  .classAdd(classes.header.inner)
5424
5492
  .css({
5425
5493
  'box-sizing': 'content-box',
5426
5494
  width: scroll.xInner || '100%'
5427
5495
  })
5428
- .append(headerClone
5496
+ .attr('role', 'none')
5497
+ .append(tableCloneHeader
5429
5498
  .attrRemove('id')
5430
5499
  .css('margin-left', '0')
5431
5500
  .append(captionSide === 'top' ? caption : null)
5432
5501
  .append(table.children('thead')))))
5433
- .append(Dom
5434
- .c('div')
5502
+ .append(Dom.c('div')
5435
5503
  .classAdd(classes.body)
5436
5504
  .css({
5437
5505
  position: 'relative',
5438
5506
  overflow: 'auto',
5439
5507
  width: size(scrollX)
5440
5508
  })
5509
+ .attr('role', 'none')
5441
5510
  .append(table));
5442
5511
  if (footer.count()) {
5443
- scroller.append(Dom
5444
- .c('div')
5512
+ scroller.append(Dom.c('div')
5445
5513
  .classAdd(classes.footer.self)
5446
5514
  .css({
5447
5515
  overflow: 'hidden',
5448
5516
  border: '0',
5449
5517
  width: scrollX ? size(scrollX) : '100%'
5450
5518
  })
5451
- .append(Dom
5452
- .c('div')
5519
+ .attr('role', 'none')
5520
+ .append(Dom.c('div')
5453
5521
  .classAdd(classes.footer.inner)
5454
- .append(footerClone
5522
+ .attr('role', 'none')
5523
+ .append(tableCloneFooter
5455
5524
  .attrRemove('id')
5456
5525
  .css('margin-left', '0')
5457
5526
  .append(captionSide === 'bottom' ? caption : null)
@@ -5489,6 +5558,22 @@ function featureTable(settings) {
5489
5558
  settings.scrollFoot = scrollFoot;
5490
5559
  // On redraw - align columns
5491
5560
  settings.callbacks.draw.push(scrollDraw);
5561
+ // Aria roles - because we break the table up into parts we need to be very
5562
+ // explicit with the roles to create the accessability tree for the table,
5563
+ // otherwise browser's attempt to "fix" the tree by filling in what it
5564
+ // thinks are gaps. The static elements that we can assign roles to are done
5565
+ // here. Dynamic ones are done in the draw function below.
5566
+ table.attr('role', 'none');
5567
+ table.find('tbody').attr('role', 'rowgroup');
5568
+ tableCloneHeader.attr('role', 'none');
5569
+ tableCloneFooter.attr('role', 'none');
5570
+ settings.colgroup.find('colgroup').attr('role', 'none');
5571
+ // Move the info feature's aria desc by to the new "table"
5572
+ let describedBy = table.attr('aria-describedby');
5573
+ if (describedBy) {
5574
+ scroller.attr('aria-describedby', describedBy);
5575
+ table.attrRemove('aria-describedby');
5576
+ }
5492
5577
  return scroller.get(0);
5493
5578
  }
5494
5579
  /**
@@ -5521,10 +5606,13 @@ function scrollDraw(settings) {
5521
5606
  else {
5522
5607
  settings.scrollBarVis = scrollBarVis;
5523
5608
  }
5609
+ header.find('thead').attr('role', 'rowgroup');
5610
+ footer.find('tfoot').attr('role', 'rowgroup');
5524
5611
  // 1. Re-create the table inside the scrolling div
5525
5612
  // Remove the old minimised thead and tfoot elements in the inner table
5526
5613
  table.children('thead, tfoot').remove();
5527
- // Clone the current header and footer elements and then place it into the inner table
5614
+ // Clone the current header and footer elements and then place it into the
5615
+ // inner table
5528
5616
  headerCopy = header.clone(true).prependTo(table);
5529
5617
  headerCopy.find('th, td').attrRemove('tabindex');
5530
5618
  headerCopy.find('[id]').attrRemove('id');
@@ -5559,8 +5647,7 @@ function scrollDraw(settings) {
5559
5647
  }
5560
5648
  }
5561
5649
  if (firstTr) {
5562
- let colSizes = Dom
5563
- .s(firstTr)
5650
+ let colSizes = Dom.s(firstTr)
5564
5651
  .children('th, td')
5565
5652
  .mapTo(function (cell, idx) {
5566
5653
  return {
@@ -5625,6 +5712,18 @@ function scrollDraw(settings) {
5625
5712
  }
5626
5713
  // Correct DOM ordering for colgroup - comes before the thead
5627
5714
  table.children('colgroup').prependTo(table);
5715
+ // Remove tabindex from the hidden row elements
5716
+ table.find('thead, tfoot').find('[tabindex]').attrRemove('tabindex');
5717
+ // Dynamic ARIA roles - see setup for details on why this is needed
5718
+ table
5719
+ .find('thead, tfoot')
5720
+ .attr('role', 'none')
5721
+ .find('[role]')
5722
+ .attrRemove('role');
5723
+ table.find('tbody tr:not([role])').attr('role', 'row');
5724
+ table.find('tbody td:not([role]), tbody th:not([role])').attr('role', 'cell');
5725
+ scrollAria(headerCopy);
5726
+ scrollAria(footerCopy);
5628
5727
  // Adjust the position of the header in case we loose the y-scrollbar
5629
5728
  divBody.trigger('scroll');
5630
5729
  // If sorting or filtering has occurred, jump the scrolling back to the top
@@ -5633,6 +5732,18 @@ function scrollDraw(settings) {
5633
5732
  divBodyEl.scrollTop(0);
5634
5733
  }
5635
5734
  }
5735
+ /**
5736
+ * Apply ARIA roles for the header / footer of a scrolling table
5737
+ * @param element
5738
+ */
5739
+ function scrollAria(element) {
5740
+ if (element) {
5741
+ element.find('tfoot:not([role])').attr('role', 'rowgroup');
5742
+ element.find('tr:not([role])').attr('role', 'row');
5743
+ element.find('th:not([role])').attr('role', 'columnheader');
5744
+ element.find('td:not([role])').attr('role', 'cell');
5745
+ }
5746
+ }
5636
5747
 
5637
5748
  /**
5638
5749
  * Add a column to the list used for the table with default values
@@ -5856,6 +5967,7 @@ function columnTypes(settings) {
5856
5967
  var types = ext.type.detect;
5857
5968
  var i, iLen, j, jen, k, ken;
5858
5969
  var col, detectedType, cache;
5970
+ var originalTypes = columns.map(c => c.type).join(',');
5859
5971
  // For each column, spin over the data type detection functions, seeing if
5860
5972
  // one matches
5861
5973
  for (i = 0, iLen = columns.length; i < iLen; i++) {
@@ -5953,6 +6065,10 @@ function columnTypes(settings) {
5953
6065
  _columnAutoRender(settings, i);
5954
6066
  }
5955
6067
  }
6068
+ var newTypes = columns.map(c => c.type).join(',');
6069
+ if (newTypes !== originalTypes) {
6070
+ callbackFire(settings, null, 'columnTypes', [settings], false);
6071
+ }
5956
6072
  }
5957
6073
  /**
5958
6074
  * Apply an auto detected renderer to data which doesn't yet have a renderer
@@ -7154,13 +7270,13 @@ function implementState(settings, s, callback) {
7154
7270
  let idx = currentNames.indexOf(col[0]);
7155
7271
  if (idx < 0) {
7156
7272
  // If the column was not found ignore it and continue
7157
- return;
7273
+ continue;
7158
7274
  }
7159
7275
  set[0] = idx;
7160
7276
  }
7161
7277
  else if (set[0] >= columns.length) {
7162
7278
  // If the column index is out of bounds ignore it and continue
7163
- return;
7279
+ continue;
7164
7280
  }
7165
7281
  settings.order.push(set);
7166
7282
  }
@@ -8297,6 +8413,11 @@ function reDraw(settings, holdPosition, recompute) {
8297
8413
  if (holdPosition !== true) {
8298
8414
  settings.displayStart = 0;
8299
8415
  }
8416
+ else {
8417
+ // Keep position, but make sure that there is actually data to display,
8418
+ // otherwise we need to rewind a bit (e.g. if rows were deleted)
8419
+ lengthOverflow(settings);
8420
+ }
8300
8421
  // Let any modules know about the draw hold position state (used by
8301
8422
  // scrolling internally)
8302
8423
  settings.drawHold = holdPosition;
@@ -9473,12 +9594,13 @@ function selectCells(settings, selector, opts) {
9473
9594
  // Otherwise the selector is a node, and there is one last option - the
9474
9595
  // element might be a child of an element which has dt-row and dt-column
9475
9596
  // data attributes
9476
- host = Dom.s(s).closest('*[data-dt-row]');
9477
- return host.count()
9597
+ let rowHost = Dom.s(s).closest('*[data-dt-row]');
9598
+ let columnHost = Dom.s(s).closest('*[data-dt-column]');
9599
+ return rowHost.count()
9478
9600
  ? [
9479
9601
  {
9480
- row: host.data('dt-row'),
9481
- column: host.data('dt-column')
9602
+ row: parseInt(rowHost.attr('data-dt-row')),
9603
+ column: parseInt(columnHost.attr('data-dt-column'))
9482
9604
  }
9483
9605
  ]
9484
9606
  : [];
@@ -9791,7 +9913,7 @@ function selectColumns(settings, selector, opts) {
9791
9913
  // Otherwise a node which might have a `dt-column` data attribute, or be
9792
9914
  // a child or such an element
9793
9915
  var host = Dom.s(s).closest('*[data-dt-column]');
9794
- return host.count() ? [host.data('dt-column')] : [];
9916
+ return host.count() ? [parseInt(host.attr('data-dt-column'))] : [];
9795
9917
  };
9796
9918
  var selected = selectorRun('column', selector, run, settings, opts);
9797
9919
  return opts.columnOrder && opts.columnOrder === 'index'
@@ -9988,18 +10110,19 @@ registerPlural('columns().widths()', 'column().width()', function () {
9988
10110
  // Injects a fake row into the table for just a moment so the widths can
9989
10111
  // be read, regardless of colspan in the header and rows being present
9990
10112
  // in the body
9991
- var columns = this.columns(':visible').count();
10113
+ var columns = this.columns(':visible');
9992
10114
  var row = Dom
9993
10115
  .c('tr')
9994
- .html('<td>' + Array(columns).join('</td><td>') + '</td>');
10116
+ .html('<td>' + Array(columns.count()).join('</td><td>') + '</td>');
9995
10117
  Dom.s(this.table().body()).append(row);
9996
- var widths = row.children().mapTo(el => {
9997
- return Dom.s(el).width('outer');
10118
+ var widths = [];
10119
+ var indexes = columns.indexes();
10120
+ row.children().each((el, idx) => {
10121
+ widths[indexes[idx]] = Dom.s(el).width('outer');
9998
10122
  });
9999
10123
  row.remove();
10000
- return this.iterator('column', function (settings, column) {
10001
- var visIdx = columnIndexToVisible(settings, column);
10002
- return visIdx !== null ? widths[visIdx] : 0;
10124
+ return this.iterator('column', (settings, column) => {
10125
+ return widths[column] || 0;
10003
10126
  }, true);
10004
10127
  });
10005
10128
  registerPlural('columns().indexes()', 'column().index()', function (type) {
@@ -10471,7 +10594,7 @@ function selectRows(settings, selector, opts) {
10471
10594
  }
10472
10595
  else {
10473
10596
  var host = Dom.s(sel).closest('*[data-dt-row]');
10474
- return host.count() ? [host.data('dt-row')] : [];
10597
+ return host.count() ? [parseInt(host.attr('data-dt-row'))] : [];
10475
10598
  }
10476
10599
  }
10477
10600
  // ID selector. Want to always be able to select rows by id, regardless
@@ -10503,8 +10626,7 @@ function selectRows(settings, selector, opts) {
10503
10626
  // Get nodes in the order from the `rows` array with null values removed
10504
10627
  var nodes = util.array.removeEmpty(util.array.pluckOrder(settings.data, rows, 'tr'));
10505
10628
  // Selector - selector string, array of nodes or jQuery object.
10506
- return Dom
10507
- .s(nodes)
10629
+ return Dom.s(nodes)
10508
10630
  .filter(sel)
10509
10631
  .mapTo((el) => el._DT_RowIndex);
10510
10632
  };
@@ -10649,7 +10771,7 @@ register('row().data()', function (data) {
10649
10771
  util.set(ctx[0].rowId)(data, row.tr.id);
10650
10772
  }
10651
10773
  // Automatically invalidate
10652
- invalidate(ctx[0], this[0], 'data');
10774
+ invalidate(ctx[0], this[0][0], 'data');
10653
10775
  return this;
10654
10776
  });
10655
10777
  register('row().node()', function () {
@@ -10967,10 +11089,10 @@ register('caption()', function (value, side) {
10967
11089
  caption.css('caption-side', side);
10968
11090
  caption.get(0)._captionSide = side;
10969
11091
  }
10970
- if (container.find('div.dataTables_scroll').count()) {
10971
- var selector = side === 'top' ? 'Head' : 'Foot';
11092
+ if (container.find('div.dt-scroll').count()) {
11093
+ var selector = side === 'top' ? 'head' : 'foot';
10972
11094
  container
10973
- .find('div.dataTables_scroll' + selector + ' table')
11095
+ .find('div.dt-scroll-' + selector + ' table')
10974
11096
  .prepend(caption);
10975
11097
  }
10976
11098
  else {
@@ -10983,6 +11105,272 @@ register('caption.node()', function () {
10983
11105
  return ctx.length ? ctx[0].captionNode : null;
10984
11106
  });
10985
11107
 
11108
+ /**
11109
+ * What's this!? "DataTables Plus" is a commercial set of extensions for
11110
+ * DataTables, such as Editor, and the functions in this file allow a license
11111
+ * key to be provided (`DataTable.key(...)`) to unlock those features.
11112
+ *
11113
+ * This is the modal that I've selected to make DataTables sustainable, open
11114
+ * source core, with some commercial extensions available.
11115
+ *
11116
+ * Please support DataTables and open source by purchasing a Plus license from
11117
+ * https://datatables.net/plus .
11118
+ */
11119
+ let _ready = false;
11120
+ let _notice;
11121
+ let _processingKey = false;
11122
+ let _delayedReleaseDate = null;
11123
+ let _delayedSoftware = null;
11124
+ const _licenseInfo = {
11125
+ developers: 0,
11126
+ type: null,
11127
+ expires: null,
11128
+ valid: null
11129
+ };
11130
+ const _wm = Dom.c('div');
11131
+ const _publicKey = 'BE1A9w9D9U/4s4/TogY+1sW/dLJ8IquzK1PmV70J93ZTIvXMZ0eV2NAb52ntpgwVFySSB2fOI7geLNO737rQAyo=';
11132
+ /**
11133
+ * Convert a base64 string to a binary array
11134
+ *
11135
+ * @param b64 Source string
11136
+ * @returns Array
11137
+ */
11138
+ function b64ToBuf(b64) {
11139
+ return Uint8Array.from(atob(b64), c => c.charCodeAt(0));
11140
+ }
11141
+ /**
11142
+ * Logic to check the trial and plus license expiry and display messages if
11143
+ * needed. There is particular consideration for checking a release date of
11144
+ * software, as the license for DataTables Plus is perpetual for the version
11145
+ * purchased, and it shouldn't show a message for the purchased version ever.
11146
+ *
11147
+ * @param releaseDate The date the software was released on.
11148
+ * @param software The software name being validated. Can be null for a general
11149
+ * "Plus" check.
11150
+ * @returns true if valid, false otherwise
11151
+ */
11152
+ function check(releaseDate, software) {
11153
+ let expires = _licenseInfo.expires;
11154
+ if (_licenseInfo.valid === false) {
11155
+ noticePrep('License key invalid');
11156
+ noticeDisplay();
11157
+ }
11158
+ else if (_licenseInfo.type === 'trial') {
11159
+ // Trail is for plus, so the software type isn't taken into account
11160
+ let remaining = expires
11161
+ ? Math.ceil((expires.getTime() - new Date().getTime()) / 86400000)
11162
+ : -1;
11163
+ if (remaining < 0) {
11164
+ // Trial expires
11165
+ consoleMsg('Your trial has now expired - https://datatables.net/plus', 'warn');
11166
+ noticePrep('Trial expired');
11167
+ noticeDisplay();
11168
+ return false;
11169
+ }
11170
+ else {
11171
+ // Let the user know when it is going to expire with a console
11172
+ // message.
11173
+ consoleMsg('Your trial expires in ' +
11174
+ remaining +
11175
+ ' day' +
11176
+ (remaining === 1 ? '' : 's'));
11177
+ return true;
11178
+ }
11179
+ }
11180
+ else if (_licenseInfo.type === 'plus' ||
11181
+ (_licenseInfo.type === 'editor' && software === 'editor')) {
11182
+ if (!expires || new Date(releaseDate) > expires) {
11183
+ noticePrep('Upgrade required for this version');
11184
+ noticeDisplay();
11185
+ return false;
11186
+ }
11187
+ return true;
11188
+ }
11189
+ else if (_licenseInfo.type === 'editor' && software !== 'editor') {
11190
+ noticePrep('License for Editor only. Upgrade for Plus');
11191
+ noticeDisplay();
11192
+ return false;
11193
+ }
11194
+ noticePrep();
11195
+ noticeDisplay();
11196
+ return false;
11197
+ }
11198
+ /**
11199
+ * Common log message handling
11200
+ *
11201
+ * @param msg Message to show
11202
+ * @param level Log level
11203
+ */
11204
+ function consoleMsg(msg, level = 'log') {
11205
+ let fn = level === 'log' ? console.log : console.warn;
11206
+ fn('%cDataTables Plus%c ' + msg, 'background: #007bff; color: #fff; padding: 2px 5px;', 'color: inherit;');
11207
+ }
11208
+ /**
11209
+ * Set the DataTables Plus key to use
11210
+ *
11211
+ * @param key DataTables Plus key - obtain from https://datatables.net/account .
11212
+ */
11213
+ const key = function (key) {
11214
+ _processingKey = true;
11215
+ // Run the verification of the key
11216
+ verify(key)
11217
+ .then(result => {
11218
+ _processingKey = false;
11219
+ check(_delayedReleaseDate, _delayedSoftware);
11220
+ })
11221
+ .catch(() => {
11222
+ _processingKey = false;
11223
+ check(_delayedReleaseDate, _delayedSoftware);
11224
+ });
11225
+ };
11226
+ /**
11227
+ * Build the notice
11228
+ *
11229
+ * @returns
11230
+ */
11231
+ function noticePrep(text) {
11232
+ if (!_ready) {
11233
+ let shadow = _wm[0].attachShadow({ mode: 'closed' });
11234
+ let notice = Dom.c('div').css({
11235
+ position: 'fixed',
11236
+ bottom: '1em',
11237
+ right: '1em',
11238
+ border: '1px solid #ffc107',
11239
+ background: '#fff3cd',
11240
+ color: '#856404',
11241
+ padding: '0.5em 1em',
11242
+ 'font-family': 'sans-serif',
11243
+ 'font-size': '12px',
11244
+ 'border-radius': '4px',
11245
+ 'z-index': '10000',
11246
+ 'box-shadow': '0 2px 5px rgba(0,0,0,0.2)'
11247
+ });
11248
+ Dom.c('a')
11249
+ .attr('href', 'https://datatables.net/tn/25')
11250
+ .attr('target', '_blank')
11251
+ .css({
11252
+ color: 'inherit',
11253
+ 'text-decoration': 'none'
11254
+ })
11255
+ .appendTo(notice);
11256
+ if (!text) {
11257
+ text = 'License key required';
11258
+ }
11259
+ shadow.appendChild(notice[0]);
11260
+ _notice = notice;
11261
+ _ready = true;
11262
+ }
11263
+ if (text) {
11264
+ _notice
11265
+ .find('a')
11266
+ .html('DataTables Plus: ' + text + ' - learn more &#187;');
11267
+ }
11268
+ }
11269
+ /**
11270
+ * Display the license notice
11271
+ */
11272
+ function noticeDisplay() {
11273
+ if (!_processingKey && !document.body.contains(_wm[0])) {
11274
+ document.body.appendChild(_wm[0]);
11275
+ }
11276
+ }
11277
+ /**
11278
+ * Validate the license string, which is in two parts - the first is a payload
11279
+ * that provides a small amount of information about the license, and the second
11280
+ * which is the license key.
11281
+ *
11282
+ * @param licenseString Key to validate
11283
+ * @returns Promise with validation information
11284
+ */
11285
+ function verify(licenseString) {
11286
+ return new Promise(function (resolve) {
11287
+ try {
11288
+ var parts = licenseString.split(':');
11289
+ if (parts.length !== 2) {
11290
+ _licenseInfo.valid = false;
11291
+ return resolve();
11292
+ }
11293
+ var payload = parts[0];
11294
+ var signatureB64 = parts[1];
11295
+ // Backwards compat for old browsers
11296
+ var cryptoObj = window.crypto || window.msCrypto;
11297
+ var subtle = cryptoObj.subtle || cryptoObj.webkitSubtle;
11298
+ var rawKey = b64ToBuf(_publicKey);
11299
+ var rawSig = b64ToBuf(signatureB64);
11300
+ var data = new TextEncoder().encode(payload);
11301
+ subtle
11302
+ .importKey('raw', rawKey, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify'])
11303
+ .then(function (key) {
11304
+ return subtle.verify({ name: 'ECDSA', hash: { name: 'SHA-256' } }, key, rawSig, data);
11305
+ })
11306
+ .then(function (isValid) {
11307
+ if (!isValid) {
11308
+ _licenseInfo.valid = false;
11309
+ return resolve();
11310
+ }
11311
+ // Extract the payload to be useful
11312
+ var payloadParts = payload.match(/(plus|trial|editor)_(\d+)_(\d{4})(\d{2})(\d{2})/);
11313
+ if (!payloadParts || payloadParts.length !== 6) {
11314
+ _licenseInfo.valid = false;
11315
+ return resolve();
11316
+ }
11317
+ _licenseInfo.valid = true;
11318
+ _licenseInfo.type = payloadParts[1];
11319
+ _licenseInfo.developers = parseInt(payloadParts[2]);
11320
+ _licenseInfo.expires = new Date(payloadParts[3] +
11321
+ '-' +
11322
+ payloadParts[4] +
11323
+ '-' +
11324
+ payloadParts[5]);
11325
+ resolve();
11326
+ })
11327
+ .catch(function () {
11328
+ _licenseInfo.valid = false;
11329
+ resolve();
11330
+ });
11331
+ }
11332
+ catch (e) {
11333
+ _licenseInfo.valid = false;
11334
+ resolve();
11335
+ }
11336
+ });
11337
+ }
11338
+ /**
11339
+ * Create the `plus` function on `DataTable` which Plus extensions can call to
11340
+ * determine if the license key is valid and in date for the release. The
11341
+ * resulting function is called like this: `DataTable.plus('2026-12-25')` and
11342
+ * will return `true` or `false` depending on the key that was given (or not).
11343
+ *
11344
+ * @param DataTable The DataTable host object
11345
+ */
11346
+ function plus (DataTable) {
11347
+ Object.defineProperty(DataTable, 'plus', {
11348
+ value: function (releaseDate, software = null) {
11349
+ // Unsecure sites are only useful for development, so allow there
11350
+ // and on the site.
11351
+ let host = window.location.hostname;
11352
+ let isDev = host === '192.168.234.234' ||
11353
+ host.endsWith('.datatables.net') ||
11354
+ host === 'datatables.net';
11355
+ if (isDev) {
11356
+ return true;
11357
+ }
11358
+ if (_processingKey) {
11359
+ // The validation of the key is async, so there is a chance that
11360
+ // it could still be happening when this runs. We just queue the
11361
+ // last one if that is the case.
11362
+ _delayedReleaseDate = releaseDate;
11363
+ _delayedSoftware = software;
11364
+ return true;
11365
+ }
11366
+ return check(releaseDate, software);
11367
+ },
11368
+ configurable: false,
11369
+ enumerable: false,
11370
+ writable: false
11371
+ });
11372
+ }
11373
+
10986
11374
  /**
10987
11375
  * CommonJS factory function pass through. This will check if the arguments
10988
11376
  * given are a window object or a jQuery object. If so they are set accordingly.
@@ -11342,8 +11730,7 @@ register$2('pageLength', function (settings, optsIn) {
11342
11730
  }
11343
11731
  // Wrapper element - use a span as a holder for where the select will go
11344
11732
  var tmpId = 'tmp-' + +new Date();
11345
- var div = Dom
11346
- .c('div')
11733
+ var div = Dom.c('div')
11347
11734
  .classAdd(classes.container)
11348
11735
  .html(str.replace('_MENU_', '<span id="' + tmpId + '"></span>'));
11349
11736
  // Save text node content for macro updating
@@ -11365,9 +11752,9 @@ register$2('pageLength', function (settings, optsIn) {
11365
11752
  });
11366
11753
  };
11367
11754
  // Next, the select itself, along with the options
11368
- var select = Dom
11369
- .c('select')
11755
+ var select = Dom.c('select')
11370
11756
  .attr('aria-controls', tableId)
11757
+ .attr('autocomplete', 'off')
11371
11758
  .classAdd(classes.select);
11372
11759
  for (i = 0; i < lengths.length; i++) {
11373
11760
  // Attempt to look up the length from the i18n options
@@ -11398,7 +11785,26 @@ register$2('pageLength', function (settings, optsIn) {
11398
11785
  // Update node value whenever anything changes the table's length
11399
11786
  Dom.s(settings.table).on('length.dt.DT', function (e, s, len) {
11400
11787
  if (settings === s) {
11401
- div.find('select').val(len);
11788
+ let localSelect = div.find('select');
11789
+ // Remove any temporary values
11790
+ localSelect.find('option[data-dt-len-tmp]').remove();
11791
+ let option = localSelect.find('option[value="' + len + '"]');
11792
+ // If the select list doesn't have the target value, then we
11793
+ // need to add it for display.
11794
+ if (!option.length) {
11795
+ let after = findInsertBeforePoint(select, len);
11796
+ let tempOption = Dom.c('option')
11797
+ .val(len)
11798
+ .text(len)
11799
+ .attr('data-dt-len-tmp', true);
11800
+ if (after && after.length) {
11801
+ tempOption.insertBefore(after);
11802
+ }
11803
+ else {
11804
+ localSelect.append(tempOption);
11805
+ }
11806
+ }
11807
+ localSelect.val(len);
11402
11808
  // Resolve plurals in the text for the new length
11403
11809
  updateEntries(len);
11404
11810
  }
@@ -11406,6 +11812,19 @@ register$2('pageLength', function (settings, optsIn) {
11406
11812
  updateEntries(settings.pageLength);
11407
11813
  return div;
11408
11814
  }, 'l');
11815
+ /**
11816
+ * Find the element to insert the temporary option before to keep the sequence.
11817
+ *
11818
+ * @param select Select element
11819
+ * @param insertValue Page length value
11820
+ * @returns Target option or null if not found
11821
+ */
11822
+ function findInsertBeforePoint(select, insertValue) {
11823
+ let options = select.find('option');
11824
+ let values = options.mapTo(el => parseInt(el.value));
11825
+ let idx = values.findIndex(val => val > insertValue);
11826
+ return idx < -1 ? null : options.eq(idx);
11827
+ }
11409
11828
 
11410
11829
  let __searchCounter = 0;
11411
11830
  register$2('search', function (settings, optsIn) {
@@ -11416,7 +11835,9 @@ register$2('search', function (settings, optsIn) {
11416
11835
  let classes = settings.classes.search;
11417
11836
  let tableId = settings.tableId;
11418
11837
  let language = settings.language;
11419
- let input = '<input type="search" class="' + classes.input + '"/>';
11838
+ let input = '<input type="search" class="' +
11839
+ classes.input +
11840
+ '" autocomplete="off"/>';
11420
11841
  let opts = util.object.assignDeep({
11421
11842
  columns: '*',
11422
11843
  placeholder: language.searchPlaceholder,
@@ -12238,6 +12659,8 @@ DataTable.datetime = datetime;
12238
12659
  DataTable.__browser = browser;
12239
12660
  DataTable.Dom = Dom;
12240
12661
  DataTable.ajax = util.ajax;
12662
+ DataTable.key = key;
12663
+ plus(DataTable);
12241
12664
  /**
12242
12665
  * Private data store, containing all of the settings objects that are created
12243
12666
  * for the tables on a given page.