@refinitiv-ui/elements 7.9.1 → 7.10.1

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,1309 +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 'ArrowLeft':
541
- this.onApplyStep(Direction.Down, thumbName);
542
- break;
543
- case 'ArrowUp':
544
- case 'ArrowRight':
545
- this.onApplyStep(Direction.Up, thumbName);
546
- break;
547
- case 'Home':
548
- this.onApplyMin(thumbName);
549
- break;
550
- case 'End':
551
- this.onApplyMax(thumbName);
552
- break;
553
- default:
554
- return;
555
- }
556
- event.preventDefault();
557
- }
558
- /**
559
- * Set thumb to minimum value possible
560
- * @param data type of data to change
561
- * @returns {void}
562
- */
563
- onApplyMin(data) {
564
- let position;
565
- if (data === SliderDataName.from || data === SliderDataName.value) {
566
- position = this.calculatePosition(this.minNumber, 1);
567
- }
568
- else {
569
- position = this.calculatePosition(this.fromNumber + this.minRangeNumber, 1);
570
- }
571
- const possibleValue = this.getNearestPossibleValue(position);
572
- const value = this.getValueFromPosition(possibleValue);
573
- this.persistChangedData(value);
574
- this.dispatchDataChangedEvent();
575
- }
576
- /**
577
- * Set thumb to maximum value possible
578
- * @param data type of data to change
579
- * @returns {void}
580
- */
581
- onApplyMax(data) {
582
- let position;
583
- if (data === SliderDataName.to || data === SliderDataName.value) {
584
- position = this.calculatePosition(this.maxNumber, 1);
585
- }
586
- else {
587
- position = this.calculatePosition(this.toNumber - this.minRangeNumber, 1);
588
- }
589
- const possibleValue = this.getNearestPossibleValue(position);
590
- const value = this.getValueFromPosition(possibleValue);
591
- this.persistChangedData(value);
592
- this.dispatchDataChangedEvent();
593
- }
594
- /**
595
- * Increase or decrease value depending on direction
596
- * Then fires value change event
597
- * @param direction Up or Down
598
- * @param data type of data to change
599
- * @returns {void}
600
- */
601
- onApplyStep(direction, data) {
602
- // Get current thumb position and step in percentage format
603
- const thumbPosition = this.calculatePosition(this[`${data}Number`], 1);
604
- const step = this.calculatePosition(this.minNumber + this.stepRange, 1);
605
- const possibleValue = direction === Direction.Up ? thumbPosition + step : thumbPosition - step;
606
- const nearestPossibleValue = this.getNearestPossibleValue(possibleValue);
607
- const value = this.getValueFromPosition(nearestPossibleValue);
608
- this.persistChangedData(value);
609
- this.dispatchDataChangedEvent();
610
- }
611
- /**
612
- * Calculate thumb position based on value and multiplier
613
- * @param value decimal fraction value
614
- * @param multiplier defaults to 100
615
- * @returns thumb position as a fraction of 100
616
- */
617
- calculatePosition(value, multiplier = 100) {
618
- const position = Math.abs(((value - this.minNumber) / (this.maxNumber - this.minNumber)) * multiplier);
619
- if (position > multiplier) {
620
- return multiplier;
621
- }
622
- return position;
623
- }
624
- /**
625
- * Adds active attribute used in styling
626
- * @param event focus event
627
- * @returns {void}
628
- */
629
- onThumbFocus(event) {
630
- this.activeThumb = event.target;
631
- }
632
- /**
633
- * Removes active attribute used in styling
634
- * @param event focus event
635
- * @returns {void}
636
- */
637
- onThumbBlur() {
638
- this.activeThumb = null;
639
- }
640
- /**
641
- * On number-field blur
642
- * @param event focus event
643
- * @returns {void}
644
- */
645
- onNumberFieldBlur(event) {
646
- if (this.readonly) {
647
- return;
648
- }
649
- const { value, name } = event.target;
650
- const currentData = name;
651
- const previousData = `${name}Previous`;
652
- if (value && this[currentData] !== value) {
653
- this.updateNotifyProperty(currentData, value);
654
- this[previousData] = value;
655
- }
656
- event.preventDefault();
657
- }
658
- /**
659
- * On number-field input
660
- * @param event input event
661
- * @returns {void}
662
- */
663
- onNumberFieldInput(event) {
664
- if (this.readonly) {
665
- return;
666
- }
667
- const { value, name } = event.target;
668
- const currentData = name;
669
- this.notifyPropertyInput(currentData, value);
670
- event.preventDefault();
671
- event.stopPropagation();
672
- }
673
- /**
674
- * On number-field keydown
675
- * @param event keyboard event
676
- * @returns {void}
677
- */
678
- onNumberFieldKeyDown(event) {
679
- if (this.readonly || this.disabled) {
680
- return;
681
- }
682
- if (event.key === ' ' || event.key === 'Enter') {
683
- event.target.blur();
684
- }
685
- }
686
- /**
687
- * Update notify property by input name attribute
688
- * @param name name input attribute
689
- * @param value input value
690
- * @returns {void}
691
- */
692
- updateNotifyProperty(name, value) {
693
- let shouldUpdate = false;
694
- if (name === SliderDataName.to) {
695
- shouldUpdate = this.isValueInBoundary(Number(value), SliderDataName.to);
696
- }
697
- else {
698
- shouldUpdate = this.isValueInBoundary(Number(value), '');
699
- }
700
- if (shouldUpdate) {
701
- this[name] = value;
702
- this.notifyPropertyChange(name, value);
703
- }
704
- else {
705
- const inputName = `${name}Input`;
706
- this[inputName].value = this[name];
707
- }
708
- }
709
- /**
710
- * Dispatch data {value, from, to} changed event
711
- * @returns {void}
712
- */
713
- dispatchDataChangedEvent() {
714
- const name = this.changedThumb?.getAttribute('name') || '';
715
- const currentData = name;
716
- const previousData = `${name}Previous`;
717
- // Dispatch event only when value or from or to changed
718
- if (this[previousData] !== this[currentData]) {
719
- this.notifyPropertyChange(name, this[currentData]);
720
- this[previousData] = this[currentData];
721
- }
722
- }
723
- /**
724
- * Dispatch data {input, from-input, to-input} changing event
725
- * @returns {void}
726
- */
727
- dispatchDataInputEvent() {
728
- const name = this.changedThumb?.getAttribute('name') || '';
729
- const currentData = name;
730
- const previousDataInput = `${name}PreviousInput`;
731
- // Dispatch event only when changing the input value
732
- if (this[previousDataInput] !== this[currentData]) {
733
- this.notifyPropertyInput(name, this[currentData]);
734
- this[previousDataInput] = this[currentData];
735
- }
736
- }
737
- /**
738
- * Start dragging event on slider
739
- * @param event event dragstart
740
- * @returns {void}
741
- */
742
- onDragStart(event) {
743
- this.dragging = true;
744
- this.classList.add('grabbable');
745
- if (this.range) {
746
- const mousePosition = this.getMousePosition(event);
747
- const relativeMousePosition = (this.maxNumber - this.minNumber) * mousePosition + this.minNumber;
748
- const distanceFrom = Math.abs(relativeMousePosition - this.fromNumber);
749
- const distanceTo = Math.abs(relativeMousePosition - this.toNumber);
750
- if (distanceFrom < distanceTo) {
751
- this.changedThumb = this.fromThumbRef.value;
752
- this.fromPreviousInput = this.from;
753
- }
754
- else if (distanceFrom > distanceTo) {
755
- this.changedThumb = this.toThumbRef.value;
756
- this.toPreviousInput = this.to;
757
- }
758
- // When from === to, use latest value of changedThumb and z-index will determine thumb on top
759
- }
760
- else {
761
- this.changedThumb = this.valueThumbRef.value;
762
- this.valuePreviousInput = this.value;
763
- }
764
- this.onDrag(event);
765
- this.validateNumberField();
766
- if (event.changedTouches) {
767
- this.addEventListener('touchmove', this.onDrag);
768
- this.addEventListener('touchend', this.onDragEnd);
769
- }
770
- else {
771
- window.addEventListener('mousemove', this.onDrag);
772
- window.addEventListener('mouseup', this.onDragEnd);
773
- }
774
- }
775
- /**
776
- * Get mouse position in percentage value
777
- * @param event event mousemove and touchmove
778
- * @returns mouse position by percentage
779
- */
780
- getMousePosition(event) {
781
- const sliderRect = this.trackRef.value?.getBoundingClientRect();
782
- if (!sliderRect) {
783
- return 1;
784
- }
785
- // check drag desktop or mobile
786
- const pageX = event.changedTouches
787
- ? event.changedTouches[0].pageX
788
- : event.pageX;
789
- const positionSize = pageX - sliderRect.left;
790
- if (positionSize <= sliderRect.width) {
791
- return Math.min(Math.max((pageX - sliderRect.left) / sliderRect.width, 0), 1);
792
- }
793
- else {
794
- return 1;
795
- }
796
- }
797
- /**
798
- * Dragging after on dragging start event
799
- * @param event event mousemove and touchmove
800
- * @returns {void}
801
- */
802
- onDrag(event) {
803
- if (this.minNumber === this.maxNumber) {
804
- return;
805
- }
806
- const thumbPosition = this.getMousePosition(event);
807
- const nearestValue = this.getNearestPossibleValue(thumbPosition);
808
- if (nearestValue > 1) {
809
- return;
810
- }
811
- const newThumbPosition = this.stepRange !== 0 ? nearestValue : thumbPosition;
812
- const value = this.getValueFromPosition(newThumbPosition);
813
- this.persistChangedData(value);
814
- this.dispatchDataInputEvent();
815
- }
816
- /**
817
- * Saves changed data into correct field
818
- * @param value value of changed data
819
- * @returns {void}
820
- */
821
- persistChangedData(value) {
822
- const newValue = this.format(value);
823
- if (this.range) {
824
- if (this.changedThumb === this.fromThumbRef.value) {
825
- this.from = this.validateFrom(Number(newValue)).toString();
826
- }
827
- else {
828
- this.to = this.validateTo(Number(newValue)).toString();
829
- }
830
- }
831
- else {
832
- this.value = newValue;
833
- }
834
- }
835
- /**
836
- * Validate and return FROM value within available range
837
- * @param value from value
838
- * @returns validated from value
839
- */
840
- validateFrom(value) {
841
- const valueFrom = value + this.minRangeNumber;
842
- if (valueFrom < this.toNumber && valueFrom >= this.minNumber) {
843
- return value;
844
- }
845
- return this.toNumber - this.minRangeNumber;
846
- }
847
- /**
848
- * Validate and return TO value within available range
849
- * @param value to value
850
- * @returns validated to value.
851
- */
852
- validateTo(value) {
853
- const valueTo = value - this.minRangeNumber;
854
- if (valueTo > this.fromNumber && valueTo <= this.maxNumber) {
855
- return value;
856
- }
857
- return this.fromNumber + this.minRangeNumber;
858
- }
859
- /**
860
- * Validate number field from changed thumb
861
- * @returns {void}
862
- */
863
- validateNumberField() {
864
- if (this.isShowInputField) {
865
- const name = this.changedThumb?.getAttribute('name');
866
- const numberField = this[`${name}Input`];
867
- requestAnimationFrame(() => numberField.reportValidity());
868
- }
869
- }
870
- /**
871
- * Calculate the nearest possible step value depending on step interval
872
- * @param thumbPosition current thumb position in fraction
873
- * @returns nearest available slider step in fraction
874
- */
875
- getNearestPossibleValue(thumbPosition) {
876
- const stepSize = this.calculatePosition(this.minNumber + this.stepRange, 1);
877
- const nearestValue = Math.round(thumbPosition / stepSize) * stepSize;
878
- if (thumbPosition <= nearestValue + stepSize / 2) {
879
- if (nearestValue <= 1) {
880
- return nearestValue;
881
- }
882
- return nearestValue - stepSize;
883
- }
884
- return nearestValue + stepSize;
885
- }
886
- /**
887
- * Get slider value from thumb position
888
- * @param position thumb position
889
- * @returns calculated value
890
- */
891
- getValueFromPosition(position) {
892
- const value = this.minNumber + position * (this.maxNumber - this.minNumber);
893
- // if value is outside boundary, set to boundary
894
- if (value >= this.maxNumber) {
895
- return this.maxNumber;
896
- }
897
- else if (value <= this.minNumber) {
898
- return this.minNumber;
899
- }
900
- else {
901
- return value;
902
- }
903
- }
904
- /**
905
- * Format value to display in both integer and fraction cases
906
- * @param value value before use display
907
- * @returns formatted value
908
- */
909
- format(value) {
910
- if (isDecimalNumber(value) && countDecimalPlace(value) > this.decimalPlace) {
911
- return value.toFixed(this.decimalPlace);
912
- }
913
- return value.toString();
914
- }
915
- /**
916
- * End dragging event and remove dragging event
917
- * @param event event mouseup and touchmove
918
- * @returns {void}
919
- */
920
- onDragEnd(event) {
921
- if (this.dragging) {
922
- this.dragging = false;
923
- const touchEvent = event;
924
- if (touchEvent.changedTouches) {
925
- this.removeEventListener('touchmove', this.onDrag);
926
- this.removeEventListener('touchend', this.onDragEnd);
927
- }
928
- else {
929
- window.removeEventListener('mousemove', this.onDrag);
930
- window.removeEventListener('mouseup', this.onDragEnd);
931
- }
932
- this.classList.remove('grabbable');
933
- if (this.classList.length === 0) {
934
- this.removeAttribute('class');
935
- }
936
- if (!touchEvent.changedTouches) {
937
- event.preventDefault();
938
- }
939
- this.dispatchDataChangedEvent();
940
- }
941
- }
942
- /**
943
- * Value observer
944
- * @returns {void}
945
- */
946
- onValueChange() {
947
- if (this.readonly) {
948
- const thumbPosition = this.calculatePosition(this.valueNumber, 1);
949
- const nearestPossibleValue = this.getNearestPossibleValue(thumbPosition);
950
- const value = this.getValueFromPosition(this.stepRange === 0 ? thumbPosition : nearestPossibleValue);
951
- this.value = this.format(value);
952
- }
953
- else if (this.isValueInBoundary(this.valueNumber, '')) {
954
- this.value = this.format(this.valueNumber);
955
- }
956
- else if (this.valueNumber < this.minNumber) {
957
- this.value = this.min;
958
- }
959
- else if (this.valueNumber > this.maxNumber) {
960
- this.value = this.max;
961
- }
962
- if (!this.dragging) {
963
- // Update internal `valuePrevious` when `value` was programatically set by user.
964
- this.valuePrevious = this.value;
965
- }
966
- }
967
- /**
968
- * From value observer
969
- * @returns {void}
970
- */
971
- onFromValueChange() {
972
- if (this.isValueInBoundary(this.fromNumber, SliderDataName.from)) {
973
- this.from = this.format(this.fromNumber);
974
- }
975
- else {
976
- // if value is outside boundary, set to boundary
977
- this.from = clamp(this.fromNumber, this.minNumber, this.maxNumber);
978
- if (this.fromNumber > this.toNumber) {
979
- this.from = this.to;
980
- }
981
- if (this.minRangeNumber) {
982
- const distanceFromTo = Math.abs(this.toNumber - this.fromNumber);
983
- const distanceMin = this.toNumber - this.minRangeNumber;
984
- if (this.minRangeNumber > distanceFromTo && distanceMin > this.minNumber) {
985
- this.from = distanceMin.toString();
986
- }
987
- }
988
- }
989
- if (!this.dragging) {
990
- this.fromPrevious = this.from;
991
- }
992
- }
993
- /**
994
- * Check if value is inside min / max boundary
995
- * @param value value is checking
996
- * @param valueFor notation variable binding if range === true
997
- * @returns true if value and step inside a boundary
998
- */
999
- isValueInBoundary(value, valueFor) {
1000
- if (this.minNumber > this.maxNumber) {
1001
- return false;
1002
- }
1003
- // Check if value is in range
1004
- if (value < this.minNumber || value > this.maxNumber) {
1005
- return false;
1006
- }
1007
- if (this.range) {
1008
- if (valueFor === SliderDataName.to && value < this.fromNumber + this.minRangeNumber) {
1009
- return false;
1010
- }
1011
- else if (valueFor === SliderDataName.from && value > this.toNumber - this.minRangeNumber) {
1012
- return false;
1013
- }
1014
- }
1015
- return true;
1016
- }
1017
- /**
1018
- * To value observer
1019
- * @returns {void}
1020
- */
1021
- onToValueChange() {
1022
- if (this.isValueInBoundary(this.toNumber, SliderDataName.to)) {
1023
- this.to = this.format(this.toNumber);
1024
- }
1025
- else {
1026
- // if value is outside boundary, set to boundary
1027
- this.to = clamp(this.toNumber, this.minNumber, this.maxNumber);
1028
- if (this.toNumber < this.fromNumber) {
1029
- this.to = this.from;
1030
- }
1031
- if (this.minRangeNumber) {
1032
- const distanceFromTo = Math.abs(this.toNumber - this.fromNumber);
1033
- const distanceMax = this.fromNumber + this.minRangeNumber;
1034
- if (this.minRangeNumber > distanceFromTo && distanceMax < this.maxNumber) {
1035
- this.to = distanceMax.toString();
1036
- }
1037
- }
1038
- }
1039
- if (!this.dragging) {
1040
- this.toPrevious = this.to;
1041
- }
1042
- }
1043
- /**
1044
- * Step observer
1045
- * @returns {void}
1046
- */
1047
- onStepChange() {
1048
- this.step = this.stepNumber.toString();
1049
- }
1050
- /**
1051
- * Min range observer
1052
- * @returns {void}
1053
- */
1054
- onMinRangeChange() {
1055
- const valueMinRange = Math.abs(this.minRangeNumber);
1056
- const maximumRangeMinMax = Math.abs(this.maxNumber - this.minNumber);
1057
- if (valueMinRange && valueMinRange >= this.stepNumber) {
1058
- if (valueMinRange <= maximumRangeMinMax) {
1059
- this.minRange = valueMinRange.toString();
1060
- }
1061
- else {
1062
- this.minRange = maximumRangeMinMax.toString();
1063
- this.from = this.min;
1064
- this.to = this.max;
1065
- }
1066
- }
1067
- else {
1068
- // Reset min-range when min-range less step
1069
- this.minRange = '0';
1070
- }
1071
- }
1072
- /**
1073
- * Min observer
1074
- * @param oldValue old value of min property
1075
- * @returns {void}
1076
- */
1077
- onMinChange(oldValue) {
1078
- this.min = this.minNumber.toString();
1079
- if (this.minNumber > this.maxNumber) {
1080
- this.min = this.max;
1081
- return;
1082
- }
1083
- if (this.range) {
1084
- if (this.minNumber <= this.toNumber - this.minRangeNumber) {
1085
- this.from = clamp(this.fromNumber, this.minNumber, this.toNumber);
1086
- }
1087
- else if (oldValue) {
1088
- this.min = oldValue;
1089
- }
1090
- }
1091
- else {
1092
- this.value = clamp(this.valueNumber, this.minNumber, this.maxNumber);
1093
- }
1094
- }
1095
- /**
1096
- * Max observer
1097
- * @param oldValue old value of max property
1098
- * @returns {void}
1099
- */
1100
- onMaxChange(oldValue) {
1101
- this.max = this.maxNumber.toString();
1102
- if (this.maxNumber < this.minNumber) {
1103
- this.max = this.min;
1104
- return;
1105
- }
1106
- if (this.range) {
1107
- if (this.maxNumber >= this.fromNumber + this.minRangeNumber) {
1108
- this.to = clamp(this.toNumber, this.fromNumber, this.maxNumber);
1109
- }
1110
- else if (oldValue) {
1111
- this.max = oldValue;
1112
- }
1113
- }
1114
- else {
1115
- this.value = clamp(this.valueNumber, this.minNumber, this.maxNumber);
1116
- }
1117
- }
1118
- /**
1119
- * Implement `render` Track template.
1120
- * @param range show range slider
1121
- * @returns Track template
1122
- */
1123
- renderTrack(range) {
1124
- const stepContainerSize = this.calculatePosition(this.minNumber + this.stepNumber);
1125
- const translateX = stepContainerSize / 2;
1126
- const stepsStyle = {
1127
- transform: `translateX(${translateX}%)`,
1128
- backgroundSize: `${stepContainerSize}% 100%`
1129
- };
1130
- const stepContainerStyle = { transform: `translateX(-${translateX}%)` };
1131
- const trackFillStyle = range
1132
- ? {
1133
- width: `${this.calculatePosition(this.toNumber) - this.calculatePosition(this.fromNumber)}%`,
1134
- left: `${this.calculatePosition(this.fromNumber)}%`
1135
- }
1136
- : { width: `${this.calculatePosition(Number(this.value))}%` };
1137
- return html `
1138
- <div part="track-wrapper" ${ref(this.trackRef)}>
1139
- <div part="track-fill" style=${styleMap(trackFillStyle)}></div>
1140
- <div part="step-container" style=${styleMap(stepContainerStyle)}>
1141
- <div part="step" style=${styleMap(stepsStyle)}></div>
1142
- </div>
1143
- </div>
1144
- `;
1145
- }
1146
- /**
1147
- * Implement `render` Thumb template.
1148
- * @param value thumb value in track
1149
- * @param thumbPosition thumb position in track
1150
- * @param name name of thumb to render
1151
- * @returns Thumb template
1152
- */
1153
- thumbTemplate(value, thumbPosition, name) {
1154
- const isActive = this.activeThumb?.getAttribute('name') === name;
1155
- const isChanged = this.changedThumb?.getAttribute('name') === name;
1156
- let valueNow = this.value;
1157
- let valueMin = this.min;
1158
- let valueMax = this.max;
1159
- if (this.range) {
1160
- if (name === SliderDataName.from) {
1161
- valueNow = this.from;
1162
- valueMax = String(this.toNumber - this.minRangeNumber);
1163
- }
1164
- else {
1165
- valueNow = this.to;
1166
- valueMin = String(this.fromNumber + this.minRangeNumber);
1167
- }
1168
- }
1169
- const thumbZIndex = this.range ? (isChanged ? '4' : '3') : null;
1170
- const thumbStyle = { left: `${thumbPosition}%`, zIndex: thumbZIndex };
1171
- return html `
1172
- <div
1173
- ${ref(this[`${name}ThumbRef`])}
1174
- @focus=${this.onThumbFocus}
1175
- @blur=${this.onThumbBlur}
1176
- active=${isActive || nothing}
1177
- name="${name}"
1178
- role="slider"
1179
- aria-label="${this.t(name.toUpperCase())}"
1180
- tabindex="1"
1181
- aria-valuemin=${valueMin}
1182
- aria-valuemax=${valueMax}
1183
- aria-valuenow=${valueNow}
1184
- part="thumb-container"
1185
- style=${styleMap(thumbStyle)}
1186
- >
1187
- <div part="pin">
1188
- <span part="pin-value-marker">${value}</span>
1189
- </div>
1190
- <div part="thumb" draggable="true"></div>
1191
- </div>
1192
- `;
1193
- }
1194
- /**
1195
- * Renders thumb template depending on parameter
1196
- * @param from thumb value start in track
1197
- * @param to thumb value end in track (optional)
1198
- * @returns Thumbs template
1199
- */
1200
- renderThumb(from, to) {
1201
- return html `
1202
- ${this.thumbTemplate(from, this.calculatePosition(from), to !== undefined ? SliderDataName.from : SliderDataName.value)}
1203
- ${to !== undefined ? this.thumbTemplate(to, this.calculatePosition(to), SliderDataName.to) : nothing}
1204
- `;
1205
- }
1206
- /**
1207
- * Implement `render` number field has template.
1208
- * @param value value in the slider for binding in the input value
1209
- * @param name name input value
1210
- * @returns {TemplateResult} number field template
1211
- */
1212
- renderNumberField(value, name) {
1213
- /**
1214
- * Hiding number-field from screen reader and tabbing sequence because it's redundant,
1215
- * and complicate the accessibility implementation.
1216
- */
1217
- return html `
1218
- <ef-number-field
1219
- tabindex="-1"
1220
- aria-hidden="true"
1221
- @blur=${this.onNumberFieldBlur}
1222
- @keydown=${this.onNumberFieldKeyDown}
1223
- @input=${this.onNumberFieldInput}
1224
- part="input"
1225
- name="${name}"
1226
- no-spinner
1227
- .value="${value}"
1228
- min="${this.min}"
1229
- max="${this.max}"
1230
- step="${this.step}"
1231
- ?disabled="${this.disabled}"
1232
- ?readonly="${this.readonly || this.showInputField === 'readonly'}"
1233
- ></ef-number-field>
1234
- `;
1235
- }
1236
- /**
1237
- * Implement `render` slider template.
1238
- * @returns Slider template
1239
- */
1240
- render() {
1241
- return html `
1242
- ${this.range && this.isShowInputField ? this.renderNumberField(this.from, SliderDataName.from) : null}
1243
- <div part="slider-wrapper">
1244
- <div part="slider" ${ref(this.sliderRef)}>
1245
- ${this.renderTrack(this.range)}
1246
- ${this.range
1247
- ? this.renderThumb(this.fromNumber, this.toNumber)
1248
- : this.renderThumb(this.valueNumber)}
1249
- </div>
1250
- </div>
1251
- ${this.range && this.isShowInputField ? this.renderNumberField(this.to, SliderDataName.to) : null}
1252
- ${!this.range && this.isShowInputField
1253
- ? this.renderNumberField(this.value, SliderDataName.value)
1254
- : null}
1255
- `;
1256
- }
1257
- };
1258
- __decorate([
1259
- property({ type: String })
1260
- ], Slider.prototype, "step", void 0);
1261
- __decorate([
1262
- property({ type: String })
1263
- ], Slider.prototype, "min", void 0);
1264
- __decorate([
1265
- property({ type: String })
1266
- ], Slider.prototype, "max", void 0);
1267
- __decorate([
1268
- property({ type: String })
1269
- ], Slider.prototype, "from", void 0);
1270
- __decorate([
1271
- property({ type: String })
1272
- ], Slider.prototype, "to", void 0);
1273
- __decorate([
1274
- property({ type: Boolean, reflect: true })
1275
- ], Slider.prototype, "pin", void 0);
1276
- __decorate([
1277
- property({ type: Boolean, reflect: true })
1278
- ], Slider.prototype, "range", void 0);
1279
- __decorate([
1280
- property({ type: Boolean, reflect: true, attribute: 'show-steps' })
1281
- ], Slider.prototype, "showSteps", void 0);
1282
- __decorate([
1283
- property({ type: String, reflect: true, attribute: 'show-input-field' })
1284
- ], Slider.prototype, "showInputField", void 0);
1285
- __decorate([
1286
- property({ type: String, attribute: 'min-range' })
1287
- ], Slider.prototype, "minRange", void 0);
1288
- __decorate([
1289
- translate({ scope: 'ef-slider' })
1290
- ], Slider.prototype, "t", void 0);
1291
- __decorate([
1292
- query('ef-number-field[name=value]')
1293
- ], Slider.prototype, "valueInput", void 0);
1294
- __decorate([
1295
- query('ef-number-field[name=from]')
1296
- ], Slider.prototype, "fromInput", void 0);
1297
- __decorate([
1298
- query('ef-number-field[name=to]')
1299
- ], Slider.prototype, "toInput", void 0);
1300
- __decorate([
1301
- state()
1302
- ], Slider.prototype, "activeThumb", void 0);
1303
- __decorate([
1304
- state()
1305
- ], Slider.prototype, "changedThumb", void 0);
1306
- Slider = __decorate([
1307
- customElement('ef-slider')
1308
- ], Slider);
1309
- export { Slider };
1
+ export { Slider } from './elements/slider.js';
2
+ export { SliderMarker } from './elements/slider-marker.js';