text: import { clamp, debounceEvent } from '../../utils/helpers';
  import { createColorClasses, hostContext } from '../../utils/theme';
  export class Range {
      constructor() {
          this.noUpdate = false;
          this.hasFocus = false;
          this.ratioA = 0;
          this.ratioB = 0;
          this.debounce = 0;
          this.name = '';
          this.dualKnobs = false;
          this.min = 0;
          this.max = 100;
          this.pin = false;
          this.snaps = false;
          this.step = 1;
          this.disabled = false;
          this.value = 0;
          this.clampBounds = (value) => {
              return clamp(this.min, value, this.max);
          };
          this.ensureValueInBounds = (value) => {
              if (this.dualKnobs) {
                  return {
                      lower: this.clampBounds(value.lower),
                      upper: this.clampBounds(value.upper)
                  };
              }
              else {
                  return this.clampBounds(value);
              }
          };
          this.handleKeyboard = (knob, isIncrease) => {
              let step = this.step;
              step = step > 0 ? step : 1;
              step = step / (this.max - this.min);
              if (!isIncrease) {
                  step *= -1;
              }
              if (knob === 'A') {
                  this.ratioA = clamp(0, this.ratioA + step, 1);
              }
              else {
                  this.ratioB = clamp(0, this.ratioB + step, 1);
              }
              this.updateValue();
          };
      }
      debounceChanged() {
          this.ionChange = debounceEvent(this.ionChange, this.debounce);
      }
      minChanged() {
          if (!this.noUpdate) {
              this.updateRatio();
          }
      }
      maxChanged() {
          if (!this.noUpdate) {
              this.updateRatio();
          }
      }
      disabledChanged() {
          if (this.gesture) {
              this.gesture.setDisabled(this.disabled);
          }
          this.emitStyle();
      }
      valueChanged(value) {
          if (!this.noUpdate) {
              this.updateRatio();
          }
          value = this.ensureValueInBounds(value);
          this.ionChange.emit({ value });
      }
      onBlur() {
          if (this.hasFocus) {
              this.hasFocus = false;
              this.ionBlur.emit();
              this.emitStyle();
          }
      }
      onFocus() {
          if (!this.hasFocus) {
              this.hasFocus = true;
              this.ionFocus.emit();
              this.emitStyle();
          }
      }
      componentWillLoad() {
          this.updateRatio();
          this.debounceChanged();
          this.emitStyle();
      }
      async componentDidLoad() {
          this.gesture = (await import('../../utils/gesture')).createGesture({
              el: this.rangeSlider,
              queue: this.queue,
              gestureName: 'range',
              gesturePriority: 100,
              threshold: 0,
              onStart: ev => this.onStart(ev),
              onMove: ev => this.onMove(ev),
              onEnd: ev => this.onEnd(ev),
          });
          this.gesture.setDisabled(this.disabled);
      }
      componentDidUnload() {
          if (this.gesture) {
              this.gesture.destroy();
              this.gesture = undefined;
          }
      }
      getValue() {
          const value = this.value || 0;
          if (this.dualKnobs) {
              if (typeof value === 'object') {
                  return value;
              }
              return {
                  lower: 0,
                  upper: value
              };
          }
          else {
              if (typeof value === 'object') {
                  return value.upper;
              }
              return value;
          }
      }
      emitStyle() {
          this.ionStyle.emit({
              'interactive': true,
              'interactive-disabled': this.disabled
          });
      }
      onStart(detail) {
          const rect = this.rect = this.rangeSlider.getBoundingClientRect();
          const currentX = detail.currentX;
          let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
          if (this.doc.dir === 'rtl') {
              ratio = 1 - ratio;
          }
          this.pressedKnob =
              !this.dualKnobs ||
                  Math.abs(this.ratioA - ratio) < Math.abs(this.ratioB - ratio)
                  ? 'A'
                  : 'B';
          this.setFocus(this.pressedKnob);
          this.update(currentX);
      }
      onMove(detail) {
          this.update(detail.currentX);
      }
      onEnd(detail) {
          this.update(detail.currentX);
          this.pressedKnob = undefined;
      }
      update(currentX) {
          const rect = this.rect;
          let ratio = clamp(0, (currentX - rect.left) / rect.width, 1);
          if (this.doc.dir === 'rtl') {
              ratio = 1 - ratio;
          }
          if (this.snaps) {
              ratio = valueToRatio(ratioToValue(ratio, this.min, this.max, this.step), this.min, this.max);
          }
          if (this.pressedKnob === 'A') {
              this.ratioA = ratio;
          }
          else {
              this.ratioB = ratio;
          }
          this.updateValue();
      }
      get valA() {
          return ratioToValue(this.ratioA, this.min, this.max, this.step);
      }
      get valB() {
          return ratioToValue(this.ratioB, this.min, this.max, this.step);
      }
      get ratioLower() {
          if (this.dualKnobs) {
              return Math.min(this.ratioA, this.ratioB);
          }
          return 0;
      }
      get ratioUpper() {
          if (this.dualKnobs) {
              return Math.max(this.ratioA, this.ratioB);
          }
          return this.ratioA;
      }
      updateRatio() {
          const value = this.getValue();
          const { min, max } = this;
          if (this.dualKnobs) {
              this.ratioA = valueToRatio(value.lower, min, max);
              this.ratioB = valueToRatio(value.upper, min, max);
          }
          else {
              this.ratioA = valueToRatio(value, min, max);
          }
      }
      updateValue() {
          this.noUpdate = true;
          const { valA, valB } = this;
          this.value = !this.dualKnobs
              ? valA
              : {
                  lower: Math.min(valA, valB),
                  upper: Math.max(valA, valB)
              };
          this.noUpdate = false;
      }
      setFocus(knob) {
          if (this.el.shadowRoot) {
              const knobEl = this.el.shadowRoot.querySelector(knob === 'A' ? '.range-knob-a' : '.range-knob-b');
              if (knobEl) {
                  knobEl.focus();
              }
          }
      }
      hostData() {
          return {
              class: Object.assign({}, createColorClasses(this.color), { 'in-item': hostContext('ion-item', this.el), 'range-disabled': this.disabled, 'range-pressed': this.pressedKnob !== undefined, 'range-has-pin': this.pin })
          };
      }
      render() {
          const { min, max, step, ratioLower, ratioUpper } = this;
          const barStart = `${ratioLower * 100}%`;
          const barEnd = `${100 - ratioUpper * 100}%`;
          const doc = this.doc;
          const isRTL = doc.dir === 'rtl';
          const start = isRTL ? 'right' : 'left';
          const end = isRTL ? 'left' : 'right';
          const ticks = [];
          if (this.snaps) {
              for (let value = min; value <= max; value += step) {
                  const ratio = valueToRatio(value, min, max);
                  const tick = {
                      ratio,
                      active: ratio >= ratioLower && ratio <= ratioUpper,
                  };
                  tick[start] = `${ratio * 100}%`;
                  ticks.push(tick);
              }
          }
          const tickStyle = (tick) => {
              const style = {};
              style[start] = tick[start];
              return style;
          };
          const barStyle = () => {
              const style = {};
              style[start] = barStart;
              style[end] = barEnd;
              return style;
          };
          return [
              h("slot", { name: "start" }),
              h("div", { class: "range-slider", ref: el => this.rangeSlider = el },
                  ticks.map(tick => (h("div", { style: tickStyle(tick), role: "presentation", class: {
                          'range-tick': true,
                          'range-tick-active': tick.active
                      } }))),
                  h("div", { class: "range-bar", role: "presentation" }),
                  h("div", { class: "range-bar range-bar-active", role: "presentation", style: barStyle() }),
                  renderKnob(isRTL, {
                      knob: 'A',
                      pressed: this.pressedKnob === 'A',
                      value: this.valA,
                      ratio: this.ratioA,
                      pin: this.pin,
                      disabled: this.disabled,
                      handleKeyboard: this.handleKeyboard,
                      min,
                      max
                  }),
                  this.dualKnobs && renderKnob(isRTL, {
                      knob: 'B',
                      pressed: this.pressedKnob === 'B',
                      value: this.valB,
                      ratio: this.ratioB,
                      pin: this.pin,
                      disabled: this.disabled,
                      handleKeyboard: this.handleKeyboard,
                      min,
                      max
                  })),
              h("slot", { name: "end" })
          ];
      }
      static get is() { return "ion-range"; }
      static get encapsulation() { return "shadow"; }
      static get properties() { return {
          "color": {
              "type": String,
              "attr": "color"
          },
          "debounce": {
              "type": Number,
              "attr": "debounce",
              "watchCallbacks": ["debounceChanged"]
          },
          "disabled": {
              "type": Boolean,
              "attr": "disabled",
              "watchCallbacks": ["disabledChanged"]
          },
          "doc": {
              "context": "document"
          },
          "dualKnobs": {
              "type": Boolean,
              "attr": "dual-knobs"
          },
          "el": {
              "elementRef": true
          },
          "max": {
              "type": Number,
              "attr": "max",
              "watchCallbacks": ["maxChanged"]
          },
          "min": {
              "type": Number,
              "attr": "min",
              "watchCallbacks": ["minChanged"]
          },
          "mode": {
              "type": String,
              "attr": "mode"
          },
          "name": {
              "type": String,
              "attr": "name"
          },
          "pin": {
              "type": Boolean,
              "attr": "pin"
          },
          "pressedKnob": {
              "state": true
          },
          "queue": {
              "context": "queue"
          },
          "ratioA": {
              "state": true
          },
          "ratioB": {
              "state": true
          },
          "snaps": {
              "type": Boolean,
              "attr": "snaps"
          },
          "step": {
              "type": Number,
              "attr": "step"
          },
          "value": {
              "type": Number,
              "attr": "value",
              "mutable": true,
              "watchCallbacks": ["valueChanged"]
          }
      }; }
      static get events() { return [{
              "name": "ionChange",
              "method": "ionChange",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }, {
              "name": "ionStyle",
              "method": "ionStyle",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }, {
              "name": "ionFocus",
              "method": "ionFocus",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }, {
              "name": "ionBlur",
              "method": "ionBlur",
              "bubbles": true,
              "cancelable": true,
              "composed": true
          }]; }
      static get listeners() { return [{
              "name": "focusout",
              "method": "onBlur"
          }, {
              "name": "focusin",
              "method": "onFocus"
          }]; }
      static get style() { return "/**style-placeholder:ion-range:**/"; }
      static get styleMode() { return "/**style-id-placeholder:ion-range:**/"; }
  }
  function renderKnob(isRTL, { knob, value, ratio, min, max, disabled, pressed, pin, handleKeyboard }) {
      const start = isRTL ? 'right' : 'left';
      const knobStyle = () => {
          const style = {};
          style[start] = `${ratio * 100}%`;
          return style;
      };
      return (h("div", { onKeyDown: (ev) => {
              const key = ev.key;
              if (key === 'ArrowLeft' || key === 'ArrowDown') {
                  handleKeyboard(knob, false);
                  ev.preventDefault();
                  ev.stopPropagation();
              }
              else if (key === 'ArrowRight' || key === 'ArrowUp') {
                  handleKeyboard(knob, true);
                  ev.preventDefault();
                  ev.stopPropagation();
              }
          }, class: {
              'range-knob-handle': true,
              'range-knob-a': knob === 'A',
              'range-knob-b': knob === 'B',
              'range-knob-pressed': pressed,
              'range-knob-min': value === min,
              'range-knob-max': value === max
          }, style: knobStyle(), role: "slider", tabindex: disabled ? -1 : 0, "aria-valuemin": min, "aria-valuemax": max, "aria-disabled": disabled ? 'true' : null, "aria-valuenow": value },
          pin && h("div", { class: "range-pin", role: "presentation" }, Math.round(value)),
          h("div", { class: "range-knob", role: "presentation" })));
  }
  function ratioToValue(ratio, min, max, step) {
      let value = (max - min) * ratio;
      if (step > 0) {
          value = Math.round(value / step) * step + min;
      }
      return clamp(min, value, max);
  }
  function valueToRatio(value, min, max) {
      return clamp(0, (value - min) / (max - min), 1);
  }
  