calcite-graph_2.entry.js 58 KB

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