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