PageRenderTime 52ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/bower_components/paper-slider/paper-slider.html

https://gitlab.com/jofinckh/htwg-thumbnail
HTML | 755 lines | 596 code | 106 blank | 53 comment | 0 complexity | 507448d5a993ea4bee6715dbcd74e28e MD5 | raw file
  1. <!--
  2. @license
  3. Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
  4. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
  5. The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
  6. The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
  7. Code distributed by Google as part of the polymer project is also
  8. subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
  9. -->
  10. <link rel="import" href="../polymer/polymer.html">
  11. <link rel="import" href="../iron-a11y-keys-behavior/iron-a11y-keys-behavior.html">
  12. <link rel="import" href="../iron-flex-layout/iron-flex-layout.html">
  13. <link rel="import" href="../iron-form-element-behavior/iron-form-element-behavior.html">
  14. <link rel="import" href="../iron-range-behavior/iron-range-behavior.html">
  15. <link rel="import" href="../paper-behaviors/paper-inky-focus-behavior.html">
  16. <link rel="import" href="../paper-input/paper-input.html">
  17. <link rel="import" href="../paper-progress/paper-progress.html">
  18. <link rel="import" href="../paper-styles/color.html">
  19. <!--
  20. Material design: [Sliders](https://www.google.com/design/spec/components/sliders.html)
  21. `paper-slider` allows user to select a value from a range of values by
  22. moving the slider thumb. The interactive nature of the slider makes it a
  23. great choice for settings that reflect intensity levels, such as volume,
  24. brightness, or color saturation.
  25. Example:
  26. <paper-slider></paper-slider>
  27. Use `min` and `max` to specify the slider range. Default is 0 to 100.
  28. Example:
  29. <paper-slider min="10" max="200" value="110"></paper-slider>
  30. ### Styling
  31. The following custom properties and mixins are available for styling:
  32. Custom property | Description | Default
  33. ----------------|-------------|----------
  34. `--paper-slider-bar-color` | The background color of the slider | `transparent`
  35. `--paper-slider-active-color` | The progress bar color | `--google-blue-700`
  36. `--paper-slider-secondary-color` | The secondary progress bar color | `--google-blue-300`
  37. `--paper-slider-knob-color` | The knob color | `--google-blue-700`
  38. `--paper-slider-disabled-knob-color` | The disabled knob color | `--paper-grey-400`
  39. `--paper-slider-pin-color` | The pin color | `--google-blue-700`
  40. `--paper-slider-font-color` | The pin's text color | `#fff`
  41. `--paper-slider-disabled-active-color` | The disabled progress bar color | `--paper-grey-400`
  42. `--paper-slider-disabled-secondary-color` | The disabled secondary progress bar color | `--paper-grey-400`
  43. `--paper-slider-knob-start-color` | The fill color of the knob at the far left | `transparent`
  44. `--paper-slider-knob-start-border-color` | The border color of the knob at the far left | `--paper-grey-400`
  45. `--paper-slider-pin-start-color` | The color of the pin at the far left | `--paper-grey-400`
  46. `--paper-slider-height` | Height of the progress bar | `2px`
  47. `--paper-slider-input` | Mixin applied to the input in editable mode | `{}`
  48. @group Paper Elements
  49. @element paper-slider
  50. @demo demo/index.html
  51. @hero hero.svg
  52. -->
  53. <dom-module id="paper-slider">
  54. <template strip-whitespace>
  55. <style>
  56. :host {
  57. @apply(--layout);
  58. @apply(--layout-justified);
  59. @apply(--layout-center);
  60. width: 200px;
  61. cursor: default;
  62. -webkit-user-select: none;
  63. -moz-user-select: none;
  64. -ms-user-select: none;
  65. user-select: none;
  66. -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
  67. --paper-progress-active-color: var(--paper-slider-active-color, --google-blue-700);
  68. --paper-progress-secondary-color: var(--paper-slider-secondary-color, --google-blue-300);
  69. --paper-progress-disabled-active-color: var(--paper-slider-disabled-active-color, --paper-grey-400);
  70. --paper-progress-disabled-secondary-color: var(--paper-slider-disabled-secondary-color, --paper-grey-400);
  71. }
  72. /* focus shows the ripple */
  73. :host(:focus) {
  74. outline: none;
  75. }
  76. #sliderContainer {
  77. position: relative;
  78. width: 100%;
  79. height: calc(30px + var(--paper-slider-height, 2px));
  80. margin-left: calc(15px + var(--paper-slider-height, 2px)/2);
  81. margin-right: calc(15px + var(--paper-slider-height, 2px)/2);
  82. }
  83. #sliderContainer:focus {
  84. outline: 0;
  85. }
  86. #sliderContainer.editable {
  87. margin-top: 12px;
  88. margin-bottom: 12px;
  89. }
  90. .bar-container {
  91. position: absolute;
  92. top: 0;
  93. bottom: 0;
  94. left: 0;
  95. right: 0;
  96. overflow: hidden;
  97. }
  98. .ring > .bar-container {
  99. left: calc(5px + var(--paper-slider-height, 2px)/2);
  100. transition: left 0.18s ease;
  101. }
  102. .ring.expand.dragging > .bar-container {
  103. transition: none;
  104. }
  105. .ring.expand:not(.pin) > .bar-container {
  106. left: calc(8px + var(--paper-slider-height, 2px)/2);
  107. }
  108. #sliderBar {
  109. padding: 15px 0;
  110. width: 100%;
  111. background-color: var(--paper-slider-bar-color, transparent);
  112. --paper-progress-container-color: var(--paper-grey-400);
  113. --paper-progress-height: var(--paper-slider-height, 2px);
  114. }
  115. .slider-markers {
  116. position: absolute;
  117. top: calc(14px + var(--paper-slider-height,2px)/2);
  118. height: var(--paper-slider-height, 2px);
  119. left: 0;
  120. right: -1px;
  121. box-sizing: border-box;
  122. pointer-events: none;
  123. @apply(--layout-horizontal);
  124. }
  125. .slider-marker {
  126. @apply(--layout-flex);
  127. }
  128. .slider-markers::after,
  129. .slider-marker::after {
  130. content: "";
  131. display: block;
  132. margin-left: -1px;
  133. width: 2px;
  134. height: 2px;
  135. border-radius: 50%;
  136. background-color: black;
  137. }
  138. #sliderKnob {
  139. position: absolute;
  140. left: 0;
  141. top: 0;
  142. margin-left: calc(-15px - var(--paper-slider-height, 2px)/2);
  143. width: calc(30px + var(--paper-slider-height, 2px));
  144. height: calc(30px + var(--paper-slider-height, 2px));
  145. }
  146. .transiting > #sliderKnob {
  147. transition: left 0.08s ease;
  148. }
  149. #sliderKnob:focus {
  150. outline: none;
  151. }
  152. #sliderKnob.dragging {
  153. transition: none;
  154. }
  155. .snaps > #sliderKnob.dragging {
  156. transition: -webkit-transform 0.08s ease;
  157. transition: transform 0.08s ease;
  158. }
  159. #sliderKnobInner {
  160. margin: 10px;
  161. width: calc(100% - 20px);
  162. height: calc(100% - 20px);
  163. background-color: var(--paper-slider-knob-color, --google-blue-700);
  164. border: 2px solid var(--paper-slider-knob-color, --google-blue-700);
  165. border-radius: 50%;
  166. -moz-box-sizing: border-box;
  167. box-sizing: border-box;
  168. transition-property: -webkit-transform, background-color, border;
  169. transition-property: transform, background-color, border;
  170. transition-duration: 0.18s;
  171. transition-timing-function: ease;
  172. }
  173. .expand:not(.pin) > #sliderKnob > #sliderKnobInner {
  174. -webkit-transform: scale(1.5);
  175. transform: scale(1.5);
  176. }
  177. .ring > #sliderKnob > #sliderKnobInner {
  178. background-color: var(--paper-slider-knob-start-color, transparent);
  179. border: 2px solid var(--paper-slider-knob-start-border-color, --paper-grey-400);
  180. }
  181. #sliderKnobInner::before {
  182. background-color: var(--paper-slider-pin-color, --google-blue-700);
  183. }
  184. .pin > #sliderKnob > #sliderKnobInner::before {
  185. content: "";
  186. position: absolute;
  187. top: 0;
  188. left: 50%;
  189. margin-left: -13px;
  190. width: 26px;
  191. height: 26px;
  192. border-radius: 50% 50% 50% 0;
  193. -webkit-transform: rotate(-45deg) scale(0) translate(0);
  194. transform: rotate(-45deg) scale(0) translate(0);
  195. }
  196. #sliderKnobInner::before,
  197. #sliderKnobInner::after {
  198. transition: -webkit-transform .18s ease, background-color .18s ease;
  199. transition: transform .18s ease, background-color .18s ease;
  200. }
  201. .pin.ring > #sliderKnob > #sliderKnobInner::before {
  202. background-color: var(--paper-slider-pin-start-color, --paper-grey-400);
  203. }
  204. .pin.expand > #sliderKnob > #sliderKnobInner::before {
  205. -webkit-transform: rotate(-45deg) scale(1) translate(17px, -17px);
  206. transform: rotate(-45deg) scale(1) translate(17px, -17px);
  207. }
  208. .pin > #sliderKnob > #sliderKnobInner::after {
  209. content: attr(value);
  210. position: absolute;
  211. top: 0;
  212. left: 50%;
  213. margin-left: -16px;
  214. width: 32px;
  215. height: 26px;
  216. text-align: center;
  217. color: var(--paper-slider-font-color, #fff);
  218. font-size: 10px;
  219. -webkit-transform: scale(0) translate(0);
  220. transform: scale(0) translate(0);
  221. }
  222. .pin.expand > #sliderKnob > #sliderKnobInner::after {
  223. -webkit-transform: scale(1) translate(0, -17px);
  224. transform: scale(1) translate(0, -17px);
  225. }
  226. /* paper-input */
  227. .slider-input {
  228. width: 50px;
  229. overflow: hidden;
  230. --paper-input-container-input: {
  231. text-align: center;
  232. };
  233. @apply(--paper-slider-input);
  234. }
  235. /* disabled state */
  236. #sliderContainer.disabled {
  237. pointer-events: none;
  238. }
  239. .disabled > #sliderKnob > #sliderKnobInner {
  240. background-color: var(--paper-slider-disabled-knob-color, --paper-grey-400);
  241. border: 2px solid var(--paper-slider-disabled-knob-color, --paper-grey-400);
  242. -webkit-transform: scale3d(0.75, 0.75, 1);
  243. transform: scale3d(0.75, 0.75, 1);
  244. }
  245. .disabled.ring > #sliderKnob > #sliderKnobInner {
  246. background-color: var(--paper-slider-knob-start-color, transparent);
  247. border: 2px solid var(--paper-slider-knob-start-border-color, --paper-grey-400);
  248. }
  249. paper-ripple {
  250. color: var(--paper-slider-knob-color, --google-blue-700);
  251. }
  252. </style>
  253. <div id="sliderContainer"
  254. class$="[[_getClassNames(disabled, pin, snaps, immediateValue, min, expand, dragging, transiting, editable)]]">
  255. <div class="bar-container">
  256. <paper-progress
  257. disabled$="[[disabled]]"
  258. id="sliderBar"
  259. aria-hidden="true"
  260. min="[[min]]"
  261. max="[[max]]"
  262. step="[[step]]"
  263. value="[[immediateValue]]"
  264. secondary-progress="[[secondaryProgress]]"
  265. on-down="_bardown"
  266. on-up="_resetKnob"
  267. on-track="_onTrack">
  268. </paper-progress>
  269. </div>
  270. <template is="dom-if" if="[[snaps]]">
  271. <div class="slider-markers">
  272. <template is="dom-repeat" items="[[markers]]">
  273. <div class="slider-marker"></div>
  274. </template>
  275. </div>
  276. </template>
  277. <div id="sliderKnob"
  278. on-down="_knobdown"
  279. on-up="_resetKnob"
  280. on-track="_onTrack"
  281. on-transitionend="_knobTransitionEnd">
  282. <div id="sliderKnobInner" value$="[[immediateValue]]"></div>
  283. </div>
  284. </div>
  285. <template is="dom-if" if="[[editable]]">
  286. <paper-input
  287. id="input"
  288. type="number"
  289. step="[[step]]"
  290. min="[[min]]"
  291. max="[[max]]"
  292. class="slider-input"
  293. disabled$="[[disabled]]"
  294. value="[[immediateValue]]"
  295. on-change="_changeValue"
  296. on-keydown="_inputKeyDown"
  297. no-label-float>
  298. </paper-input>
  299. </template>
  300. </template>
  301. <script>
  302. Polymer({
  303. is: 'paper-slider',
  304. behaviors: [
  305. Polymer.IronA11yKeysBehavior,
  306. Polymer.IronFormElementBehavior,
  307. Polymer.PaperInkyFocusBehavior,
  308. Polymer.IronRangeBehavior
  309. ],
  310. properties: {
  311. /**
  312. * If true, the slider thumb snaps to tick marks evenly spaced based
  313. * on the `step` property value.
  314. */
  315. snaps: {
  316. type: Boolean,
  317. value: false,
  318. notify: true
  319. },
  320. /**
  321. * If true, a pin with numeric value label is shown when the slider thumb
  322. * is pressed. Use for settings for which users need to know the exact
  323. * value of the setting.
  324. */
  325. pin: {
  326. type: Boolean,
  327. value: false,
  328. notify: true
  329. },
  330. /**
  331. * The number that represents the current secondary progress.
  332. */
  333. secondaryProgress: {
  334. type: Number,
  335. value: 0,
  336. notify: true,
  337. observer: '_secondaryProgressChanged'
  338. },
  339. /**
  340. * If true, an input is shown and user can use it to set the slider value.
  341. */
  342. editable: {
  343. type: Boolean,
  344. value: false
  345. },
  346. /**
  347. * The immediate value of the slider. This value is updated while the user
  348. * is dragging the slider.
  349. */
  350. immediateValue: {
  351. type: Number,
  352. value: 0,
  353. readOnly: true,
  354. notify: true
  355. },
  356. /**
  357. * The maximum number of markers
  358. */
  359. maxMarkers: {
  360. type: Number,
  361. value: 0,
  362. notify: true,
  363. observer: '_maxMarkersChanged'
  364. },
  365. /**
  366. * If true, the knob is expanded
  367. */
  368. expand: {
  369. type: Boolean,
  370. value: false,
  371. readOnly: true
  372. },
  373. /**
  374. * True when the user is dragging the slider.
  375. */
  376. dragging: {
  377. type: Boolean,
  378. value: false,
  379. readOnly: true
  380. },
  381. transiting: {
  382. type: Boolean,
  383. value: false,
  384. readOnly: true
  385. },
  386. markers: {
  387. type: Array,
  388. readOnly: true,
  389. value: []
  390. },
  391. },
  392. observers: [
  393. '_updateKnob(value, min, max, snaps, step)',
  394. '_valueChanged(value)',
  395. '_immediateValueChanged(immediateValue)'
  396. ],
  397. hostAttributes: {
  398. role: 'slider',
  399. tabindex: 0
  400. },
  401. keyBindings: {
  402. 'left down pagedown home': '_decrementKey',
  403. 'right up pageup end': '_incrementKey'
  404. },
  405. ready: function() {
  406. // issue polymer/polymer#1305
  407. this.async(function() {
  408. this._updateKnob(this.value);
  409. }, 1);
  410. },
  411. /**
  412. * Increases value by `step` but not above `max`.
  413. * @method increment
  414. */
  415. increment: function() {
  416. this.value = this._clampValue(this.value + this.step);
  417. },
  418. /**
  419. * Decreases value by `step` but not below `min`.
  420. * @method decrement
  421. */
  422. decrement: function() {
  423. this.value = this._clampValue(this.value - this.step);
  424. },
  425. _updateKnob: function(value, min, max, snaps, step) {
  426. this.setAttribute('aria-valuemin', min);
  427. this.setAttribute('aria-valuemax', max);
  428. this.setAttribute('aria-valuenow', value);
  429. this._positionKnob(this._calcRatio(value));
  430. },
  431. _valueChanged: function() {
  432. this.fire('value-change');
  433. },
  434. _immediateValueChanged: function() {
  435. if (this.dragging) {
  436. this.fire('immediate-value-change');
  437. } else {
  438. this.value = this.immediateValue;
  439. }
  440. },
  441. _secondaryProgressChanged: function() {
  442. this.secondaryProgress = this._clampValue(this.secondaryProgress);
  443. },
  444. _expandKnob: function() {
  445. this._setExpand(true);
  446. },
  447. _resetKnob: function() {
  448. this.cancelDebouncer('expandKnob');
  449. this._setExpand(false);
  450. },
  451. _positionKnob: function(ratio) {
  452. this._setImmediateValue(this._calcStep(this._calcKnobPosition(ratio)));
  453. this._setRatio(this._calcRatio(this.immediateValue));
  454. this.$.sliderKnob.style.left = (this.ratio * 100) + '%';
  455. if (this.dragging) {
  456. this._knobstartx = this.ratio * this._w;
  457. this.translate3d(0, 0, 0, this.$.sliderKnob);
  458. }
  459. },
  460. _calcKnobPosition: function(ratio) {
  461. return (this.max - this.min) * ratio + this.min;
  462. },
  463. _onTrack: function(event) {
  464. event.stopPropagation();
  465. switch (event.detail.state) {
  466. case 'start':
  467. this._trackStart(event);
  468. break;
  469. case 'track':
  470. this._trackX(event);
  471. break;
  472. case 'end':
  473. this._trackEnd();
  474. break;
  475. }
  476. },
  477. _trackStart: function(event) {
  478. this._w = this.$.sliderBar.offsetWidth;
  479. this._x = this.ratio * this._w;
  480. this._startx = this._x;
  481. this._knobstartx = this._startx;
  482. this._minx = - this._startx;
  483. this._maxx = this._w - this._startx;
  484. this.$.sliderKnob.classList.add('dragging');
  485. this._setDragging(true);
  486. },
  487. _trackX: function(e) {
  488. if (!this.dragging) {
  489. this._trackStart(e);
  490. }
  491. var dx = Math.min(this._maxx, Math.max(this._minx, e.detail.dx));
  492. this._x = this._startx + dx;
  493. var immediateValue = this._calcStep(this._calcKnobPosition(this._x / this._w));
  494. this._setImmediateValue(immediateValue);
  495. // update knob's position
  496. var translateX = ((this._calcRatio(this.immediateValue) * this._w) - this._knobstartx);
  497. this.translate3d(translateX + 'px', 0, 0, this.$.sliderKnob);
  498. },
  499. _trackEnd: function() {
  500. var s = this.$.sliderKnob.style;
  501. this.$.sliderKnob.classList.remove('dragging');
  502. this._setDragging(false);
  503. this._resetKnob();
  504. this.value = this.immediateValue;
  505. s.transform = s.webkitTransform = '';
  506. this.fire('change');
  507. },
  508. _knobdown: function(event) {
  509. this._expandKnob();
  510. // cancel selection
  511. event.preventDefault();
  512. // set the focus manually because we will called prevent default
  513. this.focus();
  514. },
  515. _bardown: function(event) {
  516. this._w = this.$.sliderBar.offsetWidth;
  517. var rect = this.$.sliderBar.getBoundingClientRect();
  518. var ratio = (event.detail.x - rect.left) / this._w;
  519. var prevRatio = this.ratio;
  520. this._setTransiting(true);
  521. this._positionKnob(ratio);
  522. this.debounce('expandKnob', this._expandKnob, 60);
  523. // if the ratio doesn't change, sliderKnob's animation won't start
  524. // and `_knobTransitionEnd` won't be called
  525. // Therefore, we need to manually update the `transiting` state
  526. if (prevRatio === this.ratio) {
  527. this._setTransiting(false);
  528. }
  529. this.async(function() {
  530. this.fire('change');
  531. });
  532. // cancel selection
  533. event.preventDefault();
  534. // set the focus manually because we will called prevent default
  535. this.focus();
  536. },
  537. _knobTransitionEnd: function(event) {
  538. if (event.target === this.$.sliderKnob) {
  539. this._setTransiting(false);
  540. }
  541. },
  542. _maxMarkersChanged: function(maxMarkers) {
  543. if (!this.snaps) {
  544. this._setMarkers([]);
  545. }
  546. var steps = Math.round((this.max - this.min) / this.step);
  547. if (steps > maxMarkers) {
  548. steps = maxMarkers;
  549. }
  550. this._setMarkers(new Array(steps));
  551. },
  552. _mergeClasses: function(classes) {
  553. return Object.keys(classes).filter(
  554. function(className) {
  555. return classes[className];
  556. }).join(' ');
  557. },
  558. _getClassNames: function() {
  559. return this._mergeClasses({
  560. disabled: this.disabled,
  561. pin: this.pin,
  562. snaps: this.snaps,
  563. ring: this.immediateValue <= this.min,
  564. expand: this.expand,
  565. dragging: this.dragging,
  566. transiting: this.transiting,
  567. editable: this.editable
  568. });
  569. },
  570. _incrementKey: function(event) {
  571. if (!this.disabled) {
  572. if (event.detail.key === 'end') {
  573. this.value = this.max;
  574. } else {
  575. this.increment();
  576. }
  577. this.fire('change');
  578. }
  579. },
  580. _decrementKey: function(event) {
  581. if (!this.disabled) {
  582. if (event.detail.key === 'home') {
  583. this.value = this.min;
  584. } else {
  585. this.decrement();
  586. }
  587. this.fire('change');
  588. }
  589. },
  590. _changeValue: function(event) {
  591. this.value = event.target.value;
  592. this.fire('change');
  593. },
  594. _inputKeyDown: function(event) {
  595. event.stopPropagation();
  596. },
  597. // create the element ripple inside the `sliderKnob`
  598. _createRipple: function() {
  599. this._rippleContainer = this.$.sliderKnob;
  600. return Polymer.PaperInkyFocusBehaviorImpl._createRipple.call(this);
  601. },
  602. // Hide the ripple when user is not interacting with keyboard.
  603. // This behavior is different from other ripple-y controls, but is
  604. // according to spec: https://www.google.com/design/spec/components/sliders.html
  605. _focusedChanged: function(receivedFocusFromKeyboard) {
  606. if (receivedFocusFromKeyboard) {
  607. this.ensureRipple();
  608. }
  609. if (this.hasRipple()) {
  610. // note, ripple must be un-hidden prior to setting `holdDown`
  611. if (receivedFocusFromKeyboard) {
  612. this._ripple.style.display = '';
  613. } else {
  614. this._ripple.style.display = 'none';
  615. }
  616. this._ripple.holdDown = receivedFocusFromKeyboard;
  617. }
  618. }
  619. });
  620. /**
  621. * Fired when the slider's value changes.
  622. *
  623. * @event value-change
  624. */
  625. /**
  626. * Fired when the slider's immediateValue changes.
  627. *
  628. * @event immediate-value-change
  629. */
  630. /**
  631. * Fired when the slider's value changes due to user interaction.
  632. *
  633. * Changes to the slider's value due to changes in an underlying
  634. * bound variable will not trigger this event.
  635. *
  636. * @event change
  637. */
  638. </script>
  639. </dom-module>