calcite-graph_2.cjs.entry.js 59 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.97
  5. */
  6. 'use strict';
  7. Object.defineProperty(exports, '__esModule', { value: true });
  8. const index = require('./index-a0010f96.js');
  9. const guid = require('./guid-f4f03a7a.js');
  10. const observers = require('./observers-5706326b.js');
  11. const dom = require('./dom-2ec8c9ed.js');
  12. const math = require('./math-f71ad83a.js');
  13. const label = require('./label-28060b83.js');
  14. const form = require('./form-1d831023.js');
  15. const interactive = require('./interactive-32293bca.js');
  16. const key = require('./key-6a462411.js');
  17. const locale = require('./locale-678ce361.js');
  18. require('./resources-b5a5f8a7.js');
  19. /**
  20. * Calculate slope of the tangents
  21. * uses Steffen interpolation as it's monotonic
  22. * http://jrwalsh1.github.io/posts/interpolations/
  23. *
  24. * @param p0
  25. * @param p1
  26. * @param p2
  27. */
  28. function slope(p0, p1, p2) {
  29. const dx = p1[0] - p0[0];
  30. const dx1 = p2[0] - p1[0];
  31. const dy = p1[1] - p0[1];
  32. const dy1 = p2[1] - p1[1];
  33. const m = dy / (dx || (dx1 < 0 && 0));
  34. const m1 = dy1 / (dx1 || (dx < 0 && 0));
  35. const p = (m * dx1 + m1 * dx) / (dx + dx1);
  36. return (Math.sign(m) + Math.sign(m1)) * Math.min(Math.abs(m), Math.abs(m1), 0.5 * Math.abs(p)) || 0;
  37. }
  38. /**
  39. * Calculate slope for just one tangent (single-sided)
  40. *
  41. * @param p0
  42. * @param p1
  43. * @param m
  44. */
  45. function slopeSingle(p0, p1, m) {
  46. const dx = p1[0] - p0[0];
  47. const dy = p1[1] - p0[1];
  48. return dx ? ((3 * dy) / dx - m) / 2 : m;
  49. }
  50. /**
  51. * Given two points and their tangent slopes,
  52. * calculate the bezier handle coordinates and return draw command.
  53. *
  54. * Translates Hermite Spline to Beziér curve:
  55. * stackoverflow.com/questions/42574940/
  56. *
  57. * @param p0
  58. * @param p1
  59. * @param m0
  60. * @param m1
  61. * @param t
  62. */
  63. function bezier(p0, p1, m0, m1, t) {
  64. const [x0, y0] = p0;
  65. const [x1, y1] = p1;
  66. const dx = (x1 - x0) / 3;
  67. const h1 = t([x0 + dx, y0 + dx * m0]).join(",");
  68. const h2 = t([x1 - dx, y1 - dx * m1]).join(",");
  69. const p = t([x1, y1]).join(",");
  70. return `C ${h1} ${h2} ${p}`;
  71. }
  72. /**
  73. * Generate a function which will translate a point
  74. * from the data coordinate space to svg viewbox oriented pixels
  75. *
  76. * @param root0
  77. * @param root0.width
  78. * @param root0.height
  79. * @param root0.min
  80. * @param root0.max
  81. */
  82. function translate({ width, height, min, max }) {
  83. const rangeX = max[0] - min[0];
  84. const rangeY = max[1] - min[1];
  85. return (point) => {
  86. const x = ((point[0] - min[0]) / rangeX) * width;
  87. const y = height - (point[1] / rangeY) * height;
  88. return [x, y];
  89. };
  90. }
  91. /**
  92. * Get the min and max values from the dataset
  93. *
  94. * @param data
  95. */
  96. function range(data) {
  97. const [startX, startY] = data[0];
  98. const min = [startX, startY];
  99. const max = [startX, startY];
  100. return data.reduce(({ min, max }, [x, y]) => ({
  101. min: [Math.min(min[0], x), Math.min(min[1], y)],
  102. max: [Math.max(max[0], x), Math.max(max[1], y)]
  103. }), { min, max });
  104. }
  105. /**
  106. * Generate drawing commands for an area graph
  107. * returns a string can can be passed directly to a path element's `d` attribute
  108. *
  109. * @param root0
  110. * @param root0.data
  111. * @param root0.min
  112. * @param root0.max
  113. * @param root0.t
  114. */
  115. function area({ data, min, max, t }) {
  116. if (data.length === 0) {
  117. return "";
  118. }
  119. // important points for beginning and ending the path
  120. const [startX, startY] = t(data[0]);
  121. const [minX, minY] = t(min);
  122. const [maxX] = t(max);
  123. // keep track of previous slope/points
  124. let m;
  125. let p0;
  126. let p1;
  127. // iterate over data points, calculating command for each
  128. const commands = data.reduce((acc, point, i) => {
  129. p0 = data[i - 2];
  130. p1 = data[i - 1];
  131. if (i > 1) {
  132. const m1 = slope(p0, p1, point);
  133. const m0 = m === undefined ? slopeSingle(p0, p1, m1) : m;
  134. const command = bezier(p0, p1, m0, m1, t);
  135. m = m1;
  136. return `${acc} ${command}`;
  137. }
  138. return acc;
  139. }, `M ${minX},${minY} L ${minX},${startY} L ${startX},${startY}`);
  140. // close the path
  141. const last = data[data.length - 1];
  142. const end = bezier(p1, last, m, slopeSingle(p1, last, m), t);
  143. return `${commands} ${end} L ${maxX},${minY} Z`;
  144. }
  145. const graphCss = "@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing)}:host([hidden]){display:none}:host{display:block;block-size:100%}.svg{fill:currentColor;stroke:transparent;margin:0px;display:block;block-size:100%;inline-size:100%;padding:0px}.svg .graph-path--highlight{fill:var(--calcite-ui-brand);opacity:0.5}";
  146. const Graph = class {
  147. constructor(hostRef) {
  148. index.registerInstance(this, hostRef);
  149. //--------------------------------------------------------------------------
  150. //
  151. // Properties
  152. //
  153. //--------------------------------------------------------------------------
  154. /**
  155. * Array of tuples describing a single data point ([x, y])
  156. * These data points should be sorted by x-axis value
  157. */
  158. this.data = [];
  159. //--------------------------------------------------------------------------
  160. //
  161. // Private State/Props
  162. //
  163. //--------------------------------------------------------------------------
  164. this.graphId = `calcite-graph-${guid.guid()}`;
  165. this.resizeObserver = observers.createObserver("resize", () => index.forceUpdate(this));
  166. }
  167. //--------------------------------------------------------------------------
  168. //
  169. // Lifecycle
  170. //
  171. //--------------------------------------------------------------------------
  172. connectedCallback() {
  173. var _a;
  174. (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.observe(this.el);
  175. }
  176. disconnectedCallback() {
  177. var _a;
  178. (_a = this.resizeObserver) === null || _a === void 0 ? void 0 : _a.disconnect();
  179. }
  180. render() {
  181. const { data, colorStops, el, highlightMax, highlightMin, min, max } = this;
  182. const id = this.graphId;
  183. const { clientHeight: height, clientWidth: width } = el;
  184. // if we have no data, return empty svg
  185. if (!data || data.length === 0) {
  186. return (index.h("svg", { class: "svg", height: height, preserveAspectRatio: "none", viewBox: `0 0 ${width} ${height}`, width: width }));
  187. }
  188. const { min: rangeMin, max: rangeMax } = range(data);
  189. let currentMin = rangeMin;
  190. let currentMax = rangeMax;
  191. if (min < rangeMin[0] || min > rangeMin[0]) {
  192. currentMin = [min, 0];
  193. }
  194. if (max > rangeMax[0] || max < rangeMax[0]) {
  195. currentMax = [max, rangeMax[1]];
  196. }
  197. const t = translate({ min: currentMin, max: currentMax, width, height });
  198. const [hMinX] = t([highlightMin, currentMax[1]]);
  199. const [hMaxX] = t([highlightMax, currentMax[1]]);
  200. const areaPath = area({ data, min: rangeMin, max: rangeMax, t });
  201. const fill = colorStops ? `url(#linear-gradient-${id})` : undefined;
  202. return (index.h("svg", { class: "svg", height: height, preserveAspectRatio: "none", viewBox: `0 0 ${width} ${height}`, width: width }, colorStops ? (index.h("defs", null, index.h("linearGradient", { id: `linear-gradient-${id}`, x1: "0", x2: "1", y1: "0", y2: "0" }, colorStops.map(({ offset, color, opacity }) => (index.h("stop", { offset: `${offset * 100}%`, "stop-color": color, "stop-opacity": opacity })))))) : null, highlightMin !== undefined ? ([
  203. index.h("mask", { height: "100%", id: `${id}1`, width: "100%", x: "0%", y: "0%" }, index.h("path", { d: `
  204. M 0,0
  205. L ${hMinX - 1},0
  206. L ${hMinX - 1},${height}
  207. L 0,${height}
  208. Z
  209. `, fill: "white" })),
  210. index.h("mask", { height: "100%", id: `${id}2`, width: "100%", x: "0%", y: "0%" }, index.h("path", { d: `
  211. M ${hMinX + 1},0
  212. L ${hMaxX - 1},0
  213. L ${hMaxX - 1},${height}
  214. L ${hMinX + 1}, ${height}
  215. Z
  216. `, fill: "white" })),
  217. index.h("mask", { height: "100%", id: `${id}3`, width: "100%", x: "0%", y: "0%" }, index.h("path", { d: `
  218. M ${hMaxX + 1},0
  219. L ${width},0
  220. L ${width},${height}
  221. L ${hMaxX + 1}, ${height}
  222. Z
  223. `, fill: "white" })),
  224. index.h("path", { class: "graph-path", d: areaPath, fill: fill, mask: `url(#${id}1)` }),
  225. index.h("path", { class: "graph-path--highlight", d: areaPath, fill: fill, mask: `url(#${id}2)` }),
  226. index.h("path", { class: "graph-path", d: areaPath, fill: fill, mask: `url(#${id}3)` })
  227. ]) : (index.h("path", { class: "graph-path", d: areaPath, fill: fill }))));
  228. }
  229. get el() { return index.getElement(this); }
  230. };
  231. Graph.style = graphCss;
  232. const CSS = {
  233. handleLabel: "handle__label",
  234. handleLabelMinValue: "handle__label--minValue",
  235. handleLabelValue: "handle__label--value",
  236. tickMin: "tick__label--min",
  237. tickMax: "tick__label--max"
  238. };
  239. const sliderCss = "@charset \"UTF-8\";@keyframes in{0%{opacity:0}100%{opacity:1}}@keyframes in-down{0%{opacity:0;transform:translate3D(0, -5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-up{0%{opacity:0;transform:translate3D(0, 5px, 0)}100%{opacity:1;transform:translate3D(0, 0, 0)}}@keyframes in-scale{0%{opacity:0;transform:scale3D(0.95, 0.95, 1)}100%{opacity:1;transform:scale3D(1, 1, 1)}}:root{--calcite-animation-timing:calc(150ms * var(--calcite-internal-duration-factor));--calcite-internal-duration-factor:var(--calcite-duration-factor, 1);--calcite-internal-animation-timing-fast:calc(100ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-medium:calc(200ms * var(--calcite-internal-duration-factor));--calcite-internal-animation-timing-slow:calc(300ms * var(--calcite-internal-duration-factor))}.calcite-animate{opacity:0;animation-fill-mode:both;animation-duration:var(--calcite-animation-timing)}.calcite-animate__in{animation-name:in}.calcite-animate__in-down{animation-name:in-down}.calcite-animate__in-up{animation-name:in-up}.calcite-animate__in-scale{animation-name:in-scale}@media (prefers-reduced-motion: reduce){:root{--calcite-internal-duration-factor:0.01}}:root{--calcite-floating-ui-transition:var(--calcite-animation-timing)}:host([hidden]){display:none}:host([disabled]){pointer-events:none;cursor:default;-webkit-user-select:none;user-select:none;opacity:var(--calcite-ui-opacity-disabled)}.scale--s{--calcite-slider-handle-size:10px;--calcite-slider-handle-extension-height:6.5px;--calcite-slider-container-font-size:var(--calcite-font-size--3)}.scale--s .handle__label,.scale--s .tick__label{line-height:.75rem}.scale--m{--calcite-slider-handle-size:14px;--calcite-slider-handle-extension-height:8px;--calcite-slider-container-font-size:var(--calcite-font-size--2)}.scale--m .handle__label,.scale--m .tick__label{line-height:1rem}.scale--l{--calcite-slider-handle-size:16px;--calcite-slider-handle-extension-height:10.5px;--calcite-slider-container-font-size:var(--calcite-font-size--1)}.scale--l .handle__label,.scale--l .tick__label{line-height:1rem}.handle__label,.tick__label{font-weight:var(--calcite-font-weight-medium);color:var(--calcite-ui-text-2);font-size:var(--calcite-slider-container-font-size)}:host{display:block}.container{position:relative;display:block;overflow-wrap:normal;word-break:normal;padding-inline:calc(var(--calcite-slider-handle-size) * 0.5);padding-block:calc(var(--calcite-slider-handle-size) * 0.5);margin-block:calc(var(--calcite-slider-handle-size) * 0.5);margin-inline:0;--calcite-slider-full-handle-height:calc(\n var(--calcite-slider-handle-size) + var(--calcite-slider-handle-extension-height)\n )}:host([disabled]) .track__range,:host([disabled]) .tick--active{background-color:var(--calcite-ui-text-3)}:host([disabled]) ::slotted([calcite-hydrated][disabled]),:host([disabled]) [calcite-hydrated][disabled]{opacity:1}.scale--s .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset:-6px}.scale--m .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset:-8px}.scale--l .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset:-9px}:host([precise]:not([has-histogram])) .container .thumb--value{--calcite-slider-thumb-y-offset:calc(var(--calcite-slider-full-handle-height) * -1)}.thumb-container{position:relative;max-inline-size:100%}.thumb{--calcite-slider-thumb-x-offset:calc(var(--calcite-slider-handle-size) * 0.5);position:absolute;margin:0px;display:flex;cursor:pointer;flex-direction:column;align-items:center;border-style:none;background-color:transparent;padding:0px;font-family:inherit;outline:2px solid transparent;outline-offset:2px;transform:translate(var(--calcite-slider-thumb-x-offset), var(--calcite-slider-thumb-y-offset))}.thumb .handle__label.static,.thumb .handle__label.transformed{position:absolute;inset-block:0px;opacity:0}.thumb .handle__label.hyphen::after{content:\"—\";display:inline-block;inline-size:1em}.thumb .handle__label.hyphen--wrap{display:flex}.thumb .handle{box-sizing:border-box;border-radius:9999px;background-color:var(--calcite-ui-foreground-1);outline-color:transparent;block-size:var(--calcite-slider-handle-size);inline-size:var(--calcite-slider-handle-size);box-shadow:0 0 0 2px var(--calcite-ui-text-3) inset;transition:border var(--calcite-internal-animation-timing-medium) ease, background-color var(--calcite-internal-animation-timing-medium) ease, box-shadow var(--calcite-animation-timing) ease}.thumb .handle-extension{inline-size:0.125rem;block-size:var(--calcite-slider-handle-extension-height);background-color:var(--calcite-ui-text-3)}.thumb:hover .handle{box-shadow:0 0 0 3px var(--calcite-ui-brand) inset}.thumb:hover .handle-extension{background-color:var(--calcite-ui-brand)}.thumb:focus .handle{outline:2px solid var(--calcite-ui-brand);outline-offset:2px}.thumb:focus .handle-extension{background-color:var(--calcite-ui-brand)}.thumb.thumb--minValue{transform:translate(calc(var(--calcite-slider-thumb-x-offset) * -1), var(--calcite-slider-thumb-y-offset))}.thumb.thumb--precise{--calcite-slider-thumb-y-offset:-2px}:host([label-handles]) .thumb{--calcite-slider-thumb-x-offset:50%}:host([label-handles]):host(:not([has-histogram])) .scale--s .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset:-23px}:host([label-handles]):host(:not([has-histogram])) .scale--m .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset:-30px}:host([label-handles]):host(:not([has-histogram])) .scale--l .thumb:not(.thumb--precise){--calcite-slider-thumb-y-offset:-32px}:host([has-histogram][label-handles]) .handle__label,:host([label-handles]:not([has-histogram])) .thumb--minValue.thumb--precise .handle__label{margin-block-start:0.5em}:host(:not([has-histogram]):not([precise])) .handle__label,:host([label-handles]:not([has-histogram])) .thumb--value .handle__label{margin-block-end:0.5em}:host([label-handles][precise]):host(:not([has-histogram])) .scale--s .thumb--value{--calcite-slider-thumb-y-offset:-33px}:host([label-handles][precise]):host(:not([has-histogram])) .scale--m .thumb--value{--calcite-slider-thumb-y-offset:-44px}:host([label-handles][precise]):host(:not([has-histogram])) .scale--l .thumb--value{--calcite-slider-thumb-y-offset:-49px}.thumb:focus .handle,.thumb--active .handle{background-color:var(--calcite-ui-brand);box-shadow:0 0 8px 0 rgba(0, 0, 0, 0.16)}.thumb:hover.thumb--precise:after,.thumb:focus.thumb--precise:after,.thumb--active.thumb--precise:after{background-color:var(--calcite-ui-brand)}.track{position:relative;block-size:0.125rem;border-radius:0px;background-color:var(--calcite-ui-border-2);transition:all var(--calcite-internal-animation-timing-medium) ease-in}.track__range{position:absolute;inset-block-start:0px;block-size:0.125rem;background-color:var(--calcite-ui-brand)}.container--range .track__range:hover{cursor:ew-resize}.container--range .track__range:after{position:absolute;inline-size:100%;content:\"\";inset-block-start:calc(var(--calcite-slider-full-handle-height) * 0.5 * -1);block-size:calc(var(--calcite-slider-handle-size) + var(--calcite-slider-handle-extension-height))}@media (forced-colors: active){.thumb{outline-width:0;outline-offset:0}.handle{outline:2px solid transparent;outline-offset:2px}.thumb:focus .handle,.thumb .handle-extension,.thumb:hover .handle-extension,.thumb:focus .handle-extension,.thumb:active .handle-extension{background-color:canvasText}.track{background-color:canvasText}.track__range{background-color:highlight}}.tick{position:absolute;block-size:0.25rem;inline-size:0.125rem;border-width:1px;border-style:solid;background-color:var(--calcite-ui-border-input);border-color:var(--calcite-ui-foreground-1);inset-block-start:-2px;pointer-events:none;margin-inline-start:calc(-1 * 0.125rem)}.tick--active{background-color:var(--calcite-ui-brand)}.tick__label{pointer-events:none;margin-block-start:0.875rem;display:flex;justify-content:center}.tick__label--min{transition:opacity var(--calcite-animation-timing)}.tick__label--max{transition:opacity var(--calcite-internal-animation-timing-fast)}:host([has-histogram][label-handles]) .tick__label--min,:host([has-histogram][label-handles]) .tick__label--max,:host([has-histogram][precise]) .tick__label--min,:host([has-histogram][precise]) .tick__label--max{font-weight:var(--calcite-font-weight-normal);color:var(--calcite-ui-text-3)}.graph{color:var(--calcite-ui-foreground-3);block-size:48px}:host([label-ticks][ticks]) .container{padding-block-end:calc(0.875rem + var(--calcite-slider-container-font-size))}:host([has-histogram]):host([precise][label-handles]) .container{padding-block-end:calc(var(--calcite-slider-full-handle-height) + 1em)}:host([has-histogram]):host([label-handles]:not([precise])) .container{padding-block-end:calc(var(--calcite-slider-handle-size) * 0.5 + 1em)}:host([has-histogram]):host([precise]:not([label-handles])) .container{padding-block-end:var(--calcite-slider-full-handle-height)}:host(:not([has-histogram])):host([precise]:not([label-handles])) .container{padding-block-start:var(--calcite-slider-full-handle-height)}:host(:not([has-histogram])):host([precise]:not([label-handles])) .container--range{padding-block-end:var(--calcite-slider-full-handle-height)}:host(:not([has-histogram])):host([label-handles]:not([precise])) .container{padding-block-start:calc(var(--calcite-slider-full-handle-height) + 4px)}:host(:not([has-histogram])):host([label-handles][precise]) .container{padding-block-start:calc(var(--calcite-slider-full-handle-height) + var(--calcite-slider-container-font-size) + 4px)}:host(:not([has-histogram])):host([label-handles][precise]) .container--range{padding-block-end:calc(var(--calcite-slider-full-handle-height) + var(--calcite-slider-container-font-size) + 4px)}::slotted(input[slot=hidden-form-input]){margin:0 !important;opacity:0 !important;outline:none !important;padding:0 !important;position:absolute !important;inset:0 !important;transform:none !important;-webkit-appearance:none !important;z-index:-1 !important}";
  240. function isRange(value) {
  241. return Array.isArray(value);
  242. }
  243. const Slider = class {
  244. constructor(hostRef) {
  245. index.registerInstance(this, hostRef);
  246. this.calciteSliderInput = index.createEvent(this, "calciteSliderInput", 6);
  247. this.calciteSliderChange = index.createEvent(this, "calciteSliderChange", 6);
  248. this.calciteSliderUpdate = index.createEvent(this, "calciteSliderUpdate", 6);
  249. //--------------------------------------------------------------------------
  250. //
  251. // Properties
  252. //
  253. //--------------------------------------------------------------------------
  254. /** When `true`, interaction is prevented and the component is displayed with lower opacity. */
  255. this.disabled = false;
  256. /**
  257. * When `true`, number values are displayed with a group separator corresponding to the language and country format.
  258. */
  259. this.groupSeparator = false;
  260. /** When `true`, indicates a histogram is present. */
  261. this.hasHistogram = false;
  262. /** When `true`, displays label handles with their numeric value. */
  263. this.labelHandles = false;
  264. /** When `true` and `ticks` is specified, displays label tick marks with their numeric value. */
  265. this.labelTicks = false;
  266. /** The component's maximum selectable value. */
  267. this.max = 100;
  268. /** The component's minimum selectable value. */
  269. this.min = 0;
  270. /**
  271. * When `true`, the slider will display values from high to low.
  272. *
  273. * Note that this value will be ignored if the slider has an associated histogram.
  274. */
  275. this.mirrored = false;
  276. /** When `true`, sets a finer point for handles. */
  277. this.precise = false;
  278. /**
  279. * When `true`, the component must have a value in order for the form to submit.
  280. */
  281. this.required = false;
  282. /** When `true`, enables snap selection in coordination with `step` via a mouse. */
  283. this.snap = false;
  284. /** Specifies the interval to move with the up, or down keys. */
  285. this.step = 1;
  286. /** The component's value. */
  287. this.value = 0;
  288. /**
  289. * Specifies the size of the component.
  290. */
  291. this.scale = "m";
  292. this.activeProp = "value";
  293. this.guid = `calcite-slider-${guid.guid()}`;
  294. this.effectiveLocale = "";
  295. this.minMaxValueRange = null;
  296. this.minValueDragRange = null;
  297. this.maxValueDragRange = null;
  298. this.tickValues = [];
  299. this.dragUpdate = (event) => {
  300. event.preventDefault();
  301. if (this.dragProp) {
  302. const value = this.translate(event.clientX || event.pageX);
  303. if (isRange(this.value) && this.dragProp === "minMaxValue") {
  304. if (this.minValueDragRange && this.maxValueDragRange && this.minMaxValueRange) {
  305. const newMinValue = value - this.minValueDragRange;
  306. const newMaxValue = value + this.maxValueDragRange;
  307. if (newMaxValue <= this.max &&
  308. newMinValue >= this.min &&
  309. newMaxValue - newMinValue === this.minMaxValueRange) {
  310. this.setValue({
  311. minValue: this.clamp(newMinValue, "minValue"),
  312. maxValue: this.clamp(newMaxValue, "maxValue")
  313. });
  314. }
  315. }
  316. else {
  317. this.minValueDragRange = value - this.minValue;
  318. this.maxValueDragRange = this.maxValue - value;
  319. this.minMaxValueRange = this.maxValue - this.minValue;
  320. }
  321. }
  322. else {
  323. this.setValue({ [this.dragProp]: this.clamp(value, this.dragProp) });
  324. }
  325. }
  326. };
  327. this.pointerUpDragEnd = (event) => {
  328. if (!dom.isPrimaryPointerButton(event)) {
  329. return;
  330. }
  331. this.dragEnd(event);
  332. };
  333. this.dragEnd = (event) => {
  334. this.removeDragListeners();
  335. this.focusActiveHandle(event.clientX);
  336. if (this.lastDragPropValue != this[this.dragProp]) {
  337. this.emitChange();
  338. }
  339. this.dragProp = null;
  340. this.lastDragPropValue = null;
  341. this.minValueDragRange = null;
  342. this.maxValueDragRange = null;
  343. this.minMaxValueRange = null;
  344. };
  345. /**
  346. * Set the reference of the track Element
  347. *
  348. * @internal
  349. * @param node
  350. */
  351. this.storeTrackRef = (node) => {
  352. this.trackEl = node;
  353. };
  354. /**
  355. * Returns a string representing the localized label value based if the groupSeparator prop is parsed.
  356. *
  357. * @param value
  358. */
  359. this.determineGroupSeparator = (value) => {
  360. if (typeof value === "number") {
  361. locale.numberStringFormatter.numberFormatOptions = {
  362. locale: this.effectiveLocale,
  363. numberingSystem: this.numberingSystem,
  364. useGrouping: this.groupSeparator
  365. };
  366. return locale.numberStringFormatter.localize(value.toString());
  367. }
  368. };
  369. }
  370. histogramWatcher(newHistogram) {
  371. this.hasHistogram = !!newHistogram;
  372. }
  373. valueHandler() {
  374. this.setMinMaxFromValue();
  375. }
  376. minMaxValueHandler() {
  377. this.setValueFromMinMax();
  378. }
  379. //--------------------------------------------------------------------------
  380. //
  381. // Lifecycle
  382. //
  383. //--------------------------------------------------------------------------
  384. connectedCallback() {
  385. locale.connectLocalized(this);
  386. this.setMinMaxFromValue();
  387. this.setValueFromMinMax();
  388. label.connectLabel(this);
  389. form.connectForm(this);
  390. }
  391. disconnectedCallback() {
  392. label.disconnectLabel(this);
  393. form.disconnectForm(this);
  394. locale.disconnectLocalized(this);
  395. this.removeDragListeners();
  396. }
  397. componentWillLoad() {
  398. this.tickValues = this.generateTickValues();
  399. if (!isRange(this.value)) {
  400. this.value = this.clamp(this.value);
  401. }
  402. form.afterConnectDefaultValueSet(this, this.value);
  403. if (this.snap && !isRange(this.value)) {
  404. this.value = this.getClosestStep(this.value);
  405. }
  406. if (this.histogram) {
  407. this.hasHistogram = true;
  408. }
  409. }
  410. componentDidRender() {
  411. if (this.labelHandles) {
  412. this.adjustHostObscuredHandleLabel("value");
  413. if (isRange(this.value)) {
  414. this.adjustHostObscuredHandleLabel("minValue");
  415. if (!(this.precise && !this.hasHistogram)) {
  416. this.hyphenateCollidingRangeHandleLabels();
  417. }
  418. }
  419. }
  420. this.hideObscuredBoundingTickLabels();
  421. interactive.updateHostInteraction(this);
  422. }
  423. render() {
  424. const id = this.el.id || this.guid;
  425. const maxProp = isRange(this.value) ? "maxValue" : "value";
  426. const value = isRange(this.value) ? this.maxValue : this.value;
  427. const displayedValue = this.determineGroupSeparator(value);
  428. const displayedMinValue = this.determineGroupSeparator(this.minValue);
  429. const min = this.minValue || this.min;
  430. const useMinValue = this.shouldUseMinValue();
  431. const minInterval = this.getUnitInterval(useMinValue ? this.minValue : min) * 100;
  432. const maxInterval = this.getUnitInterval(value) * 100;
  433. const mirror = this.shouldMirror();
  434. const leftThumbOffset = `${mirror ? 100 - minInterval : minInterval}%`;
  435. const rightThumbOffset = `${mirror ? maxInterval : 100 - maxInterval}%`;
  436. const valueIsRange = isRange(this.value);
  437. const handleLabelMinValueClasses = `${CSS.handleLabel} ${CSS.handleLabelMinValue}`;
  438. const handleLabelValueClasses = `${CSS.handleLabel} ${CSS.handleLabelValue}`;
  439. const handle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  440. thumb: true,
  441. "thumb--value": true,
  442. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp
  443. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle" })));
  444. const labeledHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  445. thumb: true,
  446. "thumb--value": true,
  447. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp
  448. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("span", { "aria-hidden": "true", class: handleLabelValueClasses }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} static` }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} transformed` }, displayedValue), index.h("div", { class: "handle" })));
  449. const histogramLabeledHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  450. thumb: true,
  451. "thumb--value": true,
  452. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp
  453. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle" }), index.h("span", { "aria-hidden": "true", class: handleLabelValueClasses }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} static` }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} transformed` }, displayedValue)));
  454. const preciseHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  455. thumb: true,
  456. "thumb--value": true,
  457. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp,
  458. "thumb--precise": true
  459. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle" }), index.h("div", { class: "handle-extension" })));
  460. const histogramPreciseHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  461. thumb: true,
  462. "thumb--value": true,
  463. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp,
  464. "thumb--precise": true
  465. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle-extension" }), index.h("div", { class: "handle" })));
  466. const labeledPreciseHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  467. thumb: true,
  468. "thumb--value": true,
  469. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp,
  470. "thumb--precise": true
  471. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("span", { "aria-hidden": "true", class: handleLabelValueClasses }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} static` }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} transformed` }, displayedValue), index.h("div", { class: "handle" }), index.h("div", { class: "handle-extension" })));
  472. const histogramLabeledPreciseHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": valueIsRange ? this.maxLabel : this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": value, class: {
  473. thumb: true,
  474. "thumb--value": true,
  475. "thumb--active": this.lastDragProp !== "minMaxValue" && this.dragProp === maxProp,
  476. "thumb--precise": true
  477. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = maxProp), onPointerDown: (event) => this.pointerDownDragStart(event, maxProp), ref: (el) => (this.maxHandle = el), role: "slider", style: { right: rightThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle-extension" }), index.h("div", { class: "handle" }), index.h("span", { "aria-hidden": "true", class: handleLabelValueClasses }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} static` }, displayedValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelValueClasses} transformed` }, displayedValue)));
  478. const minHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": this.minValue, class: {
  479. thumb: true,
  480. "thumb--minValue": true,
  481. "thumb--active": this.dragProp === "minValue"
  482. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = "minValue"), onPointerDown: (event) => this.pointerDownDragStart(event, "minValue"), ref: (el) => (this.minHandle = el), role: "slider", style: { left: leftThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle" })));
  483. const minLabeledHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": this.minValue, class: {
  484. thumb: true,
  485. "thumb--minValue": true,
  486. "thumb--active": this.dragProp === "minValue"
  487. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = "minValue"), onPointerDown: (event) => this.pointerDownDragStart(event, "minValue"), ref: (el) => (this.minHandle = el), role: "slider", style: { left: leftThumbOffset }, tabIndex: 0 }, index.h("span", { "aria-hidden": "true", class: handleLabelMinValueClasses }, displayedMinValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelMinValueClasses} static` }, displayedMinValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelMinValueClasses} transformed` }, displayedMinValue), index.h("div", { class: "handle" })));
  488. const minHistogramLabeledHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": this.minValue, class: {
  489. thumb: true,
  490. "thumb--minValue": true,
  491. "thumb--active": this.dragProp === "minValue"
  492. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = "minValue"), onPointerDown: (event) => this.pointerDownDragStart(event, "minValue"), ref: (el) => (this.minHandle = el), role: "slider", style: { left: leftThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle" }), index.h("span", { "aria-hidden": "true", class: handleLabelMinValueClasses }, displayedMinValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelMinValueClasses} static` }, displayedMinValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelMinValueClasses} transformed` }, displayedMinValue)));
  493. const minPreciseHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": this.minValue, class: {
  494. thumb: true,
  495. "thumb--minValue": true,
  496. "thumb--active": this.dragProp === "minValue",
  497. "thumb--precise": true
  498. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = "minValue"), onPointerDown: (event) => this.pointerDownDragStart(event, "minValue"), ref: (el) => (this.minHandle = el), role: "slider", style: { left: leftThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle-extension" }), index.h("div", { class: "handle" })));
  499. const minLabeledPreciseHandle = (index.h("div", { "aria-disabled": this.disabled, "aria-label": this.minLabel, "aria-orientation": "horizontal", "aria-valuemax": this.max, "aria-valuemin": this.min, "aria-valuenow": this.minValue, class: {
  500. thumb: true,
  501. "thumb--minValue": true,
  502. "thumb--active": this.dragProp === "minValue",
  503. "thumb--precise": true
  504. }, onBlur: () => (this.activeProp = null), onFocus: () => (this.activeProp = "minValue"), onPointerDown: (event) => this.pointerDownDragStart(event, "minValue"), ref: (el) => (this.minHandle = el), role: "slider", style: { left: leftThumbOffset }, tabIndex: 0 }, index.h("div", { class: "handle-extension" }), index.h("div", { class: "handle" }), index.h("span", { "aria-hidden": "true", class: handleLabelMinValueClasses }, displayedMinValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelMinValueClasses} static` }, displayedMinValue), index.h("span", { "aria-hidden": "true", class: `${handleLabelMinValueClasses} transformed` }, displayedMinValue)));
  505. return (index.h(index.Host, { id: id, onTouchStart: this.handleTouchStart }, index.h("div", { class: {
  506. ["container"]: true,
  507. ["container--range"]: valueIsRange,
  508. [`scale--${this.scale}`]: true
  509. } }, this.renderGraph(), index.h("div", { class: "track", ref: this.storeTrackRef }, index.h("div", { class: "track__range", onPointerDown: (event) => this.pointerDownDragStart(event, "minMaxValue"), style: {
  510. left: `${mirror ? 100 - maxInterval : minInterval}%`,
  511. right: `${mirror ? minInterval : 100 - maxInterval}%`
  512. } }), index.h("div", { class: "ticks" }, this.tickValues.map((tick) => {
  513. const tickOffset = `${this.getUnitInterval(tick) * 100}%`;
  514. let activeTicks = tick >= min && tick <= value;
  515. if (useMinValue) {
  516. activeTicks = tick >= this.minValue && tick <= this.maxValue;
  517. }
  518. return (index.h("span", { class: {
  519. tick: true,
  520. "tick--active": activeTicks
  521. }, style: {
  522. left: mirror ? "" : tickOffset,
  523. right: mirror ? tickOffset : ""
  524. } }, this.renderTickLabel(tick)));
  525. }))), index.h("div", { class: "thumb-container" }, !this.precise && !this.labelHandles && valueIsRange && minHandle, !this.hasHistogram &&
  526. !this.precise &&
  527. this.labelHandles &&
  528. valueIsRange &&
  529. minLabeledHandle, this.precise && !this.labelHandles && valueIsRange && minPreciseHandle, this.precise && this.labelHandles && valueIsRange && minLabeledPreciseHandle, this.hasHistogram &&
  530. !this.precise &&
  531. this.labelHandles &&
  532. valueIsRange &&
  533. minHistogramLabeledHandle, !this.precise && !this.labelHandles && handle, !this.hasHistogram && !this.precise && this.labelHandles && labeledHandle, !this.hasHistogram && this.precise && !this.labelHandles && preciseHandle, this.hasHistogram && this.precise && !this.labelHandles && histogramPreciseHandle, !this.hasHistogram && this.precise && this.labelHandles && labeledPreciseHandle, this.hasHistogram && !this.precise && this.labelHandles && histogramLabeledHandle, this.hasHistogram &&
  534. this.precise &&
  535. this.labelHandles &&
  536. histogramLabeledPreciseHandle, index.h(form.HiddenFormInputSlot, { component: this })))));
  537. }
  538. renderGraph() {
  539. return this.histogram ? (index.h("calcite-graph", { class: "graph", colorStops: this.histogramStops, data: this.histogram, highlightMax: isRange(this.value) ? this.maxValue : this.value, highlightMin: isRange(this.value) ? this.minValue : this.min, max: this.max, min: this.min })) : null;
  540. }
  541. renderTickLabel(tick) {
  542. const valueIsRange = isRange(this.value);
  543. const isMinTickLabel = tick === this.min;
  544. const isMaxTickLabel = tick === this.max;
  545. const displayedTickValue = this.determineGroupSeparator(tick);
  546. const tickLabel = (index.h("span", { class: {
  547. tick__label: true,
  548. [CSS.tickMin]: isMinTickLabel,
  549. [CSS.tickMax]: isMaxTickLabel
  550. } }, displayedTickValue));
  551. if (this.labelTicks && !this.hasHistogram && !valueIsRange) {
  552. return tickLabel;
  553. }
  554. if (this.labelTicks &&
  555. !this.hasHistogram &&
  556. valueIsRange &&
  557. !this.precise &&
  558. !this.labelHandles) {
  559. return tickLabel;
  560. }
  561. if (this.labelTicks &&
  562. !this.hasHistogram &&
  563. valueIsRange &&
  564. !this.precise &&
  565. this.labelHandles) {
  566. return tickLabel;
  567. }
  568. if (this.labelTicks &&
  569. !this.hasHistogram &&
  570. valueIsRange &&
  571. this.precise &&
  572. (isMinTickLabel || isMaxTickLabel)) {
  573. return tickLabel;
  574. }
  575. if (this.labelTicks && this.hasHistogram && !this.precise && !this.labelHandles) {
  576. return tickLabel;
  577. }
  578. if (this.labelTicks &&
  579. this.hasHistogram &&
  580. this.precise &&
  581. !this.labelHandles &&
  582. (isMinTickLabel || isMaxTickLabel)) {
  583. return tickLabel;
  584. }
  585. if (this.labelTicks &&
  586. this.hasHistogram &&
  587. !this.precise &&
  588. this.labelHandles &&
  589. (isMinTickLabel || isMaxTickLabel)) {
  590. return tickLabel;
  591. }
  592. if (this.labelTicks &&
  593. this.hasHistogram &&
  594. this.precise &&
  595. this.labelHandles &&
  596. (isMinTickLabel || isMaxTickLabel)) {
  597. return tickLabel;
  598. }
  599. return null;
  600. }
  601. //--------------------------------------------------------------------------
  602. //
  603. // Event Listeners
  604. //
  605. //--------------------------------------------------------------------------
  606. keyDownHandler(event) {
  607. const mirror = this.shouldMirror();
  608. const { activeProp, max, min, pageStep, step } = this;
  609. const value = this[activeProp];
  610. const { key: key$1 } = event;
  611. if (key.isActivationKey(key$1)) {
  612. event.preventDefault();
  613. return;
  614. }
  615. let adjustment;
  616. if (key$1 === "ArrowUp" || key$1 === "ArrowRight") {
  617. const directionFactor = mirror && key$1 === "ArrowRight" ? -1 : 1;
  618. adjustment = value + step * directionFactor;
  619. }
  620. else if (key$1 === "ArrowDown" || key$1 === "ArrowLeft") {
  621. const directionFactor = mirror && key$1 === "ArrowLeft" ? -1 : 1;
  622. adjustment = value - step * directionFactor;
  623. }
  624. else if (key$1 === "PageUp") {
  625. if (pageStep) {
  626. adjustment = value + pageStep;
  627. }
  628. }
  629. else if (key$1 === "PageDown") {
  630. if (pageStep) {
  631. adjustment = value - pageStep;
  632. }
  633. }
  634. else if (key$1 === "Home") {
  635. adjustment = min;
  636. }
  637. else if (key$1 === "End") {
  638. adjustment = max;
  639. }
  640. if (isNaN(adjustment)) {
  641. return;
  642. }
  643. event.preventDefault();
  644. const fixedDecimalAdjustment = Number(adjustment.toFixed(math.decimalPlaces(step)));
  645. this.setValue({
  646. [activeProp]: this.clamp(fixedDecimalAdjustment, activeProp)
  647. });
  648. }
  649. pointerDownHandler(event) {
  650. if (!dom.isPrimaryPointerButton(event)) {
  651. return;
  652. }
  653. const x = event.clientX || event.pageX;
  654. const position = this.translate(x);
  655. let prop = "value";
  656. if (isRange(this.value)) {
  657. const inRange = position >= this.minValue && position <= this.maxValue;
  658. if (inRange && this.lastDragProp === "minMaxValue") {
  659. prop = "minMaxValue";
  660. }
  661. else {
  662. const closerToMax = Math.abs(this.maxValue - position) < Math.abs(this.minValue - position);
  663. prop = closerToMax || position > this.maxValue ? "maxValue" : "minValue";
  664. }
  665. }
  666. this.lastDragPropValue = this[prop];
  667. this.dragStart(prop);
  668. const isThumbActive = this.el.shadowRoot.querySelector(".thumb:active");
  669. if (!isThumbActive) {
  670. this.setValue({ [prop]: this.clamp(position, prop) });
  671. }
  672. this.focusActiveHandle(x);
  673. }
  674. handleTouchStart(event) {
  675. // needed to prevent extra click at the end of a handle drag
  676. event.preventDefault();
  677. }
  678. //--------------------------------------------------------------------------
  679. //
  680. // Public Methods
  681. //
  682. //--------------------------------------------------------------------------
  683. /** Sets focus on the component. */
  684. async setFocus() {
  685. const handle = this.minHandle ? this.minHandle : this.maxHandle;
  686. handle === null || handle === void 0 ? void 0 : handle.focus();
  687. }
  688. //--------------------------------------------------------------------------
  689. //
  690. // Private Methods
  691. //
  692. //--------------------------------------------------------------------------
  693. setValueFromMinMax() {
  694. const { minValue, maxValue } = this;
  695. if (typeof minValue === "number" && typeof maxValue === "number") {
  696. this.value = [minValue, maxValue];
  697. }
  698. }
  699. setMinMaxFromValue() {
  700. const { value } = this;
  701. if (isRange(value)) {
  702. this.minValue = value[0];
  703. this.maxValue = value[1];
  704. }
  705. }
  706. onLabelClick() {
  707. this.setFocus();
  708. }
  709. shouldMirror() {
  710. return this.mirrored && !this.hasHistogram;
  711. }
  712. shouldUseMinValue() {
  713. if (!isRange(this.value)) {
  714. return false;
  715. }
  716. return ((this.hasHistogram && this.maxValue === 0) || (!this.hasHistogram && this.minValue === 0));
  717. }
  718. generateTickValues() {
  719. const ticks = [];
  720. let current = this.min;
  721. while (this.ticks && current < this.max + this.ticks) {
  722. ticks.push(Math.min(current, this.max));
  723. current = current + this.ticks;
  724. }
  725. return ticks;
  726. }
  727. pointerDownDragStart(event, prop) {
  728. if (!dom.isPrimaryPointerButton(event)) {
  729. return;
  730. }
  731. this.dragStart(prop);
  732. }
  733. dragStart(prop) {
  734. this.dragProp = prop;
  735. this.lastDragProp = this.dragProp;
  736. this.activeProp = prop;
  737. document.addEventListener("pointermove", this.dragUpdate);
  738. document.addEventListener("pointerup", this.pointerUpDragEnd);
  739. document.addEventListener("pointercancel", this.dragEnd);
  740. }
  741. focusActiveHandle(valueX) {
  742. switch (this.dragProp) {
  743. case "minValue":
  744. this.minHandle.focus();
  745. break;
  746. case "maxValue":
  747. case "value":
  748. this.maxHandle.focus();
  749. break;
  750. case "minMaxValue":
  751. this.getClosestHandle(valueX).focus();
  752. break;
  753. }
  754. }
  755. emitInput() {
  756. this.calciteSliderInput.emit();
  757. this.calciteSliderUpdate.emit();
  758. }
  759. emitChange() {
  760. this.calciteSliderChange.emit();
  761. }
  762. removeDragListeners() {
  763. document.removeEventListener("pointermove", this.dragUpdate);
  764. document.removeEventListener("pointerup", this.pointerUpDragEnd);
  765. document.removeEventListener("pointercancel", this.dragEnd);
  766. }
  767. /**
  768. * Set prop value(s) if changed at the component level
  769. *
  770. * @param {object} values - a set of key/value pairs delineating what properties in the component to update
  771. */
  772. setValue(values) {
  773. let valueChanged;
  774. Object.keys(values).forEach((propName) => {
  775. const newValue = values[propName];
  776. if (!valueChanged) {
  777. const oldValue = this[propName];
  778. valueChanged = oldValue !== newValue;
  779. }
  780. this[propName] = newValue;
  781. });
  782. if (!valueChanged) {
  783. return;
  784. }
  785. const dragging = this.dragProp;
  786. if (!dragging) {
  787. this.emitChange();
  788. }
  789. this.emitInput();
  790. }
  791. /**
  792. * If number is outside range, constrain to min or max
  793. *
  794. * @param value
  795. * @param prop
  796. * @internal
  797. */
  798. clamp(value, prop) {
  799. value = math.clamp(value, this.min, this.max);
  800. // ensure that maxValue and minValue don't swap positions
  801. if (prop === "maxValue") {
  802. value = Math.max(value, this.minValue);
  803. }
  804. if (prop === "minValue") {
  805. value = Math.min(value, this.maxValue);
  806. }
  807. return value;
  808. }
  809. /**
  810. * Translate a pixel position to value along the range
  811. *
  812. * @param x
  813. * @internal
  814. */
  815. translate(x) {
  816. const range = this.max - this.min;
  817. const { left, width } = this.trackEl.getBoundingClientRect();
  818. const percent = (x - left) / width;
  819. const mirror = this.shouldMirror();
  820. const clampedValue = this.clamp(this.min + range * (mirror ? 1 - percent : percent));
  821. let value = Number(clampedValue.toFixed(math.decimalPlaces(this.step)));
  822. if (this.snap && this.step) {
  823. value = this.getClosestStep(value);
  824. }
  825. return value;
  826. }
  827. /**
  828. * Get closest allowed value along stepped values
  829. *
  830. * @param num
  831. * @internal
  832. */
  833. getClosestStep(num) {
  834. num = Number(this.clamp(num).toFixed(math.decimalPlaces(this.step)));
  835. if (this.step) {
  836. const step = Math.round(num / this.step) * this.step;
  837. num = Number(this.clamp(step).toFixed(math.decimalPlaces(this.step)));
  838. }
  839. return num;
  840. }
  841. getClosestHandle(valueX) {
  842. return this.getDistanceX(this.maxHandle, valueX) > this.getDistanceX(this.minHandle, valueX)
  843. ? this.minHandle
  844. : this.maxHandle;
  845. }
  846. getDistanceX(el, valueX) {
  847. return Math.abs(el.getBoundingClientRect().left - valueX);
  848. }
  849. getFontSizeForElement(element) {
  850. return Number(window.getComputedStyle(element).getPropertyValue("font-size").match(/\d+/)[0]);
  851. }
  852. /**
  853. * Get position of value along range as fractional value
  854. *
  855. * @param num
  856. * @return {number} number in the unit interval [0,1]
  857. * @internal
  858. */
  859. getUnitInterval(num) {
  860. num = this.clamp(num);
  861. const range = this.max - this.min;
  862. return (num - this.min) / range;
  863. }
  864. adjustHostObscuredHandleLabel(name) {
  865. const label = this.el.shadowRoot.querySelector(`.handle__label--${name}`);
  866. const labelStatic = this.el.shadowRoot.querySelector(`.handle__label--${name}.static`);
  867. const labelTransformed = this.el.shadowRoot.querySelector(`.handle__label--${name}.transformed`);
  868. const labelStaticBounds = labelStatic.getBoundingClientRect();
  869. const labelStaticOffset = this.getHostOffset(labelStaticBounds.left, labelStaticBounds.right);
  870. label.style.transform = `translateX(${labelStaticOffset}px)`;
  871. labelTransformed.style.transform = `translateX(${labelStaticOffset}px)`;
  872. }
  873. hyphenateCollidingRangeHandleLabels() {
  874. const { shadowRoot } = this.el;
  875. const mirror = this.shouldMirror();
  876. const leftModifier = mirror ? "value" : "minValue";
  877. const rightModifier = mirror ? "minValue" : "value";
  878. const leftValueLabel = shadowRoot.querySelector(`.handle__label--${leftModifier}`);
  879. const leftValueLabelStatic = shadowRoot.querySelector(`.handle__label--${leftModifier}.static`);
  880. const leftValueLabelTransformed = shadowRoot.querySelector(`.handle__label--${leftModifier}.transformed`);
  881. const leftValueLabelStaticHostOffset = this.getHostOffset(leftValueLabelStatic.getBoundingClientRect().left, leftValueLabelStatic.getBoundingClientRect().right);
  882. const rightValueLabel = shadowRoot.querySelector(`.handle__label--${rightModifier}`);
  883. const rightValueLabelStatic = shadowRoot.querySelector(`.handle__label--${rightModifier}.static`);
  884. const rightValueLabelTransformed = shadowRoot.querySelector(`.handle__label--${rightModifier}.transformed`);
  885. const rightValueLabelStaticHostOffset = this.getHostOffset(rightValueLabelStatic.getBoundingClientRect().left, rightValueLabelStatic.getBoundingClientRect().right);
  886. const labelFontSize = this.getFontSizeForElement(leftValueLabel);
  887. const labelTransformedOverlap = this.getRangeLabelOverlap(leftValueLabelTransformed, rightValueLabelTransformed);
  888. const hyphenLabel = leftValueLabel;
  889. const labelOffset = labelFontSize / 2;
  890. if (labelTransformedOverlap > 0) {
  891. hyphenLabel.classList.add("hyphen", "hyphen--wrap");
  892. if (rightValueLabelStaticHostOffset === 0 && leftValueLabelStaticHostOffset === 0) {
  893. // Neither handle overlaps the host boundary
  894. let leftValueLabelTranslate = labelTransformedOverlap / 2 - labelOffset;
  895. leftValueLabelTranslate =
  896. Math.sign(leftValueLabelTranslate) === -1
  897. ? Math.abs(leftValueLabelTranslate)
  898. : -leftValueLabelTranslate;
  899. const leftValueLabelTransformedHostOffset = this.getHostOffset(leftValueLabelTransformed.getBoundingClientRect().left +
  900. leftValueLabelTranslate -
  901. labelOffset, leftValueLabelTransformed.getBoundingClientRect().right +
  902. leftValueLabelTranslate -
  903. labelOffset);
  904. let rightValueLabelTranslate = labelTransformedOverlap / 2;
  905. const rightValueLabelTransformedHostOffset = this.getHostOffset(rightValueLabelTransformed.getBoundingClientRect().left + rightValueLabelTranslate, rightValueLabelTransformed.getBoundingClientRect().right + rightValueLabelTranslate);
  906. if (leftValueLabelTransformedHostOffset !== 0) {
  907. leftValueLabelTranslate += leftValueLabelTransformedHostOffset;
  908. rightValueLabelTranslate += leftValueLabelTransformedHostOffset;
  909. }
  910. if (rightValueLabelTransformedHostOffset !== 0) {
  911. leftValueLabelTranslate += rightValueLabelTransformedHostOffset;
  912. rightValueLabelTranslate += rightValueLabelTransformedHostOffset;
  913. }
  914. leftValueLabel.style.transform = `translateX(${leftValueLabelTranslate}px)`;
  915. leftValueLabelTransformed.style.transform = `translateX(${leftValueLabelTranslate - labelOffset}px)`;
  916. rightValueLabel.style.transform = `translateX(${rightValueLabelTranslate}px)`;
  917. rightValueLabelTransformed.style.transform = `translateX(${rightValueLabelTranslate}px)`;
  918. }
  919. else if (leftValueLabelStaticHostOffset > 0 || rightValueLabelStaticHostOffset > 0) {
  920. // labels overlap host boundary on the left side
  921. leftValueLabel.style.transform = `translateX(${leftValueLabelStaticHostOffset + labelOffset}px)`;
  922. rightValueLabel.style.transform = `translateX(${labelTransformedOverlap + rightValueLabelStaticHostOffset}px)`;
  923. rightValueLabelTransformed.style.transform = `translateX(${labelTransformedOverlap + rightValueLabelStaticHostOffset}px)`;
  924. }
  925. else if (leftValueLabelStaticHostOffset < 0 || rightValueLabelStaticHostOffset < 0) {
  926. // labels overlap host boundary on the right side
  927. let leftValueLabelTranslate = Math.abs(leftValueLabelStaticHostOffset) + labelTransformedOverlap - labelOffset;
  928. leftValueLabelTranslate =
  929. Math.sign(leftValueLabelTranslate) === -1
  930. ? Math.abs(leftValueLabelTranslate)
  931. : -leftValueLabelTranslate;
  932. leftValueLabel.style.transform = `translateX(${leftValueLabelTranslate}px)`;
  933. leftValueLabelTransformed.style.transform = `translateX(${leftValueLabelTranslate - labelOffset}px)`;
  934. }
  935. }
  936. else {
  937. hyphenLabel.classList.remove("hyphen", "hyphen--wrap");
  938. leftValueLabel.style.transform = `translateX(${leftValueLabelStaticHostOffset}px)`;
  939. leftValueLabelTransformed.style.transform = `translateX(${leftValueLabelStaticHostOffset}px)`;
  940. rightValueLabel.style.transform = `translateX(${rightValueLabelStaticHostOffset}px)`;
  941. rightValueLabelTransformed.style.transform = `translateX(${rightValueLabelStaticHostOffset}px)`;
  942. }
  943. }
  944. /**
  945. * Hides bounding tick labels that are obscured by either handle.
  946. */
  947. hideObscuredBoundingTickLabels() {
  948. const valueIsRange = isRange(this.value);
  949. if (!this.hasHistogram && !valueIsRange && !this.labelHandles && !this.precise) {
  950. return;
  951. }
  952. if (!this.hasHistogram && !valueIsRange && this.labelHandles && !this.precise) {
  953. return;
  954. }
  955. if (!this.hasHistogram && !valueIsRange && !this.labelHandles && this.precise) {
  956. return;
  957. }
  958. if (!this.hasHistogram && !valueIsRange && this.labelHandles && this.precise) {
  959. return;
  960. }
  961. if (!this.hasHistogram && valueIsRange && !this.precise) {
  962. return;
  963. }
  964. if (this.hasHistogram && !this.precise && !this.labelHandles) {
  965. return;
  966. }
  967. const minHandle = this.el.shadowRoot.querySelector(".thumb--minValue");
  968. const maxHandle = this.el.shadowRoot.querySelector(".thumb--value");
  969. const minTickLabel = this.el.shadowRoot.querySelector(".tick__label--min");
  970. const maxTickLabel = this.el.shadowRoot.querySelector(".tick__label--max");
  971. if (!minHandle && maxHandle && minTickLabel && maxTickLabel) {
  972. minTickLabel.style.opacity = this.isMinTickLabelObscured(minTickLabel, maxHandle) ? "0" : "1";
  973. maxTickLabel.style.opacity = this.isMaxTickLabelObscured(maxTickLabel, maxHandle) ? "0" : "1";
  974. }
  975. if (minHandle && maxHandle && minTickLabel && maxTickLabel) {
  976. minTickLabel.style.opacity =
  977. this.isMinTickLabelObscured(minTickLabel, minHandle) ||
  978. this.isMinTickLabelObscured(minTickLabel, maxHandle)
  979. ? "0"
  980. : "1";
  981. maxTickLabel.style.opacity =
  982. this.isMaxTickLabelObscured(maxTickLabel, minHandle) ||
  983. (this.isMaxTickLabelObscured(maxTickLabel, maxHandle) && this.hasHistogram)
  984. ? "0"
  985. : "1";
  986. }
  987. }
  988. /**
  989. * Returns an integer representing the number of pixels to offset on the left or right side based on desired position behavior.
  990. *
  991. * @param leftBounds
  992. * @param rightBounds
  993. * @internal
  994. */
  995. getHostOffset(leftBounds, rightBounds) {
  996. const hostBounds = this.el.getBoundingClientRect();
  997. const buffer = 7;
  998. if (leftBounds + buffer < hostBounds.left) {
  999. return hostBounds.left - leftBounds - buffer;
  1000. }
  1001. if (rightBounds - buffer > hostBounds.right) {
  1002. return -(rightBounds - hostBounds.right) + buffer;
  1003. }
  1004. return 0;
  1005. }
  1006. /**
  1007. * Returns an integer representing the number of pixels that the two given span elements are overlapping, taking into account
  1008. * a space in between the two spans equal to the font-size set on them to account for the space needed to render a hyphen.
  1009. *
  1010. * @param leftLabel
  1011. * @param rightLabel
  1012. */
  1013. getRangeLabelOverlap(leftLabel, rightLabel) {
  1014. const leftLabelBounds = leftLabel.getBoundingClientRect();
  1015. const rightLabelBounds = rightLabel.getBoundingClientRect();
  1016. const leftLabelFontSize = this.getFontSizeForElement(leftLabel);
  1017. const rangeLabelOverlap = leftLabelBounds.right + leftLabelFontSize - rightLabelBounds.left;
  1018. return Math.max(rangeLabelOverlap, 0);
  1019. }
  1020. /**
  1021. * Returns a boolean value representing if the minLabel span element is obscured (being overlapped) by the given handle div element.
  1022. *
  1023. * @param minLabel
  1024. * @param handle
  1025. */
  1026. isMinTickLabelObscured(minLabel, handle) {
  1027. const minLabelBounds = minLabel.getBoundingClientRect();
  1028. const handleBounds = handle.getBoundingClientRect();
  1029. return dom.intersects(minLabelBounds, handleBounds);
  1030. }
  1031. /**
  1032. * Returns a boolean value representing if the maxLabel span element is obscured (being overlapped) by the given handle div element.
  1033. *
  1034. * @param maxLabel
  1035. * @param handle
  1036. */
  1037. isMaxTickLabelObscured(maxLabel, handle) {
  1038. const maxLabelBounds = maxLabel.getBoundingClientRect();
  1039. const handleBounds = handle.getBoundingClientRect();
  1040. return dom.intersects(maxLabelBounds, handleBounds);
  1041. }
  1042. get el() { return index.getElement(this); }
  1043. static get watchers() { return {
  1044. "histogram": ["histogramWatcher"],
  1045. "value": ["valueHandler"],
  1046. "minValue": ["minMaxValueHandler"],
  1047. "maxValue": ["minMaxValueHandler"]
  1048. }; }
  1049. };
  1050. Slider.style = sliderCss;
  1051. exports.calcite_graph = Graph;
  1052. exports.calcite_slider = Slider;