UNPKG

isu-elements

Version:

Polymer components for building web apps.

490 lines (463 loc) 13.1 kB
import { html, PolymerElement } from '@polymer/polymer' import './isu-input.js' /** Example: ```html <isu-slider show-tooltip></isu-slider> <isu-slider value="50" show-tooltip></isu-slider> <isu-slider></isu-slider> <isu-slider value="70" disabled></isu-slider> <isu-slider value="50" step="10" show-tooltip min="-10" max="100"></isu-slider> <isu-slider show-tooltip show-input></isu-slider> <isu-slider vertical height="200px" value="50" show-input show-tooltip></isu-slider> ``` * * @customElement * @polymer * @demo demo/isu-slider/index.html */ class IsuSlider extends PolymerElement { static get is () { return 'isu-slider' } static get template () { return html` <style> :host { --slider-height: 6px; } :host .isu-slider { position: relative; } :host .isu-slider::after { display: table; content: ""; } :host .isu-slider::before { display: table; content: ""; } :host .isu-slider-runway { width: 100%; height: var(--slider-height); margin: 16px 0; background-color: #e4e7ed; border-radius: 3px; position: relative; cursor: pointer; vertical-align: middle; } :host .isu-slider.is-vertical .isu-slider-runway { width: 6px; height: 100%; margin: 0 16px; } :host .isu-slider-runway.disabled { cursor: default; } :host .isu-slider-runway.hasInput { width: calc(100% - 80px); } :host .isu-slider-bar { height: 6px; background-color: #409eff; border-top-left-radius: 3px; border-bottom-left-radius: 3px; position: absolute; } :host .isu-slider.is-vertical .isu-slider-bar { width: 6px; height: auto; border-radius: 0 0 3px 3px; } :host .isu-slider-runway.disabled .isu-slider-bar { background-color: #c0c4cc; } :host .isu-slider-button-wrapper { height: 36px; width: 36px; position: absolute; z-index: 1001; top: -15px; transform: translateX(-50%); background-color: transparent; text-align: center; user-select: none; line-height: normal; } :host .isu-slider.is-vertical .isu-slider-button-wrapper { top: auto; left: -15px; transform: translateY(50%); } :host .isu-slider-button { width: 16px; height: 16px; border: 2px solid #409eff; background-color: #fff; border-radius: 50%; transition: .2s; user-select: none; margin: 8px auto; } :host .isu-slider-runway.disabled .isu-slider-button { border-color: #c0c4cc; } :host .isu-slider-button:hover { cursor: grab; transform: scale(1.2); } :host .isu-slider-runway.disabled .isu-slider-button:hover { cursor: not-allowed; transform: scale(1); } :host .isu-slider-tooltip { position: absolute; top: -100%; left: 0; z-index: 1001; padding: 10px; border-radius: 4px; font-size: 12px; background: #303133; color: #FFF; text-align: center; min-width: 100%; box-sizing: border-box; } :host .isu-slider-tooltip-arrow { position: absolute; display: inline-block; width: 0; height: 0; border: 6px solid transparent; border-top: 6px solid #303133; top: 100%; left: 50%; transform: translate(-50%); } :host .isu-slider-input { width: 120px; position: absolute; right: 0; top: 0; height: 34px; } :host .isu-slider.is-vertical .isu-slider-input { position: static; margin-top: 10px; } </style> <div class$="[[getIsuSliderClass(vertical)]]"> <div id="slider" class$="[[getSliderRunwayClass(disabled)]]" style$="[[getHasInputStyle(showInput, inputSize)]] [[getVerticalStyle(vertical, height)]]" on-click="onSliderClick" > <div class="isu-slider-bar" style$="[[barStyle]]"></div> <div id="sliderButtonWrapper" class="isu-slider-button-wrapper" style$="[[buttonStyle]]" on-mousedown="handleMouseDown" on-mouseenter="handleMouseEnter" on-mouseleave="handleMouseLeave" > <div class="isu-slider-button"></div> <template is="dom-if" if="[[isShowToolTip(showTooltip, dragging, hovering)]]"> <div class="isu-slider-tooltip"> [[value]] <div class="isu-slider-tooltip-arrow"></div> </div> </template> </div> </div> <template is="dom-if" if="[[showInput]]"> <isu-input class="isu-slider-input" style$=[[getInputStyle(inputSize)]] type="number" min="{{min}}" max="{{max}}" value={{value}}></isu-input> </template> </div> ` } static get properties () { return { /** * min value * @type {Number} * @default 0 */ min: { type: Number, value: 0 }, /** * max value * @type {Number} * @default 100 */ max: { type: Number, value: 100 }, /** * Current value * @type {Number} * @default 0 */ value: { type: Number, value: 0, observer: 'onValueChange' }, /** * Whether to disable * @type {Boolean} * @default false */ disabled: { type: Boolean, value: false }, /** * step * @type {Number} * @default 1 */ step: { type: Number, value: 1 }, /** * Whether to display the input box * @type {Boolean} * @default false */ showInput: { type: Boolean, value: false }, /** * Input box size, the value must be one of large, medium, small, and mini * @type {string} * @default small */ inputSize: { type: String, value: 'small', observer: 'onInputSizeChange' }, /** * Whether to display the tip * @type {Boolean} * @default false */ showTooltip: { type: Boolean, value: false }, /** * direction * @type {Boolean} * @default false */ vertical: { type: Boolean, value: false }, /** * Vertical height */ height: { type: String }, barStyle: { type: String, value: '' }, buttonStyle: { type: String, value: '' }, dragging: { type: Boolean, value: false }, hovering: { type: Boolean, value: false }, sliderSize: { type: Number, value: 0 }, startPosition: { type: Number, value: 0, readOnly: true }, startX: { type: Number, value: 0, readOnly: true }, startY: { type: Number, value: 0, readOnly: true }, precision: { type: Number, readOnly: true } } } static get observers () { return [ 'computeValue(min, max)' ] } getIsuSliderClass (vertical) { return vertical ? 'isu-slider is-vertical' : 'isu-slider' } getVerticalStyle (vertical, height) { if (vertical) { return `height: ${height};` } } isShowToolTip (showTooltip, dragging, hovering) { if (!showTooltip) { return false } if (dragging || hovering) { return true } } handleMouseDown (e) { if (this.disabled) return e.preventDefault() this.onDragStart(e) window.addEventListener('mousemove', this.onDragging.bind(this)) window.addEventListener('mouseup', this.onDragEnd.bind(this)) window.addEventListener('contextmenu', this.onDragEnd.bind(this)) } handleMouseEnter () { this.hovering = true } handleMouseLeave () { this.hovering = false } onDragStart (e) { this.dragging = true const { clientX, clientY } = e if (this.vertical) { this._setStartY(clientY) } else { this._setStartX(clientX) } const currentPosition = `${(this.value - this.min) / (this.max - this.min) * 100}%` this._setStartPosition(parseFloat(currentPosition)) } onDragEnd () { this.dragging = false window.removeEventListener('mousemove', this.onDragging.bind(this)) window.removeEventListener('mouseup', this.onDragEnd.bind(this)) window.removeEventListener('contextmenu', this.onDragEnd.bind(this)) } onDragging (e) { if (this.dragging) { if (!this.sliderSize) { this.setSliderSize() } const { clientX, clientY } = e let diff = 0 if (this.vertical) { diff = (this.startY - clientY) / this.sliderSize * 100 } else { diff = (clientX - this.startX) / this.sliderSize * 100 } const newPosition = this.startPosition + diff this.setValue(newPosition) } } getSliderRunwayClass (disabled) { return disabled ? 'isu-slider-runway disabled' : 'isu-slider-runway' } getHasInputStyle (showInput, inputSize) { if (!showInput) return '' if (this.vertical) return '' const styleText = 'margin-right: 20px' const sizeMap = { large: 'calc(100% - 160px)', medium: 'calc(100% - 140px)', small: 'calc(100% - 100px)', mini: 'calc(100% - 80px)' } return `width: ${sizeMap[inputSize]}; ${styleText}` } getInputStyle (inputSize) { const sizeMap = { large: '140px', medium: '120px', small: '80px', mini: '60px' } return `width: ${sizeMap[inputSize]}` } computeValue (min, max) { this.value = this.value < min ? min : this.value > max ? max : this.value } onValueChange () { const position = 100 * (this.value - this.min) / (this.max - this.min) this.setBarAndButtonStyle(position) } setBarAndButtonStyle (position) { position = position < 0 ? 0 : position > 100 ? 100 : position position = `${position}%` this.barStyle = this.vertical ? `height: ${position}; bottom: 0%;` : `width: ${position}` this.buttonStyle = this.vertical ? `bottom: ${position}` : `left: ${position}` } onSliderClick (e) { if (this.disabled) return if (!this.sliderSize) { this.setSliderSize() } const { bottom, left } = this.$.slider.getBoundingClientRect() if (this.vertical) { this.setValue((bottom - e.clientY) / this.sliderSize * 100) } else { this.setValue((e.clientX - left) / this.sliderSize * 100) } } setValue (newPosition) { if (newPosition === null || isNaN(newPosition)) return if (newPosition < 0) { newPosition = 0 } else if (newPosition > 100) { newPosition = 100 } if (!this.precision) { this.setPrecision() } const lengthPerStep = 100 / ((this.max - this.min) / this.step) const steps = Math.round(newPosition / lengthPerStep) let value = steps * lengthPerStep * (this.max - this.min) * 0.01 + this.min value = parseFloat(value.toFixed(this.precision)) this.value = value } setSliderSize () { this.sliderSize = this.vertical ? this.$.slider.clientHeight : this.$.slider.clientWidth } setPrecision () { const value = [this.min, this.max, this.step].map(item => { const decimal = ('' + item).split('.')[1] return decimal ? decimal.length : 0 }) this._setPrecision(Math.max.apply(null, value)) } onInputSizeChange (size) { const sizeArray = ['large', 'medium', 'small', 'mini'] if (sizeArray.indexOf(size) === -1) { throw new Error('The "inputSize" value must be one of large, medium, small, and mini.') } } } window.customElements.define(IsuSlider.is, IsuSlider)