@refinitiv-ui/elements 6.14.2 → 6.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1313 +1,2 @@
1
- import { __decorate } from "tslib";
2
- import { ControlElement, WarningNotice, css, html, nothing } from '@refinitiv-ui/core';
3
- import { customElement } from '@refinitiv-ui/core/decorators/custom-element.js';
4
- import { property } from '@refinitiv-ui/core/decorators/property.js';
5
- import { query } from '@refinitiv-ui/core/decorators/query.js';
6
- import { state } from '@refinitiv-ui/core/decorators/state.js';
7
- import { createRef, ref } from '@refinitiv-ui/core/directives/ref.js';
8
- import { styleMap } from '@refinitiv-ui/core/directives/style-map.js';
9
- import '@refinitiv-ui/phrasebook/locale/en/slider.js';
10
- import { translate } from '@refinitiv-ui/translate';
11
- import '../number-field/index.js';
12
- import { VERSION } from '../version.js';
13
- import { Direction, SliderDataName } from './constants.js';
14
- import { clamp, countDecimalPlace, isDecimalNumber, preventDefault } from './utils.js';
15
- /**
16
- * Allows users to make selections from a range of values
17
- *
18
- * @attr {string} value - Value of slider. Not applicable in range mode.
19
- * @prop {string} [value="0"] - Value of slider. Not applicable in range mode.
20
- *
21
- * @attr {boolean} readonly - Set readonly state
22
- * @prop {boolean} [readonly=false] - Set readonly state
23
- *
24
- * @attr {boolean} disabled - Set disabled state
25
- * @prop {boolean} [disabled=false] - Set disabled state
26
- *
27
- * @fires value-changed - Fired when the user commits a value change. The event is not triggered if `value` property is changed programmatically.
28
- * @fires from-changed - Fired when the user changes from's value. The event is not triggered if `from` property is changed programmatically.
29
- * @fires to-changed - Fired when the user changes to's value. The event is not triggered if `to` property is changed programmatically.
30
- * @fires input - Fired with the value of the input in `e.detail.value` like another custom events when the user inputs a value by interacting with the slider or updating its input field.
31
- * @fires from-input - Fired when the user inputs from's value by interacting with the slider or updating its input field.
32
- * @fires to-input - Fired when the user inputs to's value by interacting with the slider or updating its input field.
33
- */
34
- let Slider = class Slider extends ControlElement {
35
- /**
36
- * Element version number
37
- * @returns version number
38
- */
39
- static get version() {
40
- return VERSION;
41
- }
42
- /**
43
- * Define styles in a tagged template literal, using the css tag function.
44
- * @returns CSS template
45
- */
46
- static get styles() {
47
- return css `
48
- :host {
49
- display: flex;
50
- }
51
- [part='slider-wrapper'] {
52
- position: relative;
53
- width: 100%;
54
- }
55
- [part='slider'] {
56
- width: 100%;
57
- height: 100%;
58
- display: inline-block;
59
- }
60
- :host(:not([disabled]):focus) {
61
- outline: none;
62
- }
63
- :host([show-steps]) [part='track-wrapper']::after {
64
- display: block;
65
- position: absolute;
66
- content: '';
67
- right: 0;
68
- }
69
- [part='track-wrapper'] {
70
- content: '';
71
- position: absolute;
72
- width: 100%;
73
- top: 50%;
74
- left: 0;
75
- pointer-events: none;
76
- }
77
- [part='thumb-container'] {
78
- outline: none;
79
- position: absolute;
80
- top: 0;
81
- width: 100%;
82
- z-index: 3;
83
- }
84
- [part='thumb'] {
85
- position: absolute;
86
- margin: 0 auto;
87
- }
88
- [part='pin'] {
89
- display: flex;
90
- position: absolute;
91
- align-items: center;
92
- justify-content: center;
93
- z-index: 1;
94
- }
95
- :host([show-steps]) [part='step-container'] {
96
- position: absolute;
97
- left: 0;
98
- width: 100%;
99
- }
100
- :host([show-steps]) [part='step'] {
101
- width: 100%;
102
- position: absolute;
103
- left: 0;
104
- }
105
- [part='track-fill'] {
106
- z-index: 2;
107
- content: '';
108
- position: absolute;
109
- left: 0;
110
- }
111
- :host([show-steps][step='0']) [part='track-wrapper']::after {
112
- width: 0;
113
- }
114
- `;
115
- }
116
- /**
117
- * Converts value from string to number for calculations
118
- * @returns value of input as a number
119
- */
120
- get valueNumber() {
121
- const value = parseFloat(this.value);
122
- if (!this.value || isNaN(value)) {
123
- return 0;
124
- }
125
- return value;
126
- }
127
- /**
128
- * Converts min value from string to number for calculations
129
- * @returns minimum value of slider as a number
130
- */
131
- get minNumber() {
132
- const min = parseFloat(this.min);
133
- if (!this.min || isNaN(min)) {
134
- return 0;
135
- }
136
- return min;
137
- }
138
- /**
139
- * Converts max value from string to number for calculations
140
- * @returns maximum value of slider as a number
141
- */
142
- get maxNumber() {
143
- const max = parseFloat(this.max);
144
- if (!this.max || isNaN(max)) {
145
- return 100;
146
- }
147
- return max;
148
- }
149
- /**
150
- * Converts step value from string to number for calculations
151
- * @returns step value of slider as a number
152
- */
153
- get stepNumber() {
154
- const step = parseFloat(this.step);
155
- if (!this.step || isNaN(step)) {
156
- return 1;
157
- }
158
- return step;
159
- }
160
- /**
161
- * Compute and normalise step value for calculations
162
- * @returns step value that should be inside the min / max boundary
163
- */
164
- get stepRange() {
165
- const step = Math.abs(this.stepNumber);
166
- if (step > this.maxNumber - this.minNumber && !isDecimalNumber(step)) {
167
- // new step shouldn't be larger than slider
168
- return Math.abs(this.maxNumber - this.minNumber);
169
- }
170
- return step;
171
- }
172
- /**
173
- * Converts from value from string to number for calculations
174
- * @returns from value of slider as a number
175
- */
176
- get fromNumber() {
177
- const from = parseFloat(this.from);
178
- if (!this.from || isNaN(from)) {
179
- return this.minNumber;
180
- }
181
- return from;
182
- }
183
- /**
184
- * Converts to value from string to number for calculations
185
- * @returns to value of slider as a number
186
- */
187
- get toNumber() {
188
- const to = parseFloat(this.to);
189
- if (!this.to || isNaN(to)) {
190
- return this.maxNumber;
191
- }
192
- return to;
193
- }
194
- /**
195
- * Converts min-range from string to number for calculations
196
- * @returns min-range of input as a number
197
- */
198
- get minRangeNumber() {
199
- const minRange = parseFloat(this.minRange);
200
- if (!this.minRange || isNaN(minRange)) {
201
- return 0;
202
- }
203
- return minRange;
204
- }
205
- /**
206
- * Number of decimal places used for displaying value
207
- * Based on step or min decimal places
208
- */
209
- get decimalPlace() {
210
- if (isDecimalNumber(this.stepRange) || isDecimalNumber(this.minNumber)) {
211
- const stepDecimal = countDecimalPlace(this.stepRange);
212
- const minDecimal = countDecimalPlace(this.minNumber);
213
- return stepDecimal > minDecimal ? stepDecimal : minDecimal;
214
- }
215
- return 0;
216
- }
217
- /**
218
- * Return hide/show input field state
219
- * @returns true if showInputField value is exist
220
- */
221
- get isShowInputField() {
222
- return this.showInputField !== null && this.showInputField !== undefined;
223
- }
224
- constructor() {
225
- super();
226
- /**
227
- * Whether if the thumb is being drag
228
- */
229
- this.dragging = false;
230
- this.valuePrevious = '';
231
- this.fromPrevious = '';
232
- this.toPrevious = '';
233
- this.valuePreviousInput = ''; // dynamically accessed
234
- this.fromPreviousInput = ''; // dynamically accessed
235
- this.toPreviousInput = ''; // dynamically accessed
236
- /**
237
- * Specified size of increment or decrement jump between value.
238
- */
239
- this.step = '1';
240
- /**
241
- * Set minimum value of slider.
242
- */
243
- this.min = '0';
244
- /**
245
- * Set maximum value of slider.
246
- */
247
- this.max = '100';
248
- /**
249
- * Uses with `range`. Low value of slider in range mode.
250
- */
251
- this.from = '0';
252
- /**
253
- * Uses with `range`. High value of slider in range mode
254
- */
255
- this.to = '100';
256
- /**
257
- * Set slider appearances to show pin mode.
258
- * @ignore
259
- * NOTE: Pin isn't applicable in Halo. Hide this from document
260
- */
261
- this.pin = false;
262
- /**
263
- * Set slider to range mode. Instead of a single value, slider will provide `from` and `to`.
264
- */
265
- this.range = false;
266
- /**
267
- * Show steps marker on slider.
268
- */
269
- this.showSteps = false;
270
- /**
271
- * Show input number field.
272
- */
273
- this.showInputField = null;
274
- /**
275
- * Uses with `range`. Set minimum allowance value (distance) between `from` and `to`.
276
- */
277
- this.minRange = '0';
278
- /**
279
- * Slider element reference
280
- */
281
- this.sliderRef = createRef();
282
- /**
283
- * Slider's track reference
284
- */
285
- this.trackRef = createRef();
286
- /**
287
- * From value thumb reference, rendered only in range mode
288
- */
289
- this.fromThumbRef = createRef();
290
- /**
291
- * To value thumb reference, rendered only in range mode
292
- */
293
- this.toThumbRef = createRef();
294
- /**
295
- * Value thumb reference
296
- */
297
- this.valueThumbRef = createRef();
298
- /**
299
- * Current focused thumb
300
- */
301
- this.activeThumb = null;
302
- /**
303
- * Thumb that may involves data changes
304
- */
305
- this.changedThumb = null;
306
- /**
307
- * @ignore
308
- */
309
- this.onDrag = this.onDrag.bind(this);
310
- /**
311
- * @ignore
312
- */
313
- this.onDragStart = this.onDragStart.bind(this);
314
- /**
315
- * @ignore
316
- */
317
- this.onDragEnd = this.onDragEnd.bind(this);
318
- /**
319
- * @ignore
320
- */
321
- this.onKeyDown = this.onKeyDown.bind(this);
322
- }
323
- /**
324
- * On first updated lifecycle
325
- * @param changedProperties changed properties
326
- * @returns {void}
327
- */
328
- firstUpdated(changedProperties) {
329
- super.firstUpdated(changedProperties);
330
- this.prepareValues();
331
- this.prepareThumbs();
332
- this.prepareSliderTrack();
333
- }
334
- /**
335
- * On willUpdate lifecycle
336
- * @param changedProperties changed properties
337
- * @returns {void}
338
- */
339
- willUpdate(changedProperties) {
340
- super.willUpdate(changedProperties);
341
- if ((changedProperties.has('disabled') && changedProperties.get('disabled') !== undefined) ||
342
- (changedProperties.has('readonly') && changedProperties.get('readonly') !== undefined)) {
343
- this.prepareSliderTrack();
344
- }
345
- changedProperties.forEach((_, changedProperty) => {
346
- if (['value', 'min', 'max', 'from', 'to', 'step', 'minRange'].includes(changedProperty)) {
347
- this.showWarningInvalidProperty(changedProperty);
348
- }
349
- });
350
- }
351
- /**
352
- * On updated lifecycle
353
- * @param changedProperties changed properties
354
- * @returns {void}
355
- */
356
- updated(changedProperties) {
357
- super.updated(changedProperties);
358
- if (changedProperties.has('value')) {
359
- this.onValueChange();
360
- }
361
- if (changedProperties.has('min')) {
362
- this.onMinChange(changedProperties.get('min'));
363
- }
364
- if (changedProperties.has('max')) {
365
- this.onMaxChange(changedProperties.get('max'));
366
- }
367
- if (this.range) {
368
- if (changedProperties.has('from')) {
369
- this.onFromValueChange();
370
- }
371
- if (changedProperties.has('to')) {
372
- this.onToValueChange();
373
- }
374
- }
375
- if (changedProperties.has('step')) {
376
- this.onStepChange();
377
- }
378
- if (changedProperties.has('minRange')) {
379
- this.onMinRangeChange();
380
- }
381
- if (changedProperties.has('range')) {
382
- this.prepareValues();
383
- this.prepareThumbs();
384
- }
385
- }
386
- /**
387
- * Show Warning a warning message invalid property
388
- * @param propName value for checking
389
- * @returns {void}
390
- */
391
- showWarningInvalidProperty(propName) {
392
- let isValid = true;
393
- let message = '';
394
- if (propName === 'value') {
395
- isValid = this.isValueInBoundary(this.valueNumber, '');
396
- message = 'value should be between min and max.';
397
- }
398
- else if (propName === 'min') {
399
- isValid = this.minNumber <= this.maxNumber;
400
- message = 'value should be less than max.';
401
- }
402
- else if (propName === 'max') {
403
- isValid = this.maxNumber >= this.minNumber;
404
- message = 'value should be more than min.';
405
- }
406
- else if (propName === 'from' && this.range) {
407
- isValid = this.fromNumber >= this.minNumber && this.fromNumber <= this.toNumber;
408
- message = 'value should be more than min and less than to.';
409
- }
410
- else if (propName === 'to' && this.range) {
411
- isValid = this.toNumber <= this.maxNumber && this.toNumber >= this.fromNumber;
412
- message = 'value should be less than max and more than from.';
413
- }
414
- else if (propName === 'step') {
415
- isValid = this.maxNumber - this.minNumber >= this.stepNumber;
416
- message = 'value should be between min and max.';
417
- }
418
- else if (propName === 'minRange' && this.minRangeNumber > 0) {
419
- const distanceFromTo = Math.abs(this.toNumber - this.fromNumber);
420
- const distanceMinMax = Math.abs(this.maxNumber - this.minNumber);
421
- isValid = distanceMinMax >= this.minRangeNumber && distanceFromTo >= this.minRangeNumber;
422
- message = 'value should be less than distance from and to, min and max.';
423
- }
424
- if (!isValid) {
425
- new WarningNotice(`${this.localName}: Invalid ${propName} provided, The correct ${propName} ${message}`).show();
426
- }
427
- }
428
- /**
429
- * Initialises slider value properties
430
- * @returns {void}
431
- */
432
- prepareValues() {
433
- if (this.minNumber !== this.maxNumber) {
434
- if (this.range) {
435
- if (this.minRangeNumber) {
436
- const distanceFromTo = Math.abs(this.toNumber - this.fromNumber);
437
- const clampValueFrom = this.toNumber - this.minRangeNumber;
438
- if (this.minRangeNumber > distanceFromTo) {
439
- if (clampValueFrom < this.minNumber) {
440
- this.to = (this.fromNumber + this.minRangeNumber).toString();
441
- }
442
- else {
443
- this.from = clampValueFrom.toString();
444
- }
445
- }
446
- }
447
- else {
448
- this.from = clamp(this.fromNumber, this.minNumber, this.toNumber);
449
- this.to = clamp(this.toNumber, this.fromNumber, this.maxNumber);
450
- }
451
- }
452
- else {
453
- this.value = clamp(this.valueNumber, this.minNumber, this.maxNumber);
454
- }
455
- }
456
- else if (this.range) {
457
- this.from = this.min;
458
- this.to = this.max;
459
- }
460
- else {
461
- this.value = this.min;
462
- }
463
- this.valuePrevious = this.value;
464
- this.toPrevious = this.to;
465
- this.fromPrevious = this.from;
466
- }
467
- /**
468
- * Add event listeners to thumbs depending on mode
469
- * @returns {void}
470
- */
471
- prepareThumbs() {
472
- if (this.range) {
473
- this.fromThumbRef.value?.addEventListener('keydown', this.onKeyDown);
474
- this.fromThumbRef.value?.addEventListener('drag', preventDefault);
475
- this.fromThumbRef.value?.addEventListener('dragstart', preventDefault);
476
- this.fromThumbRef.value?.addEventListener('dragend', preventDefault);
477
- this.toThumbRef.value?.addEventListener('keydown', this.onKeyDown);
478
- this.toThumbRef.value?.addEventListener('drag', preventDefault);
479
- this.toThumbRef.value?.addEventListener('dragstart', preventDefault);
480
- this.toThumbRef.value?.addEventListener('dragend', preventDefault);
481
- }
482
- else {
483
- this.valueThumbRef.value?.addEventListener('keydown', this.onKeyDown);
484
- this.valueThumbRef.value?.addEventListener('drag', preventDefault);
485
- this.valueThumbRef.value?.addEventListener('dragstart', preventDefault);
486
- this.valueThumbRef.value?.addEventListener('dragend', preventDefault);
487
- }
488
- }
489
- /**
490
- * Add or remove event listener on slider track depending on slider disabled and readonly state
491
- * @returns {void}
492
- */
493
- prepareSliderTrack() {
494
- if (this.disabled || this.readonly) {
495
- this.sliderRef.value?.removeEventListener('mousedown', this.onDragStart);
496
- this.sliderRef.value?.removeEventListener('touchstart', this.onDragStart);
497
- }
498
- else {
499
- this.sliderRef.value?.addEventListener('mousedown', this.onDragStart, { passive: true });
500
- this.sliderRef.value?.addEventListener('touchstart', this.onDragStart, { passive: true });
501
- }
502
- }
503
- /**
504
- * Get slider data name from keyboard event target
505
- * @param target target element
506
- * @returns Slider data name
507
- */
508
- getThumbName(target) {
509
- switch (target) {
510
- case this.fromThumbRef.value:
511
- return SliderDataName.from;
512
- case this.toThumbRef.value:
513
- return SliderDataName.to;
514
- case this.valueThumbRef.value:
515
- return SliderDataName.value;
516
- default:
517
- return null;
518
- }
519
- }
520
- /**
521
- * Handles key down event on thumbs
522
- * @param event Keyboard event
523
- * @returns {void}
524
- */
525
- onKeyDown(event) {
526
- if (this.readonly || event.defaultPrevented || this.minNumber >= this.maxNumber) {
527
- return;
528
- }
529
- // Ignore special keys
530
- if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) {
531
- return;
532
- }
533
- const thumbName = this.getThumbName(event.target);
534
- if (!thumbName) {
535
- return;
536
- }
537
- this.changedThumb = event.target;
538
- switch (event.key) {
539
- case 'ArrowDown':
540
- case 'Down':
541
- case 'ArrowLeft':
542
- case 'Left':
543
- this.onApplyStep(Direction.Down, thumbName);
544
- break;
545
- case 'ArrowUp':
546
- case 'Up':
547
- case 'ArrowRight':
548
- case 'Right':
549
- this.onApplyStep(Direction.Up, thumbName);
550
- break;
551
- case 'Home':
552
- this.onApplyMin(thumbName);
553
- break;
554
- case 'End':
555
- this.onApplyMax(thumbName);
556
- break;
557
- default:
558
- return;
559
- }
560
- event.preventDefault();
561
- }
562
- /**
563
- * Set thumb to minimum value possible
564
- * @param data type of data to change
565
- * @returns {void}
566
- */
567
- onApplyMin(data) {
568
- let position;
569
- if (data === SliderDataName.from || data === SliderDataName.value) {
570
- position = this.calculatePosition(this.minNumber, 1);
571
- }
572
- else {
573
- position = this.calculatePosition(this.fromNumber + this.minRangeNumber, 1);
574
- }
575
- const possibleValue = this.getNearestPossibleValue(position);
576
- const value = this.getValueFromPosition(possibleValue);
577
- this.persistChangedData(value);
578
- this.dispatchDataChangedEvent();
579
- }
580
- /**
581
- * Set thumb to maximum value possible
582
- * @param data type of data to change
583
- * @returns {void}
584
- */
585
- onApplyMax(data) {
586
- let position;
587
- if (data === SliderDataName.to || data === SliderDataName.value) {
588
- position = this.calculatePosition(this.maxNumber, 1);
589
- }
590
- else {
591
- position = this.calculatePosition(this.toNumber - this.minRangeNumber, 1);
592
- }
593
- const possibleValue = this.getNearestPossibleValue(position);
594
- const value = this.getValueFromPosition(possibleValue);
595
- this.persistChangedData(value);
596
- this.dispatchDataChangedEvent();
597
- }
598
- /**
599
- * Increase or decrease value depending on direction
600
- * Then fires value change event
601
- * @param direction Up or Down
602
- * @param data type of data to change
603
- * @returns {void}
604
- */
605
- onApplyStep(direction, data) {
606
- // Get current thumb position and step in percentage format
607
- const thumbPosition = this.calculatePosition(this[`${data}Number`], 1);
608
- const step = this.calculatePosition(this.minNumber + this.stepRange, 1);
609
- const possibleValue = direction === Direction.Up ? thumbPosition + step : thumbPosition - step;
610
- const nearestPossibleValue = this.getNearestPossibleValue(possibleValue);
611
- const value = this.getValueFromPosition(nearestPossibleValue);
612
- this.persistChangedData(value);
613
- this.dispatchDataChangedEvent();
614
- }
615
- /**
616
- * Calculate thumb position based on value and multiplier
617
- * @param value decimal fraction value
618
- * @param multiplier defaults to 100
619
- * @returns thumb position as a fraction of 100
620
- */
621
- calculatePosition(value, multiplier = 100) {
622
- const position = Math.abs(((value - this.minNumber) / (this.maxNumber - this.minNumber)) * multiplier);
623
- if (position > multiplier) {
624
- return multiplier;
625
- }
626
- return position;
627
- }
628
- /**
629
- * Adds active attribute used in styling
630
- * @param event focus event
631
- * @returns {void}
632
- */
633
- onThumbFocus(event) {
634
- this.activeThumb = event.target;
635
- }
636
- /**
637
- * Removes active attribute used in styling
638
- * @param event focus event
639
- * @returns {void}
640
- */
641
- onThumbBlur() {
642
- this.activeThumb = null;
643
- }
644
- /**
645
- * On number-field blur
646
- * @param event focus event
647
- * @returns {void}
648
- */
649
- onNumberFieldBlur(event) {
650
- if (this.readonly) {
651
- return;
652
- }
653
- const { value, name } = event.target;
654
- const currentData = name;
655
- const previousData = `${name}Previous`;
656
- if (value && this[currentData] !== value) {
657
- this.updateNotifyProperty(currentData, value);
658
- this[previousData] = value;
659
- }
660
- event.preventDefault();
661
- }
662
- /**
663
- * On number-field input
664
- * @param event input event
665
- * @returns {void}
666
- */
667
- onNumberFieldInput(event) {
668
- if (this.readonly) {
669
- return;
670
- }
671
- const { value, name } = event.target;
672
- const currentData = name;
673
- this.notifyPropertyInput(currentData, value);
674
- event.preventDefault();
675
- event.stopPropagation();
676
- }
677
- /**
678
- * On number-field keydown
679
- * @param event keyboard event
680
- * @returns {void}
681
- */
682
- onNumberFieldKeyDown(event) {
683
- if (this.readonly || this.disabled) {
684
- return;
685
- }
686
- if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
687
- event.target.blur();
688
- }
689
- }
690
- /**
691
- * Update notify property by input name attribute
692
- * @param name name input attribute
693
- * @param value input value
694
- * @returns {void}
695
- */
696
- updateNotifyProperty(name, value) {
697
- let shouldUpdate = false;
698
- if (name === SliderDataName.to) {
699
- shouldUpdate = this.isValueInBoundary(Number(value), SliderDataName.to);
700
- }
701
- else {
702
- shouldUpdate = this.isValueInBoundary(Number(value), '');
703
- }
704
- if (shouldUpdate) {
705
- this[name] = value;
706
- this.notifyPropertyChange(name, value);
707
- }
708
- else {
709
- const inputName = `${name}Input`;
710
- this[inputName].value = this[name];
711
- }
712
- }
713
- /**
714
- * Dispatch data {value, from, to} changed event
715
- * @returns {void}
716
- */
717
- dispatchDataChangedEvent() {
718
- const name = this.changedThumb?.getAttribute('name') || '';
719
- const currentData = name;
720
- const previousData = `${name}Previous`;
721
- // Dispatch event only when value or from or to changed
722
- if (this[previousData] !== this[currentData]) {
723
- this.notifyPropertyChange(name, this[currentData]);
724
- this[previousData] = this[currentData];
725
- }
726
- }
727
- /**
728
- * Dispatch data {input, from-input, to-input} changing event
729
- * @returns {void}
730
- */
731
- dispatchDataInputEvent() {
732
- const name = this.changedThumb?.getAttribute('name') || '';
733
- const currentData = name;
734
- const previousDataInput = `${name}PreviousInput`;
735
- // Dispatch event only when changing the input value
736
- if (this[previousDataInput] !== this[currentData]) {
737
- this.notifyPropertyInput(name, this[currentData]);
738
- this[previousDataInput] = this[currentData];
739
- }
740
- }
741
- /**
742
- * Start dragging event on slider
743
- * @param event event dragstart
744
- * @returns {void}
745
- */
746
- onDragStart(event) {
747
- this.dragging = true;
748
- this.classList.add('grabbable');
749
- if (this.range) {
750
- const mousePosition = this.getMousePosition(event);
751
- const relativeMousePosition = (this.maxNumber - this.minNumber) * mousePosition + this.minNumber;
752
- const distanceFrom = Math.abs(relativeMousePosition - this.fromNumber);
753
- const distanceTo = Math.abs(relativeMousePosition - this.toNumber);
754
- if (distanceFrom < distanceTo) {
755
- this.changedThumb = this.fromThumbRef.value;
756
- this.fromPreviousInput = this.from;
757
- }
758
- else if (distanceFrom > distanceTo) {
759
- this.changedThumb = this.toThumbRef.value;
760
- this.toPreviousInput = this.to;
761
- }
762
- // When from === to, use latest value of changedThumb and z-index will determine thumb on top
763
- }
764
- else {
765
- this.changedThumb = this.valueThumbRef.value;
766
- this.valuePreviousInput = this.value;
767
- }
768
- this.onDrag(event);
769
- this.validateNumberField();
770
- if (event.changedTouches) {
771
- this.addEventListener('touchmove', this.onDrag);
772
- this.addEventListener('touchend', this.onDragEnd);
773
- }
774
- else {
775
- window.addEventListener('mousemove', this.onDrag);
776
- window.addEventListener('mouseup', this.onDragEnd);
777
- }
778
- }
779
- /**
780
- * Get mouse position in percentage value
781
- * @param event event mousemove and touchmove
782
- * @returns mouse position by percentage
783
- */
784
- getMousePosition(event) {
785
- const sliderRect = this.trackRef.value?.getBoundingClientRect();
786
- if (!sliderRect) {
787
- return 1;
788
- }
789
- // check drag desktop or mobile
790
- const pageX = event.changedTouches
791
- ? event.changedTouches[0].pageX
792
- : event.pageX;
793
- const positionSize = pageX - sliderRect.left;
794
- if (positionSize <= sliderRect.width) {
795
- return Math.min(Math.max((pageX - sliderRect.left) / sliderRect.width, 0), 1);
796
- }
797
- else {
798
- return 1;
799
- }
800
- }
801
- /**
802
- * Dragging after on dragging start event
803
- * @param event event mousemove and touchmove
804
- * @returns {void}
805
- */
806
- onDrag(event) {
807
- if (this.minNumber === this.maxNumber) {
808
- return;
809
- }
810
- const thumbPosition = this.getMousePosition(event);
811
- const nearestValue = this.getNearestPossibleValue(thumbPosition);
812
- if (nearestValue > 1) {
813
- return;
814
- }
815
- const newThumbPosition = this.stepRange !== 0 ? nearestValue : thumbPosition;
816
- const value = this.getValueFromPosition(newThumbPosition);
817
- this.persistChangedData(value);
818
- this.dispatchDataInputEvent();
819
- }
820
- /**
821
- * Saves changed data into correct field
822
- * @param value value of changed data
823
- * @returns {void}
824
- */
825
- persistChangedData(value) {
826
- const newValue = this.format(value);
827
- if (this.range) {
828
- if (this.changedThumb === this.fromThumbRef.value) {
829
- this.from = this.validateFrom(Number(newValue)).toString();
830
- }
831
- else {
832
- this.to = this.validateTo(Number(newValue)).toString();
833
- }
834
- }
835
- else {
836
- this.value = newValue;
837
- }
838
- }
839
- /**
840
- * Validate and return FROM value within available range
841
- * @param value from value
842
- * @returns validated from value
843
- */
844
- validateFrom(value) {
845
- const valueFrom = value + this.minRangeNumber;
846
- if (valueFrom < this.toNumber && valueFrom >= this.minNumber) {
847
- return value;
848
- }
849
- return this.toNumber - this.minRangeNumber;
850
- }
851
- /**
852
- * Validate and return TO value within available range
853
- * @param value to value
854
- * @returns validated to value.
855
- */
856
- validateTo(value) {
857
- const valueTo = value - this.minRangeNumber;
858
- if (valueTo > this.fromNumber && valueTo <= this.maxNumber) {
859
- return value;
860
- }
861
- return this.fromNumber + this.minRangeNumber;
862
- }
863
- /**
864
- * Validate number field from changed thumb
865
- * @returns {void}
866
- */
867
- validateNumberField() {
868
- if (this.isShowInputField) {
869
- const name = this.changedThumb?.getAttribute('name');
870
- const numberField = this[`${name}Input`];
871
- requestAnimationFrame(() => numberField.reportValidity());
872
- }
873
- }
874
- /**
875
- * Calculate the nearest possible step value depending on step interval
876
- * @param thumbPosition current thumb position in fraction
877
- * @returns nearest available slider step in fraction
878
- */
879
- getNearestPossibleValue(thumbPosition) {
880
- const stepSize = this.calculatePosition(this.minNumber + this.stepRange, 1);
881
- const nearestValue = Math.round(thumbPosition / stepSize) * stepSize;
882
- if (thumbPosition <= nearestValue + stepSize / 2) {
883
- if (nearestValue <= 1) {
884
- return nearestValue;
885
- }
886
- return nearestValue - stepSize;
887
- }
888
- return nearestValue + stepSize;
889
- }
890
- /**
891
- * Get slider value from thumb position
892
- * @param position thumb position
893
- * @returns calculated value
894
- */
895
- getValueFromPosition(position) {
896
- const value = this.minNumber + position * (this.maxNumber - this.minNumber);
897
- // if value is outside boundary, set to boundary
898
- if (value >= this.maxNumber) {
899
- return this.maxNumber;
900
- }
901
- else if (value <= this.minNumber) {
902
- return this.minNumber;
903
- }
904
- else {
905
- return value;
906
- }
907
- }
908
- /**
909
- * Format value to display in both integer and fraction cases
910
- * @param value value before use display
911
- * @returns formatted value
912
- */
913
- format(value) {
914
- if (isDecimalNumber(value) && countDecimalPlace(value) > this.decimalPlace) {
915
- return value.toFixed(this.decimalPlace);
916
- }
917
- return value.toString();
918
- }
919
- /**
920
- * End dragging event and remove dragging event
921
- * @param event event mouseup and touchmove
922
- * @returns {void}
923
- */
924
- onDragEnd(event) {
925
- if (this.dragging) {
926
- this.dragging = false;
927
- const touchEvent = event;
928
- if (touchEvent.changedTouches) {
929
- this.removeEventListener('touchmove', this.onDrag);
930
- this.removeEventListener('touchend', this.onDragEnd);
931
- }
932
- else {
933
- window.removeEventListener('mousemove', this.onDrag);
934
- window.removeEventListener('mouseup', this.onDragEnd);
935
- }
936
- this.classList.remove('grabbable');
937
- if (this.classList.length === 0) {
938
- this.removeAttribute('class');
939
- }
940
- if (!touchEvent.changedTouches) {
941
- event.preventDefault();
942
- }
943
- this.dispatchDataChangedEvent();
944
- }
945
- }
946
- /**
947
- * Value observer
948
- * @returns {void}
949
- */
950
- onValueChange() {
951
- if (this.readonly) {
952
- const thumbPosition = this.calculatePosition(this.valueNumber, 1);
953
- const nearestPossibleValue = this.getNearestPossibleValue(thumbPosition);
954
- const value = this.getValueFromPosition(this.stepRange === 0 ? thumbPosition : nearestPossibleValue);
955
- this.value = this.format(value);
956
- }
957
- else if (this.isValueInBoundary(this.valueNumber, '')) {
958
- this.value = this.format(this.valueNumber);
959
- }
960
- else if (this.valueNumber < this.minNumber) {
961
- this.value = this.min;
962
- }
963
- else if (this.valueNumber > this.maxNumber) {
964
- this.value = this.max;
965
- }
966
- if (!this.dragging) {
967
- // Update internal `valuePrevious` when `value` was programatically set by user.
968
- this.valuePrevious = this.value;
969
- }
970
- }
971
- /**
972
- * From value observer
973
- * @returns {void}
974
- */
975
- onFromValueChange() {
976
- if (this.isValueInBoundary(this.fromNumber, SliderDataName.from)) {
977
- this.from = this.format(this.fromNumber);
978
- }
979
- else {
980
- // if value is outside boundary, set to boundary
981
- this.from = clamp(this.fromNumber, this.minNumber, this.maxNumber);
982
- if (this.fromNumber > this.toNumber) {
983
- this.from = this.to;
984
- }
985
- if (this.minRangeNumber) {
986
- const distanceFromTo = Math.abs(this.toNumber - this.fromNumber);
987
- const distanceMin = this.toNumber - this.minRangeNumber;
988
- if (this.minRangeNumber > distanceFromTo && distanceMin > this.minNumber) {
989
- this.from = distanceMin.toString();
990
- }
991
- }
992
- }
993
- if (!this.dragging) {
994
- this.fromPrevious = this.from;
995
- }
996
- }
997
- /**
998
- * Check if value is inside min / max boundary
999
- * @param value value is checking
1000
- * @param valueFor notation variable binding if range === true
1001
- * @returns true if value and step inside a boundary
1002
- */
1003
- isValueInBoundary(value, valueFor) {
1004
- if (this.minNumber > this.maxNumber) {
1005
- return false;
1006
- }
1007
- // Check if value is in range
1008
- if (value < this.minNumber || value > this.maxNumber) {
1009
- return false;
1010
- }
1011
- if (this.range) {
1012
- if (valueFor === SliderDataName.to && value < this.fromNumber + this.minRangeNumber) {
1013
- return false;
1014
- }
1015
- else if (valueFor === SliderDataName.from && value > this.toNumber - this.minRangeNumber) {
1016
- return false;
1017
- }
1018
- }
1019
- return true;
1020
- }
1021
- /**
1022
- * To value observer
1023
- * @returns {void}
1024
- */
1025
- onToValueChange() {
1026
- if (this.isValueInBoundary(this.toNumber, SliderDataName.to)) {
1027
- this.to = this.format(this.toNumber);
1028
- }
1029
- else {
1030
- // if value is outside boundary, set to boundary
1031
- this.to = clamp(this.toNumber, this.minNumber, this.maxNumber);
1032
- if (this.toNumber < this.fromNumber) {
1033
- this.to = this.from;
1034
- }
1035
- if (this.minRangeNumber) {
1036
- const distanceFromTo = Math.abs(this.toNumber - this.fromNumber);
1037
- const distanceMax = this.fromNumber + this.minRangeNumber;
1038
- if (this.minRangeNumber > distanceFromTo && distanceMax < this.maxNumber) {
1039
- this.to = distanceMax.toString();
1040
- }
1041
- }
1042
- }
1043
- if (!this.dragging) {
1044
- this.toPrevious = this.to;
1045
- }
1046
- }
1047
- /**
1048
- * Step observer
1049
- * @returns {void}
1050
- */
1051
- onStepChange() {
1052
- this.step = this.stepNumber.toString();
1053
- }
1054
- /**
1055
- * Min range observer
1056
- * @returns {void}
1057
- */
1058
- onMinRangeChange() {
1059
- const valueMinRange = Math.abs(this.minRangeNumber);
1060
- const maximumRangeMinMax = Math.abs(this.maxNumber - this.minNumber);
1061
- if (valueMinRange && valueMinRange >= this.stepNumber) {
1062
- if (valueMinRange <= maximumRangeMinMax) {
1063
- this.minRange = valueMinRange.toString();
1064
- }
1065
- else {
1066
- this.minRange = maximumRangeMinMax.toString();
1067
- this.from = this.min;
1068
- this.to = this.max;
1069
- }
1070
- }
1071
- else {
1072
- // Reset min-range when min-range less step
1073
- this.minRange = '0';
1074
- }
1075
- }
1076
- /**
1077
- * Min observer
1078
- * @param oldValue old value of min property
1079
- * @returns {void}
1080
- */
1081
- onMinChange(oldValue) {
1082
- this.min = this.minNumber.toString();
1083
- if (this.minNumber > this.maxNumber) {
1084
- this.min = this.max;
1085
- return;
1086
- }
1087
- if (this.range) {
1088
- if (this.minNumber <= this.toNumber - this.minRangeNumber) {
1089
- this.from = clamp(this.fromNumber, this.minNumber, this.toNumber);
1090
- }
1091
- else if (oldValue) {
1092
- this.min = oldValue;
1093
- }
1094
- }
1095
- else {
1096
- this.value = clamp(this.valueNumber, this.minNumber, this.maxNumber);
1097
- }
1098
- }
1099
- /**
1100
- * Max observer
1101
- * @param oldValue old value of max property
1102
- * @returns {void}
1103
- */
1104
- onMaxChange(oldValue) {
1105
- this.max = this.maxNumber.toString();
1106
- if (this.maxNumber < this.minNumber) {
1107
- this.max = this.min;
1108
- return;
1109
- }
1110
- if (this.range) {
1111
- if (this.maxNumber >= this.fromNumber + this.minRangeNumber) {
1112
- this.to = clamp(this.toNumber, this.fromNumber, this.maxNumber);
1113
- }
1114
- else if (oldValue) {
1115
- this.max = oldValue;
1116
- }
1117
- }
1118
- else {
1119
- this.value = clamp(this.valueNumber, this.minNumber, this.maxNumber);
1120
- }
1121
- }
1122
- /**
1123
- * Implement `render` Track template.
1124
- * @param range show range slider
1125
- * @returns Track template
1126
- */
1127
- renderTrack(range) {
1128
- const stepContainerSize = this.calculatePosition(this.minNumber + this.stepNumber);
1129
- const translateX = stepContainerSize / 2;
1130
- const stepsStyle = {
1131
- transform: `translateX(${translateX}%)`,
1132
- backgroundSize: `${stepContainerSize}% 100%`
1133
- };
1134
- const stepContainerStyle = { transform: `translateX(-${translateX}%)` };
1135
- const trackFillStyle = range
1136
- ? {
1137
- width: `${this.calculatePosition(this.toNumber) - this.calculatePosition(this.fromNumber)}%`,
1138
- left: `${this.calculatePosition(this.fromNumber)}%`
1139
- }
1140
- : { width: `${this.calculatePosition(Number(this.value))}%` };
1141
- return html `
1142
- <div part="track-wrapper" ${ref(this.trackRef)}>
1143
- <div part="track-fill" style=${styleMap(trackFillStyle)}></div>
1144
- <div part="step-container" style=${styleMap(stepContainerStyle)}>
1145
- <div part="step" style=${styleMap(stepsStyle)}></div>
1146
- </div>
1147
- </div>
1148
- `;
1149
- }
1150
- /**
1151
- * Implement `render` Thumb template.
1152
- * @param value thumb value in track
1153
- * @param thumbPosition thumb position in track
1154
- * @param name name of thumb to render
1155
- * @returns Thumb template
1156
- */
1157
- thumbTemplate(value, thumbPosition, name) {
1158
- const isActive = this.activeThumb?.getAttribute('name') === name;
1159
- const isChanged = this.changedThumb?.getAttribute('name') === name;
1160
- let valueNow = this.value;
1161
- let valueMin = this.min;
1162
- let valueMax = this.max;
1163
- if (this.range) {
1164
- if (name === SliderDataName.from) {
1165
- valueNow = this.from;
1166
- valueMax = String(this.toNumber - this.minRangeNumber);
1167
- }
1168
- else {
1169
- valueNow = this.to;
1170
- valueMin = String(this.fromNumber + this.minRangeNumber);
1171
- }
1172
- }
1173
- const thumbZIndex = this.range ? (isChanged ? '4' : '3') : null;
1174
- const thumbStyle = { left: `${thumbPosition}%`, zIndex: thumbZIndex };
1175
- return html `
1176
- <div
1177
- ${ref(this[`${name}ThumbRef`])}
1178
- @focus=${this.onThumbFocus}
1179
- @blur=${this.onThumbBlur}
1180
- active=${isActive || nothing}
1181
- name="${name}"
1182
- role="slider"
1183
- aria-label="${this.t(name.toUpperCase())}"
1184
- tabindex="1"
1185
- aria-valuemin=${valueMin}
1186
- aria-valuemax=${valueMax}
1187
- aria-valuenow=${valueNow}
1188
- part="thumb-container"
1189
- style=${styleMap(thumbStyle)}
1190
- >
1191
- <div part="pin">
1192
- <span part="pin-value-marker">${value}</span>
1193
- </div>
1194
- <div part="thumb" draggable="true"></div>
1195
- </div>
1196
- `;
1197
- }
1198
- /**
1199
- * Renders thumb template depending on parameter
1200
- * @param from thumb value start in track
1201
- * @param to thumb value end in track (optional)
1202
- * @returns Thumbs template
1203
- */
1204
- renderThumb(from, to) {
1205
- return html `
1206
- ${this.thumbTemplate(from, this.calculatePosition(from), to !== undefined ? SliderDataName.from : SliderDataName.value)}
1207
- ${to !== undefined ? this.thumbTemplate(to, this.calculatePosition(to), SliderDataName.to) : nothing}
1208
- `;
1209
- }
1210
- /**
1211
- * Implement `render` number field has template.
1212
- * @param value value in the slider for binding in the input value
1213
- * @param name name input value
1214
- * @returns {TemplateResult} number field template
1215
- */
1216
- renderNumberField(value, name) {
1217
- /**
1218
- * Hiding number-field from screen reader and tabbing sequence because it's redundant,
1219
- * and complicate the accessibility implementation.
1220
- */
1221
- return html `
1222
- <ef-number-field
1223
- tabindex="-1"
1224
- aria-hidden="true"
1225
- @blur=${this.onNumberFieldBlur}
1226
- @keydown=${this.onNumberFieldKeyDown}
1227
- @input=${this.onNumberFieldInput}
1228
- part="input"
1229
- name="${name}"
1230
- no-spinner
1231
- .value="${value}"
1232
- min="${this.min}"
1233
- max="${this.max}"
1234
- step="${this.step}"
1235
- ?disabled="${this.disabled}"
1236
- ?readonly="${this.readonly || this.showInputField === 'readonly'}"
1237
- ></ef-number-field>
1238
- `;
1239
- }
1240
- /**
1241
- * Implement `render` slider template.
1242
- * @returns Slider template
1243
- */
1244
- render() {
1245
- return html `
1246
- ${this.range && this.isShowInputField ? this.renderNumberField(this.from, SliderDataName.from) : null}
1247
- <div part="slider-wrapper">
1248
- <div part="slider" ${ref(this.sliderRef)}>
1249
- ${this.renderTrack(this.range)}
1250
- ${this.range
1251
- ? this.renderThumb(this.fromNumber, this.toNumber)
1252
- : this.renderThumb(this.valueNumber)}
1253
- </div>
1254
- </div>
1255
- ${this.range && this.isShowInputField ? this.renderNumberField(this.to, SliderDataName.to) : null}
1256
- ${!this.range && this.isShowInputField
1257
- ? this.renderNumberField(this.value, SliderDataName.value)
1258
- : null}
1259
- `;
1260
- }
1261
- };
1262
- __decorate([
1263
- property({ type: String })
1264
- ], Slider.prototype, "step", void 0);
1265
- __decorate([
1266
- property({ type: String })
1267
- ], Slider.prototype, "min", void 0);
1268
- __decorate([
1269
- property({ type: String })
1270
- ], Slider.prototype, "max", void 0);
1271
- __decorate([
1272
- property({ type: String })
1273
- ], Slider.prototype, "from", void 0);
1274
- __decorate([
1275
- property({ type: String })
1276
- ], Slider.prototype, "to", void 0);
1277
- __decorate([
1278
- property({ type: Boolean, reflect: true })
1279
- ], Slider.prototype, "pin", void 0);
1280
- __decorate([
1281
- property({ type: Boolean, reflect: true })
1282
- ], Slider.prototype, "range", void 0);
1283
- __decorate([
1284
- property({ type: Boolean, reflect: true, attribute: 'show-steps' })
1285
- ], Slider.prototype, "showSteps", void 0);
1286
- __decorate([
1287
- property({ type: String, reflect: true, attribute: 'show-input-field' })
1288
- ], Slider.prototype, "showInputField", void 0);
1289
- __decorate([
1290
- property({ type: String, attribute: 'min-range' })
1291
- ], Slider.prototype, "minRange", void 0);
1292
- __decorate([
1293
- translate({ scope: 'ef-slider' })
1294
- ], Slider.prototype, "t", void 0);
1295
- __decorate([
1296
- query('ef-number-field[name=value]')
1297
- ], Slider.prototype, "valueInput", void 0);
1298
- __decorate([
1299
- query('ef-number-field[name=from]')
1300
- ], Slider.prototype, "fromInput", void 0);
1301
- __decorate([
1302
- query('ef-number-field[name=to]')
1303
- ], Slider.prototype, "toInput", void 0);
1304
- __decorate([
1305
- state()
1306
- ], Slider.prototype, "activeThumb", void 0);
1307
- __decorate([
1308
- state()
1309
- ], Slider.prototype, "changedThumb", void 0);
1310
- Slider = __decorate([
1311
- customElement('ef-slider')
1312
- ], Slider);
1313
- export { Slider };
1
+ export { Slider } from './elements/slider.js';
2
+ export { SliderMarker } from './elements/slider-marker.js';