1/*
2 * Copyright (C) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import { BaseElement, element } from '../BaseElement';
17import { replacePlaceholders } from '../utils/Template';
18
19let css = `
20<style>
21      /*
22       * Outer box style
23       */
24      :host{ 
25          box-sizing:border-box; 
26          display:flex;
27          
28      }
29      /*
30       * The mouse is missing
31       */
32      :host([disabled]){ 
33          opacity:0.8; 
34          cursor:not-allowed; 
35      }
36      /*
37       * Disable sliding
38       */
39      :host([disabled]) input[type="range"]{
40          pointer-events:none;
41      }
42      /*
43       * Currently the entire sliding vessel is controlled
44       */
45      #slider-con{ 
46          cursor:pointer;
47          display:flex;
48          align-items:center;
49          width:95%;
50          grid-auto-flow: row dense;
51          position: relative;
52      }
53      /*
54       * Display prompt information
55       */
56      :host([showtips]){
57          pointer-events:all;
58      }
59      
60      #slider{
61          background-color: var(--dark-background7,#D8D8D8);
62          z-index: 5;
63      }
64  
65      /*
66       * Slider basic style
67       */
68      input[type="range"]{
69          pointer-events:all;
70          margin:0 -5px;
71          width: 100%;
72          -webkit-appearance: none;
73          outline : 0;
74          background: rgba(0,0,0,0.1);
75          height: 10px;
76          border-radius:2px;
77          background: -webkit-linear-gradient(right, {1}, {2}) no-repeat;
78      }
79      
80      /*
81       * Slider-line slidedAble area component
82       */
83      input[type="range"]::-webkit-slider-runnable-track{
84          display: flex;
85          align-items: center;
86          position: relative;
87          height: 10px;
88          border-radius:5px;
89      }
90      
91       /*
92       * Slider slider component
93       */
94      input[type="range"]::-webkit-slider-thumb{
95          -webkit-appearance: none;
96          position: relative;
97          width:20px;
98          height:20px;
99          margin-top: -4px;
100          border-radius: 5px;
101          background:#999999;
102          transition:0.2s cubic-bezier(.12, .4, .29, 1.46);
103      }
104      
105      input[type="range"]:focus{
106          z-index:2;
107      }
108
109      :host(:focus-within) #slider-con,:host(:hover) #slider-con{
110          z-index:10
111      }
112      
113      :host([disabled]) #slider{ 
114          opacity:0.6; 
115      }
116      </style>
117`;
118const initHtmlStyle = (str: string | null, text: string | null): string => {
119  return replacePlaceholders(css, str!, text!);
120};
121
122@element('lit-slider')
123export class LitSlider extends BaseElement {
124  private litSliderStyle: LitSliderStyle | undefined | null;
125  private litSlider: HTMLInputElement | undefined | null;
126  private litSliderCon: HTMLDivElement | undefined | null;
127  private litResult: HTMLInputElement | undefined | null;
128  private slotEl: HTMLSlotElement | undefined | null;
129  private currentValue: number = 0;
130  private defaultTimeText: string | undefined | null;
131
132  static get observedAttributes(): string[] {
133    return ['percent', 'disabled-X', 'custom-slider', 'custom-line', 'custom-button', 'disabled'];
134  }
135
136  get sliderStyle(): LitSliderStyle {
137    if (this.litSliderStyle) {
138      return this.litSliderStyle;
139    } else {
140      return {
141        minRange: 0,
142        maxRange: 100,
143        defaultValue: '0',
144        resultUnit: '',
145        stepSize: 1,
146        lineColor: 'var(--dark-color3,#46B1E3)',
147        buttonColor: '#999999',
148      };
149    }
150  }
151
152  set disabled(value) {
153    if (value === null || value === false) {
154      this.removeAttribute('disabled');
155    } else {
156      this.setAttribute('disabled', '');
157    }
158  }
159
160  get disabled(): boolean {
161    return this.getAttribute('disabled') !== null;
162  }
163
164  set sliderStyle(value: LitSliderStyle) {
165    this.litSliderStyle = value;
166    this.currentValue = Number(value.defaultValue);
167    this.litSliderStyle.defaultValue = value.defaultValue;
168    if (this.litSliderStyle.resultUnit === 'h:m:s') {
169      let timeData = this.litSliderStyle.defaultValue.split(':');
170      let timeSize = Number(timeData[0]) * 3600 + Number(timeData[1]) * 60 + Number(timeData[2]);
171      this.defaultTimeText = timeSize.toString();
172      let defaultSize =
173        ((timeSize - this.litSliderStyle.minRange) * 100) /
174        (this.litSliderStyle.maxRange - this.litSliderStyle.minRange);
175      this.litSlider!.style.backgroundSize = defaultSize + '%';
176    } else {
177      this.defaultTimeText = this.litSliderStyle.defaultValue;
178      this.litSlider!.style.backgroundSize = '0%';
179      if (Number(this.litSliderStyle.defaultValue)) {
180        let defaultSize =
181          ((Number(this.litSliderStyle.defaultValue) - this.litSliderStyle.minRange) /
182            (this.litSliderStyle.maxRange - this.litSliderStyle.minRange)) *
183          100;
184        this.litSlider!.style.backgroundSize = defaultSize + '%';
185      }
186    }
187    let htmlInputElement = this.shadowRoot?.querySelector('#slider') as HTMLInputElement;
188    let attribute = htmlInputElement.getAttribute('type');
189    if (attribute === 'range') {
190      htmlInputElement!.setAttribute('value', this.defaultTimeText!);
191      htmlInputElement!.setAttribute('min', this.litSliderStyle!.minRange.toString());
192      htmlInputElement!.setAttribute('max', this.litSliderStyle!.maxRange.toString());
193      htmlInputElement!.setAttribute('step', this.litSliderStyle!.stepSize.toString());
194    }
195  }
196
197  get disabledX(): string {
198    return this.getAttribute('disabled-X') || '';
199  }
200
201  set disabledX(value: string) {
202    if (value) {
203      this.setAttribute('disabled-X', '');
204    } else {
205      this.removeAttribute('disabled-X');
206    }
207  }
208
209  get customSlider(): string {
210    return this.getAttribute('custom-slider') || '';
211  }
212
213  set customSlider(value: string) {
214    if (value) {
215      this.setAttribute('custom-slider', '');
216    } else {
217      this.removeAttribute('custom-slider');
218    }
219  }
220
221  get customLine(): string {
222    return this.getAttribute('custom-line') || '';
223  }
224
225  set customLine(value: string) {
226    this.setAttribute('custom-line', value);
227  }
228
229  get customButton(): string {
230    return this.getAttribute('custom-button') || '';
231  }
232
233  set customButton(value: string) {
234    this.setAttribute('custom-button', value);
235  }
236
237  get percent(): string {
238    return this.getAttribute('percent') || '';
239  }
240
241  set percent(value: string) {
242    this.setAttribute('percent', value);
243    let resultNumber =
244      ((Number(value) - this.sliderStyle!.minRange) * 100) / (this.sliderStyle!.maxRange - this.sliderStyle!.minRange);
245    this.litSlider!.style.backgroundSize = resultNumber + '%';
246  }
247
248  get resultUnit(): string {
249    return this.getAttribute('resultUnit') || '';
250  }
251
252  set resultUnit(value: string) {
253    this.setAttribute('resultUnit', value);
254  }
255
256  initElements(): void {
257    this.litSlider = this.shadowRoot?.querySelector('#slider') as HTMLInputElement;
258  }
259
260  initHtml(): string {
261    let htmlStyle = initHtmlStyle(
262      this.getAttribute('defaultColor') ? this.getAttribute('defaultColor') : '#46B1E3',
263      this.getAttribute('defaultColor') ? this.getAttribute('defaultColor') : '#46B1E3'
264    );
265    return `
266        ${htmlStyle}
267        <slot id="slot"></slot>
268        <div id='slider-con' dir="right">
269            <input id="slider" type="range" max="10000000">
270        </div>
271        `;
272  }
273
274  // It is called when the custom element is first inserted into the document DOM.
275  connectedCallback(): void {
276    this.slotEl = this.shadowRoot?.querySelector('#slot');
277    this.litSliderCon = this.shadowRoot?.querySelector('#slider-con');
278    // Add a slider for input event listeners
279    this.litSlider?.addEventListener('input', this.inputChangeEvent);
280    this.litSlider?.addEventListener('change', this.inputChangeEvent);
281    this.litSlider?.addEventListener('keydown', this.inputKeyDownEvent);
282    this.litSliderStyle = this.sliderStyle;
283  }
284
285  // @ts-ignore
286  inputKeyDownEvent = (ev): void => {
287    if (ev.key === '0' && ev.target.value.length === 1 && ev.target.value === '0') {
288      ev.preventDefault();
289    }
290  };
291
292  inputChangeEvent = (event: unknown): void => {
293    if (this.litSlider) {
294      this.currentValue = parseInt(this.litSlider?.value);
295      let resultNumber =
296        ((this.currentValue - this.litSliderStyle!.minRange) * 100) /
297        (this.litSliderStyle!.maxRange - this.litSliderStyle!.minRange);
298      this.percent = Number(resultNumber) + '%';
299      this.litSliderCon?.style.setProperty('percent', this.currentValue + '%');
300      let parentElement = this.parentNode as Element;
301      parentElement.setAttribute('percent', this.currentValue + '');
302      if (this.sliderStyle.resultUnit === 'h:m:s') {
303        this.litSlider!.style.backgroundSize = this.percent;
304      } else {
305        this.litSlider!.style.backgroundSize = this.percent;
306      }
307      this.parentElement!.setAttribute('percent', this.litSlider?.value);
308    }
309  };
310
311  disconnectedCallback(): void {
312    this.litSlider?.removeEventListener('input', this.inputChangeEvent);
313    this.litSlider?.removeEventListener('change', this.inputChangeEvent);
314    this.litSlider?.removeEventListener('change', this.inputKeyDownEvent);
315  }
316
317  adoptedCallback(): void {}
318
319  attributeChangedCallback(name: string, oldValue: string, newValue: string): void {
320    switch (name) {
321      case 'percent':
322        if (newValue === null || newValue === '0%') {
323          let parentElement = this.parentNode as Element;
324          parentElement?.removeAttribute('percent');
325        } else {
326          let parentElement = this.parentNode as Element;
327        }
328        break;
329      default:
330        break;
331    }
332  }
333
334  formatSeconds(value: string): string {
335    let result = parseInt(value);
336    let hours = Math.floor(result / 3600) < 10 ? '0' + Math.floor(result / 3600) : Math.floor(result / 3600);
337    let minute =
338      Math.floor((result / 60) % 60) < 10 ? '0' + Math.floor((result / 60) % 60) : Math.floor((result / 60) % 60);
339    let second = Math.floor(result % 60) < 10 ? '0' + Math.floor(result % 60) : Math.floor(result % 60);
340    let resultTime = '';
341    if (hours === '00') {
342      resultTime += '00:';
343    } else {
344      resultTime += `${hours}:`;
345    }
346    if (minute === '00') {
347      resultTime += '00:';
348    } else {
349      resultTime += `${minute}:`;
350    }
351    resultTime += `${second}`;
352    return resultTime;
353  }
354}
355
356export interface LitSliderStyle {
357  minRange: number;
358  maxRange: number;
359  defaultValue: string;
360  resultUnit: string;
361  stepSize: number;
362  lineColor?: string;
363  buttonColor?: string;
364}
365
366export interface LitSliderLineStyle {
367  lineWith: number;
368  lineHeight: number;
369  border?: string;
370  borderRadiusValue?: number;
371  lineChangeColor?: string;
372}
373
374export interface LitSliderButtonStyle {
375  buttonWith: number;
376  buttonHeight: number;
377  border?: string;
378  borderRadiusValue?: number;
379  buttonChangeColor?: string;
380}
381