33e3fb183625feb01e02e0f2c9c3b06f99c539f6f5f2e114c8098b827c926706ac73ef3e1b95375c50d6cbf9222c409b6cf845a5fed68bb8c8876b44b3a1cb 7.3 KB

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