scrollbars.min.js (7417B)
1 import { addClass, elt, rmClass } from "../util/dom.js" 2 import { on } from "../util/event.js" 3 import { scrollGap, paddingVert } from "../measurement/position_measurement.js" 4 import { ie, ie_version, mac, mac_geMountainLion } from "../util/browser.js" 5 import { updateHeightsInViewport } from "./update_lines.js" 6 import { Delayed } from "../util/misc.js" 7 8 import { setScrollLeft, updateScrollTop } from "./scrolling.js" 9 10 // SCROLLBARS 11 12 // Prepare DOM reads needed to update the scrollbars. Done in one 13 // shot to minimize update/measure roundtrips. 14 export function measureForScrollbars(cm) { 15 let d = cm.display, gutterW = d.gutters.offsetWidth 16 let docH = Math.round(cm.doc.height + paddingVert(cm.display)) 17 return { 18 clientHeight: d.scroller.clientHeight, 19 viewHeight: d.wrapper.clientHeight, 20 scrollWidth: d.scroller.scrollWidth, clientWidth: d.scroller.clientWidth, 21 viewWidth: d.wrapper.clientWidth, 22 barLeft: cm.options.fixedGutter ? gutterW : 0, 23 docHeight: docH, 24 scrollHeight: docH + scrollGap(cm) + d.barHeight, 25 nativeBarWidth: d.nativeBarWidth, 26 gutterWidth: gutterW 27 } 28 } 29 30 class NativeScrollbars { 31 constructor(place, scroll, cm) { 32 this.cm = cm 33 let vert = this.vert = elt("div", [elt("div", null, null, "min-width: 1px")], "CodeMirror-vscrollbar") 34 let horiz = this.horiz = elt("div", [elt("div", null, null, "height: 100%; min-height: 1px")], "CodeMirror-hscrollbar") 35 place(vert); place(horiz) 36 37 on(vert, "scroll", () => { 38 if (vert.clientHeight) scroll(vert.scrollTop, "vertical") 39 }) 40 on(horiz, "scroll", () => { 41 if (horiz.clientWidth) scroll(horiz.scrollLeft, "horizontal") 42 }) 43 44 this.checkedZeroWidth = false 45 // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). 46 if (ie && ie_version < 8) this.horiz.style.minHeight = this.vert.style.minWidth = "18px" 47 } 48 49 update(measure) { 50 let needsH = measure.scrollWidth > measure.clientWidth + 1 51 let needsV = measure.scrollHeight > measure.clientHeight + 1 52 let sWidth = measure.nativeBarWidth 53 54 if (needsV) { 55 this.vert.style.display = "block" 56 this.vert.style.bottom = needsH ? sWidth + "px" : "0" 57 let totalHeight = measure.viewHeight - (needsH ? sWidth : 0) 58 // A bug in IE8 can cause this value to be negative, so guard it. 59 this.vert.firstChild.style.height = 60 Math.max(0, measure.scrollHeight - measure.clientHeight + totalHeight) + "px" 61 } else { 62 this.vert.style.display = "" 63 this.vert.firstChild.style.height = "0" 64 } 65 66 if (needsH) { 67 this.horiz.style.display = "block" 68 this.horiz.style.right = needsV ? sWidth + "px" : "0" 69 this.horiz.style.left = measure.barLeft + "px" 70 let totalWidth = measure.viewWidth - measure.barLeft - (needsV ? sWidth : 0) 71 this.horiz.firstChild.style.width = 72 Math.max(0, measure.scrollWidth - measure.clientWidth + totalWidth) + "px" 73 } else { 74 this.horiz.style.display = "" 75 this.horiz.firstChild.style.width = "0" 76 } 77 78 if (!this.checkedZeroWidth && measure.clientHeight > 0) { 79 if (sWidth == 0) this.zeroWidthHack() 80 this.checkedZeroWidth = true 81 } 82 83 return {right: needsV ? sWidth : 0, bottom: needsH ? sWidth : 0} 84 } 85 86 setScrollLeft(pos) { 87 if (this.horiz.scrollLeft != pos) this.horiz.scrollLeft = pos 88 if (this.disableHoriz) this.enableZeroWidthBar(this.horiz, this.disableHoriz, "horiz") 89 } 90 91 setScrollTop(pos) { 92 if (this.vert.scrollTop != pos) this.vert.scrollTop = pos 93 if (this.disableVert) this.enableZeroWidthBar(this.vert, this.disableVert, "vert") 94 } 95 96 zeroWidthHack() { 97 let w = mac && !mac_geMountainLion ? "12px" : "18px" 98 this.horiz.style.height = this.vert.style.width = w 99 this.horiz.style.pointerEvents = this.vert.style.pointerEvents = "none" 100 this.disableHoriz = new Delayed 101 this.disableVert = new Delayed 102 } 103 104 enableZeroWidthBar(bar, delay, type) { 105 bar.style.pointerEvents = "auto" 106 function maybeDisable() { 107 // To find out whether the scrollbar is still visible, we 108 // check whether the element under the pixel in the bottom 109 // right corner of the scrollbar box is the scrollbar box 110 // itself (when the bar is still visible) or its filler child 111 // (when the bar is hidden). If it is still visible, we keep 112 // it enabled, if it's hidden, we disable pointer events. 113 let box = bar.getBoundingClientRect() 114 let elt = type == "vert" ? document.elementFromPoint(box.right - 1, (box.top + box.bottom) / 2) 115 : document.elementFromPoint((box.right + box.left) / 2, box.bottom - 1) 116 if (elt != bar) bar.style.pointerEvents = "none" 117 else delay.set(1000, maybeDisable) 118 } 119 delay.set(1000, maybeDisable) 120 } 121 122 clear() { 123 let parent = this.horiz.parentNode 124 parent.removeChild(this.horiz) 125 parent.removeChild(this.vert) 126 } 127 } 128 129 class NullScrollbars { 130 update() { return {bottom: 0, right: 0} } 131 setScrollLeft() {} 132 setScrollTop() {} 133 clear() {} 134 } 135 136 export function updateScrollbars(cm, measure) { 137 if (!measure) measure = measureForScrollbars(cm) 138 let startWidth = cm.display.barWidth, startHeight = cm.display.barHeight 139 updateScrollbarsInner(cm, measure) 140 for (let i = 0; i < 4 && startWidth != cm.display.barWidth || startHeight != cm.display.barHeight; i++) { 141 if (startWidth != cm.display.barWidth && cm.options.lineWrapping) 142 updateHeightsInViewport(cm) 143 updateScrollbarsInner(cm, measureForScrollbars(cm)) 144 startWidth = cm.display.barWidth; startHeight = cm.display.barHeight 145 } 146 } 147 148 // Re-synchronize the fake scrollbars with the actual size of the 149 // content. 150 function updateScrollbarsInner(cm, measure) { 151 let d = cm.display 152 let sizes = d.scrollbars.update(measure) 153 154 d.sizer.style.paddingRight = (d.barWidth = sizes.right) + "px" 155 d.sizer.style.paddingBottom = (d.barHeight = sizes.bottom) + "px" 156 d.heightForcer.style.borderBottom = sizes.bottom + "px solid transparent" 157 158 if (sizes.right && sizes.bottom) { 159 d.scrollbarFiller.style.display = "block" 160 d.scrollbarFiller.style.height = sizes.bottom + "px" 161 d.scrollbarFiller.style.width = sizes.right + "px" 162 } else d.scrollbarFiller.style.display = "" 163 if (sizes.bottom && cm.options.coverGutterNextToScrollbar && cm.options.fixedGutter) { 164 d.gutterFiller.style.display = "block" 165 d.gutterFiller.style.height = sizes.bottom + "px" 166 d.gutterFiller.style.width = measure.gutterWidth + "px" 167 } else d.gutterFiller.style.display = "" 168 } 169 170 export let scrollbarModel = {"native": NativeScrollbars, "null": NullScrollbars} 171 172 export function initScrollbars(cm) { 173 if (cm.display.scrollbars) { 174 cm.display.scrollbars.clear() 175 if (cm.display.scrollbars.addClass) 176 rmClass(cm.display.wrapper, cm.display.scrollbars.addClass) 177 } 178 179 cm.display.scrollbars = new scrollbarModel[cm.options.scrollbarStyle](node => { 180 cm.display.wrapper.insertBefore(node, cm.display.scrollbarFiller) 181 // Prevent clicks in the scrollbars from killing focus 182 on(node, "mousedown", () => { 183 if (cm.state.focused) setTimeout(() => cm.display.input.focus(), 0) 184 }) 185 node.setAttribute("cm-not-content", "true") 186 }, (pos, axis) => { 187 if (axis == "horizontal") setScrollLeft(cm, pos) 188 else updateScrollTop(cm, pos) 189 }, cm) 190 if (cm.display.scrollbars.addClass) 191 addClass(cm.display.wrapper, cm.display.scrollbars.addClass) 192 }