1/* ============================================================= 2 * bootstrap3-typeahead.js v4.0.2 3 * https://github.com/bassjobsen/Bootstrap-3-Typeahead 4 * ============================================================= 5 * Original written by @mdo and @fat 6 * ============================================================= 7 * Copyright 2014 Bass Jobsen @bassjobsen 8 * 9 * Licensed under the Apache License, Version 2.0 (the 'License'); 10 * you may not use this file except in compliance with the License. 11 * You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, software 16 * distributed under the License is distributed on an 'AS IS' BASIS, 17 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 * See the License for the specific language governing permissions and 19 * limitations under the License. 20 * ============================================================ */ 21 22 23(function (root, factory) { 24 25 'use strict'; 26 27 // CommonJS module is defined 28 if (typeof module !== 'undefined' && module.exports) { 29 module.exports = factory(require('jquery')); 30 } 31 // AMD module is defined 32 else if (typeof define === 'function' && define.amd) { 33 define(['jquery'], function ($) { 34 return factory($); 35 }); 36 } else { 37 factory(root.jQuery); 38 } 39 40}(this, function ($) { 41 42 'use strict'; 43 // jshint laxcomma: true 44 45 46 /* TYPEAHEAD PUBLIC CLASS DEFINITION 47 * ================================= */ 48 49 var Typeahead = function (element, options) { 50 this.$element = $(element); 51 this.options = $.extend({}, Typeahead.defaults, options); 52 this.matcher = this.options.matcher || this.matcher; 53 this.sorter = this.options.sorter || this.sorter; 54 this.select = this.options.select || this.select; 55 this.autoSelect = typeof this.options.autoSelect == 'boolean' ? this.options.autoSelect : true; 56 this.highlighter = this.options.highlighter || this.highlighter; 57 this.render = this.options.render || this.render; 58 this.updater = this.options.updater || this.updater; 59 this.displayText = this.options.displayText || this.displayText; 60 this.itemLink = this.options.itemLink || this.itemLink; 61 this.itemTitle = this.options.itemTitle || this.itemTitle; 62 this.followLinkOnSelect = this.options.followLinkOnSelect || this.followLinkOnSelect; 63 this.source = this.options.source; 64 this.delay = this.options.delay; 65 this.theme = this.options.theme && this.options.themes && this.options.themes[this.options.theme] || Typeahead.defaults.themes[Typeahead.defaults.theme]; 66 this.$menu = $(this.options.menu || this.theme.menu); 67 this.$appendTo = this.options.appendTo ? $(this.options.appendTo) : null; 68 this.fitToElement = typeof this.options.fitToElement == 'boolean' ? this.options.fitToElement : false; 69 this.shown = false; 70 this.listen(); 71 this.showHintOnFocus = typeof this.options.showHintOnFocus == 'boolean' || this.options.showHintOnFocus === 'all' ? this.options.showHintOnFocus : false; 72 this.afterSelect = this.options.afterSelect; 73 this.afterEmptySelect = this.options.afterEmptySelect; 74 this.addItem = false; 75 this.value = this.$element.val() || this.$element.text(); 76 this.keyPressed = false; 77 this.focused = this.$element.is(':focus'); 78 this.changeInputOnSelect = this.options.changeInputOnSelect || this.changeInputOnSelect; 79 this.changeInputOnMove = this.options.changeInputOnMove || this.changeInputOnMove; 80 this.openLinkInNewTab = this.options.openLinkInNewTab || this.openLinkInNewTab; 81 this.selectOnBlur = this.options.selectOnBlur || this.selectOnBlur; 82 this.showCategoryHeader = this.options.showCategoryHeader || this.showCategoryHeader; 83 }; 84 85 Typeahead.prototype = { 86 87 constructor: Typeahead, 88 89 90 setDefault: function (val) { 91 // var val = this.$menu.find('.active').data('value'); 92 this.$element.data('active', val); 93 if (this.autoSelect || val) { 94 var newVal = this.updater(val); 95 // Updater can be set to any random functions via "options" parameter in constructor above. 96 // Add null check for cases when updater returns void or undefined. 97 if (!newVal) { 98 newVal = ''; 99 } 100 this.$element 101 .val(this.displayText(newVal) || newVal) 102 .text(this.displayText(newVal) || newVal) 103 .change(); 104 this.afterSelect(newVal); 105 } 106 return this.hide(); 107 }, 108 109 select: function () { 110 var val = this.$menu.find('.active').data('value'); 111 112 this.$element.data('active', val); 113 if (this.autoSelect || val) { 114 var newVal = this.updater(val); 115 // Updater can be set to any random functions via "options" parameter in constructor above. 116 // Add null check for cases when updater returns void or undefined. 117 if (!newVal) { 118 newVal = ''; 119 } 120 121 if (this.changeInputOnSelect) { 122 this.$element 123 .val(this.displayText(newVal) || newVal) 124 .text(this.displayText(newVal) || newVal) 125 .change(); 126 } 127 128 if (this.followLinkOnSelect && this.itemLink(val)) { 129 if (this.openLinkInNewTab) { 130 window.open(this.itemLink(val), '_blank'); 131 } else { 132 document.location = this.itemLink(val); 133 } 134 this.afterSelect(newVal); 135 } else if (this.followLinkOnSelect && !this.itemLink(val)) {
136 this.afterEmptySelect(newVal); 137 } else { 138 this.afterSelect(newVal); 139 } 140 } else { 141 this.afterEmptySelect(); 142 } 143 144 return this.hide(); 145 }, 146 147 updater: function (item) { 148 return item; 149 }, 150 151 setSource: function (source) { 152 this.source = source; 153 }, 154 155 show: function () { 156 var pos = $.extend({}, this.$element.position(), { 157 height: this.$element[0].offsetHeight 158 }); 159 160 var scrollHeight = typeof this.options.scrollHeight == 'function' ? 161 this.options.scrollHeight.call() : 162 this.options.scrollHeight; 163 164 var element; 165 if (this.shown) { 166 element = this.$menu; 167 } else if (this.$appendTo) { 168 element = this.$menu.appendTo(this.$appendTo); 169 this.hasSameParent = this.$appendTo.is(this.$element.parent()); 170 } else { 171 element = this.$menu.insertAfter(this.$element); 172 this.hasSameParent = true; 173 } 174 175 if (!this.hasSameParent) { 176 // We cannot rely on the element position, need to position relative to the window 177 element.css('position', 'fixed'); 178 var offset = this.$element.offset(); 179 pos.top = offset.top; 180 pos.left = offset.left; 181 } 182 // The rules for bootstrap are: 'dropup' in the parent and 'dropdown-menu-right' in the element. 183 // Note that to get right alignment, you'll need to specify `menu` in the options to be: 184 // '<ul class="typeahead dropdown-menu" role="listbox"></ul>' 185 var dropup = $(element).parent().hasClass('dropup'); 186 var newTop = dropup ? 'auto' : (pos.top + pos.height + scrollHeight); 187 var right = $(element).hasClass('dropdown-menu-right'); 188 var newLeft = right ? 'auto' : pos.left; 189 // it seems like setting the css is a bad idea (just let Bootstrap do it), but I'll keep the old 190 // logic in place except for the dropup/right-align cases. 191 element.css({ top: newTop, left: newLeft }).show(); 192 193 if (this.options.fitToElement === true) { 194 element.css('width', this.$element.outerWidth() + 'px'); 195 } 196 197 this.shown = true; 198 return this; 199 }, 200 201 hide: function () { 202 this.$menu.hide(); 203 this.shown = false; 204 return this; 205 }, 206 207 lookup: function (query) { 208 if (typeof(query) != 'undefined' && query !== null) { 209 this.query = query; 210 } else { 211 this.query = this.$element.val(); 212 } 213 214 if (this.query.length < this.options.minLength && !this.options.showHintOnFocus) { 215 return this.shown ? this.hide() : this; 216 } 217 218 var worker = $.proxy(function () { 219 220 // Bloodhound (since 0.11) needs three arguments. 221 // Two of them are callback functions (sync and async) for local and remote data processing 222 // see https://github.com/twitter/typeahead.js/blob/master/src/bloodhound/bloodhound.js#L132 223 if ($.isFunction(this.source) && this.source.length === 3) { 224 this.source(this.query, $.proxy(this.process, this), $.proxy(this.process, this)); 225 } else if ($.isFunction(this.source)) { 226 this.source(this.query, $.proxy(this.process, this)); 227 } else if (this.source) { 228 this.process(this.source); 229 } 230 }, this); 231 232 clearTimeout(this.lookupWorker); 233 this.lookupWorker = setTimeout(worker, this.delay); 234 }, 235 236 process: function (items) { 237 var that = this; 238 239 items = $.grep(items, function (item) { 240 return that.matcher(item); 241 }); 242 243 items = this.sorter(items); 244 245 if (!items.length && !this.options.addItem) { 246 return this.shown ? this.hide() : this; 247 } 248 249 if (items.length > 0) { 250 this.$element.data('active', items[0]); 251 } else { 252 this.$element.data('active', null); 253 } 254 255 if (this.options.items != 'all') { 256 items = items.slice(0, this.options.items); 257 } 258 259 // Add item 260 if (this.options.addItem) { 261 items.push(this.options.addItem); 262 } 263 264 return this.render(items).show(); 265 }, 266 267 matcher: function (item) { 268 // console.log(item); 269 var it = this.displayText(item); 270 return ~it.toLowerCase().indexOf(this.query.toLowerCase()); 271 }, 272 273 sorter: function (items) { 274 var beginswith = []; 275 var caseSensitive = []; 276 var caseInsensitive = []; 277 var item; 278 279 while ((item = items.shift())) { 280 var it = this.displayText(item); 281 if (!it.toLowerCase().indexOf(this.query.toLowerCase())) { 282 beginswith.push(item); 283 } else if (~it.indexOf(this.query)) { 284 caseSensitive.push(item); 285 } else { 286 caseInsensitive.push(item); 287 } 288 } 289 290 return beginswith.concat(caseSensitive, caseInsensitive); 291 }, 292 293 highlighter: function (item) { 294 var text = this.query; 295 if (text === '') { 296 return item; 297 } 298 var matches = item.match(/(>)([^<]*)(<)/g); 299 var first = []; 300 var second = []; 301 var i; 302 if (matches && matches.length) { 303 // html 304 for (i = 0; i < matches.length; ++i) { 305 if (matches[i].length > 2) {// escape '><' 306 first.push(matches[i]); 307 } 308 } 309 } else { 310 // text 311 first = []; 312 first.push(item); 313 } 314 text = text.replace((/[\(\)\/\.\*\+\?\[\]]/g), function (mat) { 315 return '\\' + mat; 316 }); 317 var reg = new RegExp(text, 'g'); 318 var m; 319 for (i = 0; i < first.length; ++i) { 320 m = first[i].match(reg); 321 if (m && m.length > 0) {// find all text nodes matches 322 second.push(first[i]); 323 } 324 } 325 for (i = 0; i < second.length; ++i) { 326 item = item.replace(second[i], second[i].replace(reg, '<strong>$&</strong>')); 327 } 328 return item; 329 }, 330 331 render: function (items) { 332 var that = this; 333 var self = this; 334 var activeFound = false;
335 var data = []; 336 var _category = that.options.separator; 337 338 $.each(items, function (key, value) { 339 // inject separator 340 if (key > 0 && value[_category] !== items[key - 1][_category]) { 341 data.push({ 342 __type: 'divider' 343 }); 344 } 345 346 if (this.showCategoryHeader) { 347 // inject category header 348 if (value[_category] && (key === 0 || value[_category] !== items[key - 1][_category])) { 349 data.push({ 350 __type: 'category', 351 name: value[_category] 352 }); 353 } 354 } 355 356 data.push(value); 357 }); 358 359 items = $(data).map(function (i, item) { 360 if ((item.__type || false) == 'category'){ 361 return $(that.options.headerHtml || that.theme.headerHtml).text(item.name)[0]; 362 } 363 364 if ((item.__type || false) == 'divider'){ 365 return $(that.options.headerDivider || that.theme.headerDivider)[0]; 366 } 367 368 var text = self.displayText(item); 369 i = $(that.options.item || that.theme.item).data('value', item); 370 i.find(that.options.itemContentSelector || that.theme.itemContentSelector) 371 .addBack(that.options.itemContentSelector || that.theme.itemContentSelector) 372 .html(that.highlighter(text, item)); 373 if(that.options.followLinkOnSelect) { 374 i.find('a').attr('href', self.itemLink(item)); 375 } 376 i.find('a').attr('title', self.itemTitle(item)); 377 if (text == self.$element.val()) { 378 i.addClass('active'); 379 self.$element.data('active', item); 380 activeFound = true; 381 } 382 return i[0]; 383 }); 384 385 if (this.autoSelect && !activeFound) { 386 items.filter(':not(.dropdown-header)').first().addClass('active'); 387 this.$element.data('active', items.first().data('value')); 388 } 389 this.$menu.html(items); 390 return this; 391 }, 392 393 displayText: function (item) { 394 return typeof item !== 'undefined' && typeof item.name != 'undefined' ? item.name : item; 395 }, 396 397 itemLink: function (item) { 398 return null; 399 }, 400 401 itemTitle: function (item) { 402 return null; 403 }, 404 405 next: function (event) { 406 var active = this.$menu.find('.active').removeClass('active'); 407 var next = active.next(); 408 409 if (!next.length) { 410 next = $(this.$menu.find($(this.options.item || this.theme.item).prop('tagName'))[0]); 411 } 412 413 while (next.hasClass('divider') || next.hasClass('dropdown-header')) { 414 next = next.next(); 415 } 416 417 next.addClass('active'); 418 // added for screen reader 419 var newVal = this.updater(next.data('value')); 420 if (this.changeInputOnMove) { 421 this.$element.val(this.displayText(newVal) || newVal); 422 } 423 }, 424 425 prev: function (event) { 426 var active = this.$menu.find('.active').removeClass('active'); 427 var prev = active.prev(); 428 429 if (!prev.length) { 430 prev = this.$menu.find($(this.options.item || this.theme.item).prop('tagName')).last(); 431 } 432 433 while (prev.hasClass('divider') || prev.hasClass('dropdown-header')) { 434 prev = prev.prev(); 435 } 436 437 prev.addClass('active'); 438 // added for screen reader 439 var newVal = this.updater(prev.data('value')); 440 if (this.changeInputOnMove) { 441 this.$element.val(this.displayText(newVal) || newVal); 442 } 443 }, 444 445 listen: function () { 446 this.$element 447 .on('focus.bootstrap3Typeahead', $.proxy(this.focus, this)) 448 .on('blur.bootstrap3Typeahead', $.proxy(this.blur, this)) 449 .on('keypress.bootstrap3Typeahead', $.proxy(this.keypress, this)) 450 .on('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead', $.proxy(this.input, this)) 451 .on('keyup.bootstrap3Typeahead', $.proxy(this.keyup, this)); 452 453 if (this.eventSupported('keydown')) { 454 this.$element.on('keydown.bootstrap3Typeahead', $.proxy(this.keydown, this)); 455 } 456 457 var itemTagName = $(this.options.item || this.theme.item).prop('tagName'); 458 if ('ontouchstart' in document.documentElement && 'onmousemove' in document.documentElement) { 459 this.$menu 460 .on('touchstart', itemTagName, $.proxy(this.touchstart, this)) 461 .on('touchend', itemTagName, $.proxy(this.click, this)) 462 .on('click', $.proxy(this.click, this)) 463 .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this)) 464 .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this)) 465 .on('mousedown', $.proxy(this.mousedown,this)); 466 } else if ('ontouchstart' in document.documentElement) { 467 this.$menu 468 .on('touchstart', itemTagName, $.proxy(this.touchstart, this)) 469 .on('touchend', itemTagName, $.proxy(this.click, this)); 470 } else { 471 this.$menu 472 .on('click', $.proxy(this.click, this)) 473 .on('mouseenter', itemTagName, $.proxy(this.mouseenter, this)) 474 .on('mouseleave', itemTagName, $.proxy(this.mouseleave, this)) 475 .on('mousedown', $.proxy(this.mousedown, this)); 476 } 477 }, 478 479 destroy: function () { 480 this.$element.data('typeahead', null); 481 this.$element.data('active', null); 482 this.$element 483 .unbind('focus.bootstrap3Typeahead') 484 .unbind('blur.bootstrap3Typeahead') 485 .unbind('keypress.bootstrap3Typeahead') 486 .unbind('propertychange.bootstrap3Typeahead input.bootstrap3Typeahead') 487 .unbind('keyup.bootstrap3Typeahead'); 488 489 if (this.eventSupported('keydown')) { 490 this.$element.unbind('keydown.bootstrap3-typeahead'); 491 } 492 493 this.$menu.remove(); 494 this.destroyed = true; 495 }, 496 497 eventSupported: function (eventName) { 498 var isSupported = eventName in this.$element; 499 if (!isSupported) { 500 this.$element.setAttribute(eventName, 'return;'); 501 isSupported = typeof this.$element[eventName] === 'function'; 502 } 503 return isSupported; 504 }, 505 506 move: function (e) { 507 if (!this.shown) { 508 return; 509 } 510 511 switch (e.keyCode) { 512 case 9: // tab 513 case 13: // enter 514 case 27: // escape 515 e.preventDefault(); 516 break; 517 518 case 38: // up arrow 519 // with the shiftKey (this is actually the left parenthesis) 520 if (e.shiftKey) { 521 return; 522 } 523 e.preventDefault(); 524 this.prev(); 525 break; 526 527 case 40: // down arrow 528 // with the shiftKey (this is actually the right parenthesis) 529 if (e.shiftKey) { 530 return; 531 } 532 e.preventDefault(); 533 this.next(); 534 break; 535 } 536 }, 537 538 keydown: function (e) { 539 /** 540 * Prevent to make an ajax call while copying and pasting. 541 * 542 * @author Simone Sacchi 543 * @version 2018/01/18 544 */ 545 if (e.keyCode === 17) { // ctrl 546 return; 547 } 548 this.keyPressed = true; 549 this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40, 38, 9, 13, 27]); 550 if (!this.shown && e.keyCode == 40) { 551 this.lookup(); 552 } else { 553 this.move(e); 554 } 555 }, 556 557 keypress: function (e) { 558 if (this.suppressKeyPressRepeat) { 559 return; 560 } 561 this.move(e); 562 }, 563 564 input: function (e) { 565 // This is a fixed for IE10/11 that fires the input event when a placehoder is changed 566 // (https://connect.microsoft.com/IE/feedback/details/810538/ie-11-fires-input-event-on-focus) 567 var currentValue = this.$element.val() || this.$element.text(); 568 if (this.value !== currentValue) { 569 this.value = currentValue; 570 this.lookup(); 571 } 572 }, 573 574 keyup: function (e) { 575 if (this.destroyed) { 576 return; 577 } 578 switch (e.keyCode) { 579 case 40: // down arrow 580 case 38: // up arrow 581 case 16: // shift 582 case 17: // ctrl 583 case 18: // alt 584 break; 585 586 case 9: // tab 587 if (!this.shown || (this.showHintOnFocus && !this.keyPressed)) { 588 return; 589 } 590 this.select(); 591 break; 592 case 13: // enter 593 if (!this.shown) { 594 return; 595 } 596 this.select(); 597 break; 598 599 case 27: // escape 600 if (!this.shown) { 601 return; 602 } 603 this.hide(); 604 break; 605 } 606 607 }, 608 609 focus: function (e) { 610 if (!this.focused) { 611 this.focused = true; 612 this.keyPressed = false;
613 if (this.options.showHintOnFocus && this.skipShowHintOnFocus !== true) { 614 if (this.options.showHintOnFocus === 'all') { 615 this.lookup(''); 616 } else { 617 this.lookup(); 618 } 619 } 620 } 621 if (this.skipShowHintOnFocus) { 622 this.skipShowHintOnFocus = false; 623 } 624 }, 625 626 blur: function (e) { 627 if (!this.mousedover && !this.mouseddown && this.shown) { 628 if (this.selectOnBlur) { 629 this.select(); 630 } 631 this.hide(); 632 this.focused = false; 633 this.keyPressed = false; 634 } else if (this.mouseddown) { 635 // This is for IE that blurs the input when user clicks on scroll. 636 // We set the focus back on the input and prevent the lookup to occur again 637 this.skipShowHintOnFocus = true; 638 this.$element.focus(); 639 this.mouseddown = false; 640 } 641 }, 642 643 click: function (e) { 644 e.preventDefault(); 645 this.skipShowHintOnFocus = true; 646 this.select(); 647 this.$element.focus(); 648 this.hide(); 649 }, 650 651 mouseenter: function (e) { 652 this.mousedover = true; 653 this.$menu.find('.active').removeClass('active'); 654 $(e.currentTarget).addClass('active'); 655 }, 656 657 mouseleave: function (e) { 658 this.mousedover = false; 659 if (!this.focused && this.shown) { 660 this.hide(); 661 } 662 }, 663 664 /** 665 * We track the mousedown for IE. When clicking on the menu scrollbar, IE makes the input blur thus hiding the menu. 666 */ 667 mousedown: function (e) { 668 this.mouseddown = true; 669 this.$menu.one('mouseup', function (e) { 670 // IE won't fire this, but FF and Chrome will so we reset our flag for them here 671 this.mouseddown = false; 672 }.bind(this)); 673 }, 674 675 touchstart: function (e) { 676 e.preventDefault(); 677 this.$menu.find('.active').removeClass('active'); 678 $(e.currentTarget).addClass('active'); 679 }, 680 681 touchend: function (e) { 682 e.preventDefault(); 683 this.select(); 684 this.$element.focus(); 685 } 686 687 }; 688 689 690 /* TYPEAHEAD PLUGIN DEFINITION 691 * =========================== */ 692 693 var old = $.fn.typeahead; 694 695 $.fn.typeahead = function (option) { 696 var arg = arguments; 697 if (typeof option == 'string' && option == 'getActive') { 698 return this.data('active'); 699 } 700 return this.each(function () { 701 var $this = $(this); 702 var data = $this.data('typeahead'); 703 var options = typeof option == 'object' && option; 704 if (!data) { 705 $this.data('typeahead', (data = new Typeahead(this, options))); 706 } 707 if (typeof option == 'string' && data[option]) { 708 if (arg.length > 1) { 709 data[option].apply(data, Array.prototype.slice.call(arg, 1)); 710 } else { 711 data[option](); 712 } 713 } 714 }); 715 }; 716 717 Typeahead.defaults = { 718 source: [], 719 items: 8, 720 minLength: 1, 721 scrollHeight: 0, 722 autoSelect: true, 723 afterSelect: $.noop, 724 afterEmptySelect: $.noop, 725 addItem: false, 726 followLinkOnSelect: false, 727 delay: 0, 728 separator: 'category', 729 changeInputOnSelect: true, 730 changeInputOnMove: true, 731 openLinkInNewTab: false, 732 selectOnBlur: true, 733 showCategoryHeader: true, 734 theme: "bootstrap3", 735 themes: { 736 bootstrap3: { 737 menu: '<ul class="typeahead dropdown-menu" role="listbox"></ul>', 738 item: '<li><a class="dropdown-item" href="#" role="option"></a></li>', 739 itemContentSelector: "a", 740 headerHtml: '<li class="dropdown-header"></li>', 741 headerDivider: '<li class="divider" role="separator"></li>' 742 }, 743 bootstrap4: { 744 menu: '<div class="typeahead dropdown-menu" role="listbox"></div>', 745 item: '<button class="dropdown-item" role="option"></button>', 746 itemContentSelector: '.dropdown-item', 747 headerHtml: '<h6 class="dropdown-header"></h6>', 748 headerDivider: '<div class="dropdown-divider"></div>' 749 } 750 } 751}; 752 753 $.fn.typeahead.Constructor = Typeahead; 754 755 /* TYPEAHEAD NO CONFLICT 756 * =================== */ 757 758 $.fn.typeahead.noConflict = function () { 759 $.fn.typeahead = old; 760 return this; 761 }; 762 763 764 /* TYPEAHEAD DATA-API 765 * ================== */ 766
767 $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) { 768 var $this = $(this); 769 if ($this.data('typeahead')) { 770 return; 771 } 772 $this.typeahead($this.data()); 773 }); 774 775}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.