@mintplayer/ng-bootstrap 21.28.0 → 21.30.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.
@@ -0,0 +1,693 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, forwardRef, Directive, input, model, output, viewChild, computed, effect, ChangeDetectionStrategy, CUSTOM_ELEMENTS_SCHEMA, Component } from '@angular/core';
3
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
+ import { html, unsafeCSS, LitElement, nothing } from 'lit';
5
+
6
+ class BsMultiRangeValueAccessor {
7
+ constructor() {
8
+ this.host = inject(BsMultiRangeComponent);
9
+ }
10
+ onInputEvent(ev) {
11
+ if (!this.onValueChange)
12
+ return;
13
+ const detail = ev.detail;
14
+ if (detail)
15
+ this.onValueChange([...detail]);
16
+ }
17
+ onTouchEvent() {
18
+ if (this.onTouched)
19
+ this.onTouched();
20
+ }
21
+ registerOnChange(fn) {
22
+ this.onValueChange = fn;
23
+ }
24
+ registerOnTouched(fn) {
25
+ this.onTouched = fn;
26
+ }
27
+ writeValue(value) {
28
+ const ref = this.host.elementRef();
29
+ if (!ref)
30
+ return;
31
+ ref.nativeElement.value = value ?? [];
32
+ }
33
+ setDisabledState(isDisabled) {
34
+ const ref = this.host.elementRef();
35
+ if (!ref)
36
+ return;
37
+ if (isDisabled) {
38
+ ref.nativeElement.setAttribute('disabled', '');
39
+ }
40
+ else {
41
+ ref.nativeElement.removeAttribute('disabled');
42
+ }
43
+ }
44
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsMultiRangeValueAccessor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
45
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.11", type: BsMultiRangeValueAccessor, isStandalone: true, selector: "bs-multi-range", host: { listeners: { "value-input": "onInputEvent($event)", "value-change": "onTouchEvent()" } }, providers: [{
46
+ provide: NG_VALUE_ACCESSOR,
47
+ useExisting: forwardRef(() => BsMultiRangeValueAccessor),
48
+ multi: true,
49
+ }], ngImport: i0 }); }
50
+ }
51
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsMultiRangeValueAccessor, decorators: [{
52
+ type: Directive,
53
+ args: [{
54
+ selector: 'bs-multi-range',
55
+ providers: [{
56
+ provide: NG_VALUE_ACCESSOR,
57
+ useExisting: forwardRef(() => BsMultiRangeValueAccessor),
58
+ multi: true,
59
+ }],
60
+ host: {
61
+ '(value-input)': 'onInputEvent($event)',
62
+ '(value-change)': 'onTouchEvent()',
63
+ },
64
+ }]
65
+ }] });
66
+
67
+ class BsMultiRangeComponent {
68
+ constructor() {
69
+ this.min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
70
+ this.max = input(100, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
71
+ this.step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
72
+ this.minDistance = input(0, ...(ngDevMode ? [{ debugName: "minDistance" }] : /* istanbul ignore next */ []));
73
+ this.orientation = input('horizontal', ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
74
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
75
+ this.formatValue = input(null, ...(ngDevMode ? [{ debugName: "formatValue" }] : /* istanbul ignore next */ []));
76
+ this.label = input(null, ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
77
+ // Default is `undefined` so the effect doesn't clobber a writeValue() from a
78
+ // form-control binding that hasn't fired yet. Once the model is set (either
79
+ // by [(value)] binding or by user interaction), the effect drives the WC.
80
+ this.value = model(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
81
+ this.valueChange = output();
82
+ this.valueInput = output();
83
+ this.elementRef = viewChild.required('el');
84
+ this.disabledAttr = computed(() => (this.disabled() ? '' : null), ...(ngDevMode ? [{ debugName: "disabledAttr" }] : /* istanbul ignore next */ []));
85
+ effect(() => {
86
+ const ref = this.elementRef();
87
+ if (!ref)
88
+ return;
89
+ const v = this.value();
90
+ if (v === undefined)
91
+ return;
92
+ ref.nativeElement.value = v;
93
+ });
94
+ effect(() => {
95
+ const ref = this.elementRef();
96
+ if (!ref)
97
+ return;
98
+ ref.nativeElement.formatValue = this.formatValue();
99
+ });
100
+ }
101
+ onValueInput(event) {
102
+ const detail = event.detail;
103
+ if (!detail)
104
+ return;
105
+ this.value.set(detail);
106
+ this.valueInput.emit(detail);
107
+ }
108
+ onValueChange(event) {
109
+ const detail = event.detail;
110
+ if (!detail)
111
+ return;
112
+ this.value.set(detail);
113
+ this.valueChange.emit(detail);
114
+ }
115
+ /** Imperatively read the currently-rendered values from the WC. */
116
+ getValues() {
117
+ const ref = this.elementRef();
118
+ return ref ? ref.nativeElement.getValues() : [];
119
+ }
120
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsMultiRangeComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
121
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.11", type: BsMultiRangeComponent, isStandalone: true, selector: "bs-multi-range", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, minDistance: { classPropertyName: "minDistance", publicName: "minDistance", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, formatValue: { classPropertyName: "formatValue", publicName: "formatValue", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", valueChange: "valueChange", valueInput: "valueInput" }, host: { properties: { "attr.orientation": "orientation()" } }, viewQueries: [{ propertyName: "elementRef", first: true, predicate: ["el"], descendants: true, isSignal: true }], hostDirectives: [{ directive: BsMultiRangeValueAccessor }], ngImport: i0, template: `
122
+ <mp-multi-range
123
+ #el
124
+ class="bs-multi-range"
125
+ [attr.min]="min()"
126
+ [attr.max]="max()"
127
+ [attr.step]="step()"
128
+ [attr.min-distance]="minDistance()"
129
+ [attr.orientation]="orientation()"
130
+ [attr.disabled]="disabledAttr()"
131
+ [attr.aria-label]="label()"
132
+ (value-input)="onValueInput($event)"
133
+ (value-change)="onValueChange($event)"
134
+ ></mp-multi-range>
135
+ `, isInline: true, styles: [":host{display:block;width:100%}.bs-multi-range{display:block;width:100%;height:100%}:host([orientation=\"vertical\"]){width:auto;height:100%}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
136
+ }
137
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: BsMultiRangeComponent, decorators: [{
138
+ type: Component,
139
+ args: [{ selector: 'bs-multi-range', template: `
140
+ <mp-multi-range
141
+ #el
142
+ class="bs-multi-range"
143
+ [attr.min]="min()"
144
+ [attr.max]="max()"
145
+ [attr.step]="step()"
146
+ [attr.min-distance]="minDistance()"
147
+ [attr.orientation]="orientation()"
148
+ [attr.disabled]="disabledAttr()"
149
+ [attr.aria-label]="label()"
150
+ (value-input)="onValueInput($event)"
151
+ (value-change)="onValueChange($event)"
152
+ ></mp-multi-range>
153
+ `, host: {
154
+ '[attr.orientation]': 'orientation()',
155
+ }, schemas: [CUSTOM_ELEMENTS_SCHEMA], changeDetection: ChangeDetectionStrategy.OnPush, hostDirectives: [BsMultiRangeValueAccessor], styles: [":host{display:block;width:100%}.bs-multi-range{display:block;width:100%;height:100%}:host([orientation=\"vertical\"]){width:auto;height:100%}\n"] }]
156
+ }], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], minDistance: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDistance", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], formatValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "formatValue", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], valueInput: [{ type: i0.Output, args: ["valueInput"] }], elementRef: [{ type: i0.ViewChild, args: ['el', { isSignal: true }] }] } });
157
+
158
+ // AUTO-GENERATED — do not edit by hand.
159
+ // Source: mint-multi-range.element.html + mint-multi-range.element.scss
160
+ // Regenerate with the codegen-wc Nx target.
161
+ const template = html `<!-- Track + thumbs are rendered dynamically by render(). This file exists so the
162
+ codegen-wc target produces a \`styles\` export from the sibling .scss; the
163
+ \`template\` export it generates here is intentionally unused. -->`;
164
+ const styles = unsafeCSS(`:host {
165
+ display: block;
166
+ position: relative;
167
+ box-sizing: border-box;
168
+ width: 100%;
169
+ --bs-multi-range-track-bg: var(--bs-tertiary-bg, #e9ecef);
170
+ --bs-multi-range-track-radius: 1rem;
171
+ --bs-multi-range-track-thickness: 0.5rem;
172
+ --bs-multi-range-thumb-bg: var(--bs-primary, #0d6efd);
173
+ --bs-multi-range-thumb-bg-disabled: var(--bs-secondary, #6c757d);
174
+ --bs-multi-range-thumb-size: 1rem;
175
+ --bs-multi-range-thumb-shadow: 0 0.1rem 0.25rem rgba(0, 0, 0, 0.1);
176
+ --bs-multi-range-fill-bg: var(--bs-primary, #0d6efd);
177
+ --bs-multi-range-fill-bg-disabled: var(--bs-secondary, #6c757d);
178
+ --bs-multi-range-tooltip-bg: var(--bs-dark, #212529);
179
+ --bs-multi-range-tooltip-color: var(--bs-light, #f8f9fa);
180
+ }
181
+
182
+ :host([orientation=vertical]) {
183
+ width: auto;
184
+ height: 100%;
185
+ min-height: 8rem;
186
+ }
187
+
188
+ :host([disabled]) {
189
+ cursor: not-allowed;
190
+ }
191
+
192
+ .track {
193
+ position: relative;
194
+ background: var(--bs-multi-range-track-bg);
195
+ border-radius: var(--bs-multi-range-track-radius);
196
+ touch-action: none;
197
+ }
198
+
199
+ :host([orientation=horizontal]) .track {
200
+ width: 100%;
201
+ height: var(--bs-multi-range-track-thickness);
202
+ margin: calc(var(--bs-multi-range-thumb-size) / 2) 0;
203
+ }
204
+
205
+ :host([orientation=vertical]) .track {
206
+ width: var(--bs-multi-range-track-thickness);
207
+ height: 100%;
208
+ margin: 0 calc(var(--bs-multi-range-thumb-size) / 2);
209
+ }
210
+
211
+ .fill {
212
+ position: absolute;
213
+ background: var(--bs-multi-range-fill-bg);
214
+ border-radius: var(--bs-multi-range-track-radius);
215
+ }
216
+
217
+ :host([orientation=horizontal]) .fill {
218
+ top: 0;
219
+ bottom: 0;
220
+ }
221
+
222
+ :host([orientation=vertical]) .fill {
223
+ left: 0;
224
+ right: 0;
225
+ }
226
+
227
+ :host([disabled]) .fill {
228
+ background: var(--bs-multi-range-fill-bg-disabled);
229
+ }
230
+
231
+ .thumb {
232
+ position: absolute;
233
+ width: var(--bs-multi-range-thumb-size);
234
+ height: var(--bs-multi-range-thumb-size);
235
+ background: var(--bs-multi-range-thumb-bg);
236
+ border: 0;
237
+ border-radius: 50%;
238
+ box-shadow: var(--bs-multi-range-thumb-shadow);
239
+ cursor: grab;
240
+ touch-action: none;
241
+ padding: 0;
242
+ outline: none;
243
+ transition: transform 100ms ease-out;
244
+ }
245
+
246
+ .thumb:active {
247
+ cursor: grabbing;
248
+ transform: scale(1.1);
249
+ }
250
+
251
+ :host([orientation=horizontal]) .thumb {
252
+ top: 50%;
253
+ transform: translate(-50%, -50%);
254
+ }
255
+
256
+ :host([orientation=horizontal]) .thumb:active {
257
+ transform: translate(-50%, -50%) scale(1.1);
258
+ }
259
+
260
+ :host([orientation=horizontal]:dir(rtl)) .thumb {
261
+ transform: translate(50%, -50%);
262
+ }
263
+
264
+ :host([orientation=horizontal]:dir(rtl)) .thumb:active {
265
+ transform: translate(50%, -50%) scale(1.1);
266
+ }
267
+
268
+ :host([orientation=vertical]) .thumb {
269
+ left: 50%;
270
+ transform: translate(-50%, 50%);
271
+ }
272
+
273
+ :host([orientation=vertical]) .thumb:active {
274
+ transform: translate(-50%, 50%) scale(1.1);
275
+ }
276
+
277
+ :host([disabled]) .thumb {
278
+ background: var(--bs-multi-range-thumb-bg-disabled);
279
+ cursor: not-allowed;
280
+ }
281
+
282
+ .thumb:focus-visible {
283
+ box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.35), var(--bs-multi-range-thumb-shadow);
284
+ }
285
+
286
+ .tooltip {
287
+ position: absolute;
288
+ background: var(--bs-multi-range-tooltip-bg);
289
+ color: var(--bs-multi-range-tooltip-color);
290
+ font-size: 0.75rem;
291
+ font-weight: 500;
292
+ line-height: 1;
293
+ padding: 0.25rem 0.5rem;
294
+ border-radius: 0.25rem;
295
+ pointer-events: none;
296
+ white-space: nowrap;
297
+ opacity: 0;
298
+ transition: opacity 120ms ease-out;
299
+ z-index: 2;
300
+ }
301
+
302
+ :host([orientation=horizontal]) .tooltip {
303
+ bottom: calc(100% + 0.5rem);
304
+ left: 50%;
305
+ transform: translateX(-50%);
306
+ }
307
+
308
+ :host([orientation=vertical]) .tooltip {
309
+ left: calc(100% + 0.5rem);
310
+ top: 50%;
311
+ transform: translateY(-50%);
312
+ }
313
+
314
+ .thumb:hover .tooltip,
315
+ .thumb:focus-visible .tooltip,
316
+ .thumb:active .tooltip,
317
+ .thumb[data-dragging=true] .tooltip {
318
+ opacity: 1;
319
+ }
320
+
321
+ :host([disabled]) .tooltip {
322
+ display: none;
323
+ }
324
+
325
+ @media (prefers-reduced-motion: reduce) {
326
+ .thumb,
327
+ .tooltip {
328
+ transition: none;
329
+ }
330
+ }`);
331
+
332
+ /**
333
+ * Bootstrap-flavoured multi-thumb range slider.
334
+ *
335
+ * Block-crossing: thumbs cannot pass their neighbours. Identity is by index —
336
+ * value[0] is always the lowest thumb. The value setter normalises input by
337
+ * sorting ascending and clamping each entry to [min, max]; minDistance is
338
+ * enforced only at user-interaction entry points (pointer + keyboard), not on
339
+ * programmatic writes — callers' arrays are preserved verbatim within bounds.
340
+ *
341
+ * Events:
342
+ * - `value-input` fires continuously during drag and on every keyboard step.
343
+ * - `value-change` fires on commit (pointerup) and after every keyboard step.
344
+ *
345
+ * Both bubble and compose so the Angular wrapper's host listeners pick them up.
346
+ */
347
+ class MintMultiRangeElement extends LitElement {
348
+ constructor() {
349
+ super(...arguments);
350
+ this.min = 0;
351
+ this.max = 100;
352
+ this.step = 1;
353
+ this.minDistance = 0;
354
+ this.orientation = 'horizontal';
355
+ this.disabled = false;
356
+ this.formatValue = null;
357
+ this._value = null;
358
+ this.dragState = null;
359
+ // Cached references / state to keep the per-pointermove path off the hot path
360
+ // for getComputedStyle and querySelector. Track ref is set in firstUpdated()
361
+ // and is stable across renders. RTL is captured at gesture start so a single
362
+ // drag stream sees a coherent direction even if the host's `dir` changes mid-air.
363
+ this.trackEl = null;
364
+ this.rtlDuringGesture = null;
365
+ this.onThumbPointerDown = (thumbIndex, ev) => {
366
+ if (this.disabled)
367
+ return;
368
+ this.startDrag(thumbIndex, ev.pointerId, ev.currentTarget);
369
+ };
370
+ this.onTrackPointerDown = (ev) => {
371
+ if (this.disabled)
372
+ return;
373
+ // Ignore clicks that originated on a thumb — those are handled by the thumb's own pointerdown.
374
+ const path = ev.composedPath();
375
+ if (path.some(node => node instanceof HTMLElement && node.classList?.contains('thumb')))
376
+ return;
377
+ // Capture direction up-front so the first valueFromPointer / nearest-thumb
378
+ // calculation in this gesture sees a consistent value.
379
+ this.rtlDuringGesture = getComputedStyle(this).direction === 'rtl';
380
+ const targetValue = this.valueFromPointer(ev.clientX, ev.clientY);
381
+ const values = this.value;
382
+ const nearestIndex = this.nearestThumbIndex(values, targetValue);
383
+ if (this.moveThumb(nearestIndex, targetValue))
384
+ this.dispatchValueChange();
385
+ // Transfer drag to the nearest thumb so a continued press-and-drag keeps moving it.
386
+ const thumbEl = this.renderRoot.querySelector(`.thumb[data-thumb-index="${nearestIndex}"]`);
387
+ if (thumbEl)
388
+ this.startDrag(nearestIndex, ev.pointerId, thumbEl);
389
+ };
390
+ this.onPointerMove = (ev) => {
391
+ if (!this.dragState || ev.pointerId !== this.dragState.pointerId)
392
+ return;
393
+ const candidate = this.valueFromPointer(ev.clientX, ev.clientY);
394
+ this.moveThumb(this.dragState.thumbIndex, candidate);
395
+ };
396
+ this.onPointerUp = (ev) => {
397
+ if (!this.dragState || ev.pointerId !== this.dragState.pointerId)
398
+ return;
399
+ const target = ev.target;
400
+ if (target?.releasePointerCapture && target.hasPointerCapture(ev.pointerId)) {
401
+ target.releasePointerCapture(ev.pointerId);
402
+ }
403
+ this.dragState = null;
404
+ this.rtlDuringGesture = null;
405
+ this.requestUpdate();
406
+ this.dispatchValueChange();
407
+ };
408
+ this.onThumbKeyDown = (thumbIndex, ev) => {
409
+ if (this.disabled)
410
+ return;
411
+ const target = this.keyboardTarget(thumbIndex, ev.key);
412
+ if (target === null)
413
+ return;
414
+ ev.preventDefault();
415
+ if (this.moveThumb(thumbIndex, target))
416
+ this.dispatchValueChange();
417
+ };
418
+ }
419
+ static { this.styles = [styles]; }
420
+ static get observedAttributes() {
421
+ return [
422
+ ...(super.observedAttributes ?? []),
423
+ 'min',
424
+ 'max',
425
+ 'step',
426
+ 'min-distance',
427
+ 'orientation',
428
+ 'disabled',
429
+ ];
430
+ }
431
+ static { this.properties = {
432
+ value: { attribute: false },
433
+ min: { attribute: 'min', type: Number, reflect: true },
434
+ max: { attribute: 'max', type: Number, reflect: true },
435
+ step: { attribute: 'step', type: Number, reflect: true },
436
+ minDistance: { attribute: 'min-distance', type: Number },
437
+ orientation: { attribute: 'orientation', type: String, reflect: true },
438
+ disabled: { attribute: 'disabled', type: Boolean, reflect: true },
439
+ formatValue: { attribute: false },
440
+ }; }
441
+ connectedCallback() {
442
+ super.connectedCallback();
443
+ if (!this.hasAttribute('role'))
444
+ this.setAttribute('role', 'group');
445
+ }
446
+ firstUpdated() {
447
+ this.trackEl = this.renderRoot.querySelector('.track');
448
+ }
449
+ get value() {
450
+ return this._value ?? [this.min, this.max];
451
+ }
452
+ set value(next) {
453
+ const old = this._value;
454
+ const normalised = this.normalise(next);
455
+ // Skip the update entirely when the incoming array is shallow-equal to the
456
+ // current state — the wrapper's effect re-pushes the value on every
457
+ // value-input event during a drag, so without this we'd queue a redundant
458
+ // Lit update for every pointermove.
459
+ if (this.arraysEqual(old, normalised))
460
+ return;
461
+ this._value = normalised;
462
+ this.requestUpdate('value', old);
463
+ }
464
+ arraysEqual(a, b) {
465
+ if (a === b)
466
+ return true;
467
+ if (a === null || b === null)
468
+ return false;
469
+ if (a.length !== b.length)
470
+ return false;
471
+ return a.every((v, i) => v === b[i]);
472
+ }
473
+ normalise(input) {
474
+ if (!input || input.length === 0)
475
+ return null;
476
+ const clamped = input.map(v => this.clampToBounds(v));
477
+ return [...clamped].sort((a, b) => a - b);
478
+ }
479
+ clampToBounds(v) {
480
+ return Math.min(this.max, Math.max(this.min, v));
481
+ }
482
+ snapToStep(v) {
483
+ if (!this.step || this.step <= 0)
484
+ return this.clampToBounds(v);
485
+ const snapped = this.min + Math.round((v - this.min) / this.step) * this.step;
486
+ return this.clampToBounds(snapped);
487
+ }
488
+ percent(value) {
489
+ const range = this.max - this.min;
490
+ if (range <= 0)
491
+ return 0;
492
+ return ((value - this.min) / range) * 100;
493
+ }
494
+ formatThumb(value) {
495
+ if (this.formatValue) {
496
+ try {
497
+ return this.formatValue(value);
498
+ }
499
+ catch {
500
+ return String(value);
501
+ }
502
+ }
503
+ return String(value);
504
+ }
505
+ isVertical() {
506
+ return this.orientation === 'vertical';
507
+ }
508
+ /**
509
+ * Returns whether the host renders in RTL. Uses the cached value if a gesture
510
+ * is active (set in startDrag / onTrackPointerDown), otherwise reads
511
+ * getComputedStyle fresh — direction can come from any ancestor's `dir`,
512
+ * so document.dir or our own attribute aren't sufficient.
513
+ */
514
+ isRtl() {
515
+ if (this.rtlDuringGesture !== null)
516
+ return this.rtlDuringGesture;
517
+ return getComputedStyle(this).direction === 'rtl';
518
+ }
519
+ /**
520
+ * Apply the Block + minDistance constraint to a candidate value for thumb i.
521
+ * Returns the candidate clamped between its (already-respected) neighbours.
522
+ */
523
+ constrainThumb(values, i, candidate) {
524
+ const lower = i > 0 ? values[i - 1] + this.minDistance : this.min;
525
+ const upper = i < values.length - 1 ? values[i + 1] - this.minDistance : this.max;
526
+ return Math.min(Math.max(candidate, lower), upper);
527
+ }
528
+ /** Map a pointer coordinate inside the track rect to a value in [min, max]. */
529
+ valueFromPointer(clientX, clientY) {
530
+ const track = this.trackEl ?? this.renderRoot.querySelector('.track');
531
+ if (!track)
532
+ return this.min;
533
+ const rect = track.getBoundingClientRect();
534
+ let pct;
535
+ if (this.isVertical()) {
536
+ pct = (rect.bottom - clientY) / rect.height;
537
+ }
538
+ else if (this.isRtl()) {
539
+ pct = (rect.right - clientX) / rect.width;
540
+ }
541
+ else {
542
+ pct = (clientX - rect.left) / rect.width;
543
+ }
544
+ pct = Math.min(1, Math.max(0, pct));
545
+ const raw = this.min + pct * (this.max - this.min);
546
+ return this.snapToStep(raw);
547
+ }
548
+ /** Update one thumb in-place; emit `value-input`. Returns true if value changed. */
549
+ moveThumb(thumbIndex, candidate) {
550
+ const current = [...this.value];
551
+ const constrained = this.constrainThumb(current, thumbIndex, this.snapToStep(candidate));
552
+ if (constrained === current[thumbIndex])
553
+ return false;
554
+ current[thumbIndex] = constrained;
555
+ this._value = current;
556
+ this.requestUpdate('value');
557
+ this.dispatchEvent(new CustomEvent('value-input', {
558
+ detail: [...current],
559
+ bubbles: true,
560
+ composed: true,
561
+ }));
562
+ return true;
563
+ }
564
+ dispatchValueChange() {
565
+ this.dispatchEvent(new CustomEvent('value-change', {
566
+ detail: [...this.value],
567
+ bubbles: true,
568
+ composed: true,
569
+ }));
570
+ }
571
+ startDrag(thumbIndex, pointerId, target) {
572
+ if (this.disabled)
573
+ return;
574
+ if (this.rtlDuringGesture === null) {
575
+ this.rtlDuringGesture = getComputedStyle(this).direction === 'rtl';
576
+ }
577
+ target.setPointerCapture(pointerId);
578
+ target.focus();
579
+ this.dragState = { thumbIndex, pointerId };
580
+ this.requestUpdate();
581
+ }
582
+ /**
583
+ * Returns the index of the thumb closest to `target`. Ties (multiple thumbs
584
+ * stacked at the same value) are broken by direction: clicks to the right of
585
+ * the stack pick the highest-index thumb, clicks to the left pick the
586
+ * lowest. Without this, a stack would always select the lowest-index thumb,
587
+ * which is blocked by its higher-indexed neighbours and can't move toward
588
+ * the click — the user would see no response.
589
+ */
590
+ nearestThumbIndex(values, target) {
591
+ return values.reduce((best, v, i) => {
592
+ const dBest = Math.abs(values[best] - target);
593
+ const dCur = Math.abs(v - target);
594
+ if (dCur < dBest)
595
+ return i;
596
+ if (dCur > dBest)
597
+ return best;
598
+ // Tie. Prefer the thumb on the side of the target so it can move toward it.
599
+ return target > v ? i : best;
600
+ }, 0);
601
+ }
602
+ /** Return the target value for a key press, or null if the key isn't bound. */
603
+ keyboardTarget(thumbIndex, key) {
604
+ const step = this.step || 1;
605
+ const big = step * 10;
606
+ const current = this.value[thumbIndex];
607
+ const rtl = !this.isVertical() && this.isRtl();
608
+ switch (key) {
609
+ case 'ArrowRight': return current + (rtl ? -step : step);
610
+ case 'ArrowLeft': return current + (rtl ? step : -step);
611
+ case 'ArrowUp': return current + step;
612
+ case 'ArrowDown': return current - step;
613
+ case 'PageUp': return current + big;
614
+ case 'PageDown': return current - big;
615
+ case 'Home': return this.min;
616
+ case 'End': return this.max;
617
+ default: return null;
618
+ }
619
+ }
620
+ render() {
621
+ const values = this.value;
622
+ const vertical = this.isVertical();
623
+ const fills = values.slice(0, -1).map((v, i) => ({
624
+ from: this.percent(v),
625
+ to: this.percent(values[i + 1]),
626
+ }));
627
+ return html `
628
+ <div
629
+ class="track"
630
+ part="track"
631
+ @pointerdown=${this.onTrackPointerDown}
632
+ @pointermove=${this.onPointerMove}
633
+ @pointerup=${this.onPointerUp}
634
+ @pointercancel=${this.onPointerUp}
635
+ >
636
+ ${fills.map(f => this.renderFill(f, vertical))}
637
+ ${values.map((v, i) => this.renderThumb(v, i, vertical))}
638
+ </div>
639
+ `;
640
+ }
641
+ renderFill(segment, vertical) {
642
+ // Use logical `inset-inline-start` so the fill flips correctly in RTL;
643
+ // works as `left` in LTR and `right` in RTL.
644
+ const style = vertical
645
+ ? `bottom: ${segment.from}%; height: ${segment.to - segment.from}%;`
646
+ : `inset-inline-start: ${segment.from}%; width: ${segment.to - segment.from}%;`;
647
+ return html `<div class="fill" part="fill" style=${style}></div>`;
648
+ }
649
+ renderThumb(value, index, vertical) {
650
+ const pct = this.percent(value);
651
+ // Logical `inset-inline-start` so the thumb position flips in RTL.
652
+ const style = vertical ? `bottom: ${pct}%;` : `inset-inline-start: ${pct}%;`;
653
+ const formatted = this.formatThumb(value);
654
+ const isDragging = this.dragState?.thumbIndex === index;
655
+ // aria-valuetext only when formatValue is provided — otherwise aria-valuenow alone is read out.
656
+ const valueText = this.formatValue ? formatted : null;
657
+ return html `
658
+ <button
659
+ class="thumb"
660
+ part="thumb"
661
+ type="button"
662
+ role="slider"
663
+ data-thumb-index=${index}
664
+ data-dragging=${isDragging ? 'true' : 'false'}
665
+ aria-valuemin=${this.min}
666
+ aria-valuemax=${this.max}
667
+ aria-valuenow=${value}
668
+ aria-orientation=${this.orientation}
669
+ aria-valuetext=${valueText ?? nothing}
670
+ ?disabled=${this.disabled}
671
+ style=${style}
672
+ @pointerdown=${(ev) => this.onThumbPointerDown(index, ev)}
673
+ @keydown=${(ev) => this.onThumbKeyDown(index, ev)}
674
+ >
675
+ <span class="tooltip" part="tooltip">${formatted}</span>
676
+ </button>
677
+ `;
678
+ }
679
+ /** Returns a copy of the current values. */
680
+ getValues() {
681
+ return [...this.value];
682
+ }
683
+ }
684
+ if (typeof customElements !== 'undefined' && !customElements.get('mp-multi-range')) {
685
+ customElements.define('mp-multi-range', MintMultiRangeElement);
686
+ }
687
+
688
+ /**
689
+ * Generated bundle index. Do not edit.
690
+ */
691
+
692
+ export { BsMultiRangeComponent, BsMultiRangeValueAccessor, MintMultiRangeElement };
693
+ //# sourceMappingURL=mintplayer-ng-bootstrap-multi-range.mjs.map