@kanso-protocol/slider 0.1.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,432 @@
1
+ import * as i0 from '@angular/core';
2
+ import { EventEmitter, inject, ChangeDetectorRef, forwardRef, ViewChild, Output, Input, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
+
5
+ /**
6
+ * Kanso Protocol — Slider
7
+ *
8
+ * Numeric range input with a draggable thumb on a horizontal track.
9
+ * `mode="single"` exposes one thumb; `mode="range"` exposes two.
10
+ *
11
+ * Pointer drag, arrow-key stepping, track-click targeting, and a full
12
+ * ControlValueAccessor for reactive / template-driven forms. In range
13
+ * mode, `ngModel` is a `[number, number]` tuple; in single mode, a plain
14
+ * `number`.
15
+ */
16
+ class KpSliderComponent {
17
+ size = 'md';
18
+ mode = 'single';
19
+ min = 0;
20
+ max = 100;
21
+ step = 1;
22
+ showTicks = false;
23
+ showValueLabel = false;
24
+ showLabels = false;
25
+ minLabel = null;
26
+ maxLabel = null;
27
+ ariaLabel = '';
28
+ ariaLabelStart = '';
29
+ ariaLabelEnd = '';
30
+ valueFormatter = null;
31
+ set disabled(v) { this._disabled = v; }
32
+ get disabled() { return this._disabled || this.cvaDisabled; }
33
+ _disabled = false;
34
+ cvaDisabled = false;
35
+ set value(v) {
36
+ if (v == null)
37
+ return;
38
+ this.writeInternal(v);
39
+ }
40
+ get value() {
41
+ return this.mode === 'range' ? [this.value0, this.value1] : this.value0;
42
+ }
43
+ valueChange = new EventEmitter();
44
+ trackEl;
45
+ /** @internal */ value0 = 0;
46
+ /** @internal */ value1 = 100;
47
+ /** @internal */ focusedThumb = 0;
48
+ cdr = inject(ChangeDetectorRef);
49
+ dragIndex = null;
50
+ pointerId = null;
51
+ onMoveBound = (e) => this.onPointerMove(e);
52
+ onUpBound = (e) => this.onPointerUp(e);
53
+ ngOnDestroy() { this.endDrag(); }
54
+ get hostClasses() {
55
+ const c = ['kp-sl', `kp-sl--${this.size}`, `kp-sl--${this.mode}`];
56
+ if (this.disabled)
57
+ c.push('kp-sl--disabled');
58
+ return c.join(' ');
59
+ }
60
+ get fillLeft() {
61
+ return this.mode === 'single' ? 0 : this.pct(this.value0);
62
+ }
63
+ get fillWidth() {
64
+ return this.mode === 'single'
65
+ ? this.pct(this.value0)
66
+ : this.pct(this.value1) - this.pct(this.value0);
67
+ }
68
+ /** Five evenly-distributed positions across the track. */
69
+ tickPercents = [0, 25, 50, 75, 100];
70
+ pct(v) {
71
+ const span = this.max - this.min;
72
+ if (span <= 0)
73
+ return 0;
74
+ return Math.max(0, Math.min(100, ((v - this.min) / span) * 100));
75
+ }
76
+ formatValue(v) {
77
+ return this.valueFormatter ? this.valueFormatter(v) : String(v);
78
+ }
79
+ onTrackPointerDown(event) {
80
+ if (this.disabled)
81
+ return;
82
+ const target = event.target;
83
+ // Thumbs handle their own pointerdown; track click only targets bare track.
84
+ if (target.classList.contains('kp-sl__thumb'))
85
+ return;
86
+ const v = this.valueFromEvent(event);
87
+ const idx = this.nearestThumbIndex(v);
88
+ this.setThumbValue(idx, v, true);
89
+ this.startDrag(idx, event);
90
+ }
91
+ onThumbPointerDown(event, idx) {
92
+ if (this.disabled)
93
+ return;
94
+ this.focusedThumb = idx;
95
+ this.startDrag(idx, event);
96
+ }
97
+ startDrag(idx, event) {
98
+ this.dragIndex = idx;
99
+ this.pointerId = event.pointerId;
100
+ const target = event.target;
101
+ // Capture so dragging past the track bounds still fires moves here.
102
+ if (target.setPointerCapture) {
103
+ try {
104
+ target.setPointerCapture(event.pointerId);
105
+ }
106
+ catch { /* ignore */ }
107
+ }
108
+ window.addEventListener('pointermove', this.onMoveBound);
109
+ window.addEventListener('pointerup', this.onUpBound);
110
+ window.addEventListener('pointercancel', this.onUpBound);
111
+ event.preventDefault();
112
+ }
113
+ onPointerMove(event) {
114
+ if (this.dragIndex == null)
115
+ return;
116
+ if (this.pointerId != null && event.pointerId !== this.pointerId)
117
+ return;
118
+ const v = this.valueFromEvent(event);
119
+ this.setThumbValue(this.dragIndex, v, true);
120
+ }
121
+ onPointerUp(event) {
122
+ if (this.pointerId != null && event.pointerId !== this.pointerId)
123
+ return;
124
+ this.endDrag();
125
+ this.onTouched();
126
+ }
127
+ endDrag() {
128
+ this.dragIndex = null;
129
+ this.pointerId = null;
130
+ // endDrag is also called from ngOnDestroy; guard for bare-metal SSR where
131
+ // there is no `window` at teardown time.
132
+ if (typeof window !== 'undefined') {
133
+ window.removeEventListener('pointermove', this.onMoveBound);
134
+ window.removeEventListener('pointerup', this.onUpBound);
135
+ window.removeEventListener('pointercancel', this.onUpBound);
136
+ }
137
+ }
138
+ onKeyDown(event, idx) {
139
+ if (this.disabled)
140
+ return;
141
+ const big = event.shiftKey ? 10 : 1;
142
+ let next = null;
143
+ const current = idx === 0 ? this.value0 : this.value1;
144
+ switch (event.key) {
145
+ case 'ArrowRight':
146
+ case 'ArrowUp':
147
+ next = current + this.step * big;
148
+ break;
149
+ case 'ArrowLeft':
150
+ case 'ArrowDown':
151
+ next = current - this.step * big;
152
+ break;
153
+ case 'Home':
154
+ next = this.min;
155
+ break;
156
+ case 'End':
157
+ next = this.max;
158
+ break;
159
+ case 'PageUp':
160
+ next = current + this.step * 10;
161
+ break;
162
+ case 'PageDown':
163
+ next = current - this.step * 10;
164
+ break;
165
+ default: return;
166
+ }
167
+ event.preventDefault();
168
+ this.setThumbValue(idx, next, true);
169
+ }
170
+ onBlur() { this.onTouched(); }
171
+ valueFromEvent(event) {
172
+ const el = this.trackEl?.nativeElement;
173
+ if (!el)
174
+ return this.min;
175
+ const rect = el.getBoundingClientRect();
176
+ const ratio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0;
177
+ const raw = this.min + ratio * (this.max - this.min);
178
+ return this.snap(raw);
179
+ }
180
+ snap(v) {
181
+ let snapped = Math.round((v - this.min) / this.step) * this.step + this.min;
182
+ // Guard against float drift.
183
+ snapped = Math.round(snapped * 1e6) / 1e6;
184
+ return Math.max(this.min, Math.min(this.max, snapped));
185
+ }
186
+ nearestThumbIndex(v) {
187
+ if (this.mode === 'single')
188
+ return 0;
189
+ return Math.abs(v - this.value0) <= Math.abs(v - this.value1) ? 0 : 1;
190
+ }
191
+ setThumbValue(idx, next, emit) {
192
+ let changed = false;
193
+ const snapped = this.snap(next);
194
+ if (idx === 0) {
195
+ const clamped = this.mode === 'range' ? Math.min(snapped, this.value1) : snapped;
196
+ if (clamped !== this.value0) {
197
+ this.value0 = clamped;
198
+ changed = true;
199
+ }
200
+ }
201
+ else {
202
+ const clamped = Math.max(snapped, this.value0);
203
+ if (clamped !== this.value1) {
204
+ this.value1 = clamped;
205
+ changed = true;
206
+ }
207
+ }
208
+ if (!changed)
209
+ return;
210
+ this.cdr.markForCheck();
211
+ if (emit) {
212
+ const v = this.value;
213
+ this.valueChange.emit(v);
214
+ this.onChange(v);
215
+ }
216
+ }
217
+ /** Accepts a plain number (single) or a [start, end] tuple (range). */
218
+ writeInternal(v) {
219
+ if (Array.isArray(v)) {
220
+ const [a, b] = v;
221
+ this.value0 = this.snap(Math.min(a, b));
222
+ this.value1 = this.snap(Math.max(a, b));
223
+ }
224
+ else if (typeof v === 'number') {
225
+ this.value0 = this.snap(v);
226
+ }
227
+ this.cdr.markForCheck();
228
+ }
229
+ // ControlValueAccessor
230
+ onChange = () => { };
231
+ onTouched = () => { };
232
+ writeValue(v) {
233
+ if (v == null)
234
+ return;
235
+ this.writeInternal(v);
236
+ }
237
+ registerOnChange(fn) { this.onChange = fn; }
238
+ registerOnTouched(fn) { this.onTouched = fn; }
239
+ setDisabledState(d) { this.cvaDisabled = d; this.cdr.markForCheck(); }
240
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpSliderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
241
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: KpSliderComponent, isStandalone: true, selector: "kp-slider", inputs: { size: "size", mode: "mode", min: "min", max: "max", step: "step", showTicks: "showTicks", showValueLabel: "showValueLabel", showLabels: "showLabels", minLabel: "minLabel", maxLabel: "maxLabel", ariaLabel: "ariaLabel", ariaLabelStart: "ariaLabelStart", ariaLabelEnd: "ariaLabelEnd", valueFormatter: "valueFormatter", disabled: "disabled", value: "value" }, outputs: { valueChange: "valueChange" }, host: { properties: { "class": "hostClasses" } }, providers: [
242
+ {
243
+ provide: NG_VALUE_ACCESSOR,
244
+ useExisting: forwardRef(() => KpSliderComponent),
245
+ multi: true,
246
+ },
247
+ ], viewQueries: [{ propertyName: "trackEl", first: true, predicate: ["track"], descendants: true }], ngImport: i0, template: `
248
+ @if (showValueLabel) {
249
+ <div class="kp-sl__values">
250
+ @if (mode === 'single') {
251
+ <span class="kp-sl__value" [style.left.%]="pct(value0)">{{ formatValue(value0) }}</span>
252
+ } @else {
253
+ <span class="kp-sl__value" [style.left.%]="pct(value0)">{{ formatValue(value0) }}</span>
254
+ <span class="kp-sl__value" [style.left.%]="pct(value1)">{{ formatValue(value1) }}</span>
255
+ }
256
+ </div>
257
+ }
258
+
259
+ <div #track class="kp-sl__track-wrap" (pointerdown)="onTrackPointerDown($event)">
260
+ <div class="kp-sl__track"></div>
261
+ <div class="kp-sl__track-fill" [style.left.%]="fillLeft" [style.width.%]="fillWidth"></div>
262
+
263
+ @if (showTicks) {
264
+ @for (p of tickPercents; track p) {
265
+ <span class="kp-sl__tick" [style.left.%]="p"></span>
266
+ }
267
+ }
268
+
269
+ <button
270
+ type="button"
271
+ class="kp-sl__thumb"
272
+ role="slider"
273
+ [attr.aria-valuemin]="min"
274
+ [attr.aria-valuemax]="max"
275
+ [attr.aria-valuenow]="value0"
276
+ [attr.aria-label]="mode === 'range' ? (ariaLabelStart || 'Start') : (ariaLabel || null)"
277
+ [attr.aria-orientation]="'horizontal'"
278
+ [disabled]="disabled"
279
+ [style.left.%]="pct(value0)"
280
+ (pointerdown)="onThumbPointerDown($event, 0)"
281
+ (keydown)="onKeyDown($event, 0)"
282
+ (focus)="focusedThumb = 0"
283
+ (blur)="onBlur()"
284
+ ></button>
285
+
286
+ @if (mode === 'range') {
287
+ <button
288
+ type="button"
289
+ class="kp-sl__thumb"
290
+ role="slider"
291
+ [attr.aria-valuemin]="min"
292
+ [attr.aria-valuemax]="max"
293
+ [attr.aria-valuenow]="value1"
294
+ [attr.aria-label]="ariaLabelEnd || 'End'"
295
+ [attr.aria-orientation]="'horizontal'"
296
+ [disabled]="disabled"
297
+ [style.left.%]="pct(value1)"
298
+ (pointerdown)="onThumbPointerDown($event, 1)"
299
+ (keydown)="onKeyDown($event, 1)"
300
+ (focus)="focusedThumb = 1"
301
+ (blur)="onBlur()"
302
+ ></button>
303
+ }
304
+ </div>
305
+
306
+ @if (showLabels) {
307
+ <div class="kp-sl__labels">
308
+ <span>{{ minLabel ?? min }}</span>
309
+ <span>{{ maxLabel ?? max }}</span>
310
+ </div>
311
+ }
312
+ `, isInline: true, styles: [":host{display:block;width:100%;min-width:160px;font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);--kp-sl-track-h: 6px;--kp-sl-thumb-d: 20px}:host(.kp-sl--sm){--kp-sl-track-h: 4px;--kp-sl-thumb-d: 16px}:host(.kp-sl--md){--kp-sl-track-h: 6px;--kp-sl-thumb-d: 20px}:host(.kp-sl--lg){--kp-sl-track-h: 8px;--kp-sl-thumb-d: 24px}.kp-sl__values{position:relative;height:20px;margin-bottom:6px}.kp-sl__value{position:absolute;top:0;transform:translate(-50%);font-size:12px;line-height:16px;font-weight:500;color:var(--kp-color-slider-value, var(--kp-color-gray-900));font-variant-numeric:tabular-nums;white-space:nowrap;pointer-events:none}.kp-sl__track-wrap{position:relative;height:var(--kp-sl-thumb-d);display:flex;align-items:center;touch-action:none}.kp-sl__track{position:absolute;left:0;right:0;height:var(--kp-sl-track-h);background:var(--kp-color-slider-track-empty, var(--kp-color-gray-200));border-radius:calc(var(--kp-sl-track-h) / 2)}.kp-sl__track-fill{position:absolute;height:var(--kp-sl-track-h);background:var(--kp-color-slider-track-filled, var(--kp-color-blue-600));border-radius:calc(var(--kp-sl-track-h) / 2)}.kp-sl__tick{position:absolute;width:4px;height:4px;border-radius:50%;background:var(--kp-color-slider-tick, var(--kp-color-gray-400));transform:translate(-50%);pointer-events:none}.kp-sl__thumb{all:unset;position:absolute;width:var(--kp-sl-thumb-d);height:var(--kp-sl-thumb-d);border-radius:50%;background:var(--kp-color-slider-thumb-bg, var(--kp-color-white));border:2px solid var(--kp-color-slider-thumb-border, var(--kp-color-blue-600));box-shadow:var(--kp-elevation-raised);cursor:grab;transform:translate(-50%);transition:border-color var(--kp-motion-duration-fast) ease,box-shadow .12s ease;box-sizing:border-box;touch-action:none}.kp-sl__thumb:active{cursor:grabbing}.kp-sl__thumb:hover{border-color:var(--kp-color-blue-700, var(--kp-color-blue-700))}.kp-sl__thumb:focus-visible{box-shadow:0 0 0 4px var(--kp-color-slider-thumb-ring-focus, var(--kp-color-blue-100)),0 2px 4px #00000014}.kp-sl__thumb[disabled]{cursor:not-allowed;background:var(--kp-color-gray-100, var(--kp-color-gray-100));border-color:var(--kp-color-gray-400, var(--kp-color-gray-400));box-shadow:none}:host(.kp-sl--disabled) .kp-sl__track-fill{background:var(--kp-color-gray-400, var(--kp-color-gray-400))}:host(.kp-sl--disabled) .kp-sl__track{background:var(--kp-color-gray-100, var(--kp-color-gray-100))}.kp-sl__labels{display:flex;justify-content:space-between;margin-top:8px;font-size:12px;line-height:16px;color:var(--kp-color-slider-label, var(--kp-color-gray-600));font-variant-numeric:tabular-nums}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
313
+ }
314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: KpSliderComponent, decorators: [{
315
+ type: Component,
316
+ args: [{ selector: 'kp-slider', imports: [], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
317
+ {
318
+ provide: NG_VALUE_ACCESSOR,
319
+ useExisting: forwardRef(() => KpSliderComponent),
320
+ multi: true,
321
+ },
322
+ ], host: { '[class]': 'hostClasses' }, template: `
323
+ @if (showValueLabel) {
324
+ <div class="kp-sl__values">
325
+ @if (mode === 'single') {
326
+ <span class="kp-sl__value" [style.left.%]="pct(value0)">{{ formatValue(value0) }}</span>
327
+ } @else {
328
+ <span class="kp-sl__value" [style.left.%]="pct(value0)">{{ formatValue(value0) }}</span>
329
+ <span class="kp-sl__value" [style.left.%]="pct(value1)">{{ formatValue(value1) }}</span>
330
+ }
331
+ </div>
332
+ }
333
+
334
+ <div #track class="kp-sl__track-wrap" (pointerdown)="onTrackPointerDown($event)">
335
+ <div class="kp-sl__track"></div>
336
+ <div class="kp-sl__track-fill" [style.left.%]="fillLeft" [style.width.%]="fillWidth"></div>
337
+
338
+ @if (showTicks) {
339
+ @for (p of tickPercents; track p) {
340
+ <span class="kp-sl__tick" [style.left.%]="p"></span>
341
+ }
342
+ }
343
+
344
+ <button
345
+ type="button"
346
+ class="kp-sl__thumb"
347
+ role="slider"
348
+ [attr.aria-valuemin]="min"
349
+ [attr.aria-valuemax]="max"
350
+ [attr.aria-valuenow]="value0"
351
+ [attr.aria-label]="mode === 'range' ? (ariaLabelStart || 'Start') : (ariaLabel || null)"
352
+ [attr.aria-orientation]="'horizontal'"
353
+ [disabled]="disabled"
354
+ [style.left.%]="pct(value0)"
355
+ (pointerdown)="onThumbPointerDown($event, 0)"
356
+ (keydown)="onKeyDown($event, 0)"
357
+ (focus)="focusedThumb = 0"
358
+ (blur)="onBlur()"
359
+ ></button>
360
+
361
+ @if (mode === 'range') {
362
+ <button
363
+ type="button"
364
+ class="kp-sl__thumb"
365
+ role="slider"
366
+ [attr.aria-valuemin]="min"
367
+ [attr.aria-valuemax]="max"
368
+ [attr.aria-valuenow]="value1"
369
+ [attr.aria-label]="ariaLabelEnd || 'End'"
370
+ [attr.aria-orientation]="'horizontal'"
371
+ [disabled]="disabled"
372
+ [style.left.%]="pct(value1)"
373
+ (pointerdown)="onThumbPointerDown($event, 1)"
374
+ (keydown)="onKeyDown($event, 1)"
375
+ (focus)="focusedThumb = 1"
376
+ (blur)="onBlur()"
377
+ ></button>
378
+ }
379
+ </div>
380
+
381
+ @if (showLabels) {
382
+ <div class="kp-sl__labels">
383
+ <span>{{ minLabel ?? min }}</span>
384
+ <span>{{ maxLabel ?? max }}</span>
385
+ </div>
386
+ }
387
+ `, styles: [":host{display:block;width:100%;min-width:160px;font-family:var(--kp-font-family-sans, \"Onest\", system-ui, sans-serif);--kp-sl-track-h: 6px;--kp-sl-thumb-d: 20px}:host(.kp-sl--sm){--kp-sl-track-h: 4px;--kp-sl-thumb-d: 16px}:host(.kp-sl--md){--kp-sl-track-h: 6px;--kp-sl-thumb-d: 20px}:host(.kp-sl--lg){--kp-sl-track-h: 8px;--kp-sl-thumb-d: 24px}.kp-sl__values{position:relative;height:20px;margin-bottom:6px}.kp-sl__value{position:absolute;top:0;transform:translate(-50%);font-size:12px;line-height:16px;font-weight:500;color:var(--kp-color-slider-value, var(--kp-color-gray-900));font-variant-numeric:tabular-nums;white-space:nowrap;pointer-events:none}.kp-sl__track-wrap{position:relative;height:var(--kp-sl-thumb-d);display:flex;align-items:center;touch-action:none}.kp-sl__track{position:absolute;left:0;right:0;height:var(--kp-sl-track-h);background:var(--kp-color-slider-track-empty, var(--kp-color-gray-200));border-radius:calc(var(--kp-sl-track-h) / 2)}.kp-sl__track-fill{position:absolute;height:var(--kp-sl-track-h);background:var(--kp-color-slider-track-filled, var(--kp-color-blue-600));border-radius:calc(var(--kp-sl-track-h) / 2)}.kp-sl__tick{position:absolute;width:4px;height:4px;border-radius:50%;background:var(--kp-color-slider-tick, var(--kp-color-gray-400));transform:translate(-50%);pointer-events:none}.kp-sl__thumb{all:unset;position:absolute;width:var(--kp-sl-thumb-d);height:var(--kp-sl-thumb-d);border-radius:50%;background:var(--kp-color-slider-thumb-bg, var(--kp-color-white));border:2px solid var(--kp-color-slider-thumb-border, var(--kp-color-blue-600));box-shadow:var(--kp-elevation-raised);cursor:grab;transform:translate(-50%);transition:border-color var(--kp-motion-duration-fast) ease,box-shadow .12s ease;box-sizing:border-box;touch-action:none}.kp-sl__thumb:active{cursor:grabbing}.kp-sl__thumb:hover{border-color:var(--kp-color-blue-700, var(--kp-color-blue-700))}.kp-sl__thumb:focus-visible{box-shadow:0 0 0 4px var(--kp-color-slider-thumb-ring-focus, var(--kp-color-blue-100)),0 2px 4px #00000014}.kp-sl__thumb[disabled]{cursor:not-allowed;background:var(--kp-color-gray-100, var(--kp-color-gray-100));border-color:var(--kp-color-gray-400, var(--kp-color-gray-400));box-shadow:none}:host(.kp-sl--disabled) .kp-sl__track-fill{background:var(--kp-color-gray-400, var(--kp-color-gray-400))}:host(.kp-sl--disabled) .kp-sl__track{background:var(--kp-color-gray-100, var(--kp-color-gray-100))}.kp-sl__labels{display:flex;justify-content:space-between;margin-top:8px;font-size:12px;line-height:16px;color:var(--kp-color-slider-label, var(--kp-color-gray-600));font-variant-numeric:tabular-nums}\n"] }]
388
+ }], propDecorators: { size: [{
389
+ type: Input
390
+ }], mode: [{
391
+ type: Input
392
+ }], min: [{
393
+ type: Input
394
+ }], max: [{
395
+ type: Input
396
+ }], step: [{
397
+ type: Input
398
+ }], showTicks: [{
399
+ type: Input
400
+ }], showValueLabel: [{
401
+ type: Input
402
+ }], showLabels: [{
403
+ type: Input
404
+ }], minLabel: [{
405
+ type: Input
406
+ }], maxLabel: [{
407
+ type: Input
408
+ }], ariaLabel: [{
409
+ type: Input
410
+ }], ariaLabelStart: [{
411
+ type: Input
412
+ }], ariaLabelEnd: [{
413
+ type: Input
414
+ }], valueFormatter: [{
415
+ type: Input
416
+ }], disabled: [{
417
+ type: Input
418
+ }], value: [{
419
+ type: Input
420
+ }], valueChange: [{
421
+ type: Output
422
+ }], trackEl: [{
423
+ type: ViewChild,
424
+ args: ['track']
425
+ }] } });
426
+
427
+ /**
428
+ * Generated bundle index. Do not edit.
429
+ */
430
+
431
+ export { KpSliderComponent };
432
+ //# sourceMappingURL=kanso-protocol-slider.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kanso-protocol-slider.mjs","sources":["../../../../../packages/components/slider/src/slider.component.ts","../../../../../packages/components/slider/src/kanso-protocol-slider.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n ChangeDetectorRef,\n Component,\n ElementRef,\n EventEmitter,\n Input,\n OnDestroy,\n Output,\n ViewChild,\n forwardRef,\n inject,\n} from '@angular/core';\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nexport type KpSliderSize = 'sm' | 'md' | 'lg';\nexport type KpSliderMode = 'single' | 'range';\nexport type KpSliderValue = number | readonly [number, number];\n\n/**\n * Kanso Protocol — Slider\n *\n * Numeric range input with a draggable thumb on a horizontal track.\n * `mode=\"single\"` exposes one thumb; `mode=\"range\"` exposes two.\n *\n * Pointer drag, arrow-key stepping, track-click targeting, and a full\n * ControlValueAccessor for reactive / template-driven forms. In range\n * mode, `ngModel` is a `[number, number]` tuple; in single mode, a plain\n * `number`.\n */\n@Component({\n selector: 'kp-slider',\n imports: [],\n changeDetection: ChangeDetectionStrategy.OnPush,\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => KpSliderComponent),\n multi: true,\n },\n ],\n host: { '[class]': 'hostClasses' },\n template: `\n @if (showValueLabel) {\n <div class=\"kp-sl__values\">\n @if (mode === 'single') {\n <span class=\"kp-sl__value\" [style.left.%]=\"pct(value0)\">{{ formatValue(value0) }}</span>\n } @else {\n <span class=\"kp-sl__value\" [style.left.%]=\"pct(value0)\">{{ formatValue(value0) }}</span>\n <span class=\"kp-sl__value\" [style.left.%]=\"pct(value1)\">{{ formatValue(value1) }}</span>\n }\n </div>\n }\n\n <div #track class=\"kp-sl__track-wrap\" (pointerdown)=\"onTrackPointerDown($event)\">\n <div class=\"kp-sl__track\"></div>\n <div class=\"kp-sl__track-fill\" [style.left.%]=\"fillLeft\" [style.width.%]=\"fillWidth\"></div>\n\n @if (showTicks) {\n @for (p of tickPercents; track p) {\n <span class=\"kp-sl__tick\" [style.left.%]=\"p\"></span>\n }\n }\n\n <button\n type=\"button\"\n class=\"kp-sl__thumb\"\n role=\"slider\"\n [attr.aria-valuemin]=\"min\"\n [attr.aria-valuemax]=\"max\"\n [attr.aria-valuenow]=\"value0\"\n [attr.aria-label]=\"mode === 'range' ? (ariaLabelStart || 'Start') : (ariaLabel || null)\"\n [attr.aria-orientation]=\"'horizontal'\"\n [disabled]=\"disabled\"\n [style.left.%]=\"pct(value0)\"\n (pointerdown)=\"onThumbPointerDown($event, 0)\"\n (keydown)=\"onKeyDown($event, 0)\"\n (focus)=\"focusedThumb = 0\"\n (blur)=\"onBlur()\"\n ></button>\n\n @if (mode === 'range') {\n <button\n type=\"button\"\n class=\"kp-sl__thumb\"\n role=\"slider\"\n [attr.aria-valuemin]=\"min\"\n [attr.aria-valuemax]=\"max\"\n [attr.aria-valuenow]=\"value1\"\n [attr.aria-label]=\"ariaLabelEnd || 'End'\"\n [attr.aria-orientation]=\"'horizontal'\"\n [disabled]=\"disabled\"\n [style.left.%]=\"pct(value1)\"\n (pointerdown)=\"onThumbPointerDown($event, 1)\"\n (keydown)=\"onKeyDown($event, 1)\"\n (focus)=\"focusedThumb = 1\"\n (blur)=\"onBlur()\"\n ></button>\n }\n </div>\n\n @if (showLabels) {\n <div class=\"kp-sl__labels\">\n <span>{{ minLabel ?? min }}</span>\n <span>{{ maxLabel ?? max }}</span>\n </div>\n }\n `,\n styles: [`\n :host {\n display: block;\n width: 100%;\n min-width: 160px;\n font-family: var(--kp-font-family-sans, 'Onest', system-ui, sans-serif);\n --kp-sl-track-h: 6px;\n --kp-sl-thumb-d: 20px;\n }\n :host(.kp-sl--sm) { --kp-sl-track-h: 4px; --kp-sl-thumb-d: 16px; }\n :host(.kp-sl--md) { --kp-sl-track-h: 6px; --kp-sl-thumb-d: 20px; }\n :host(.kp-sl--lg) { --kp-sl-track-h: 8px; --kp-sl-thumb-d: 24px; }\n\n .kp-sl__values {\n position: relative;\n height: 20px;\n margin-bottom: 6px;\n }\n .kp-sl__value {\n position: absolute;\n top: 0;\n transform: translateX(-50%);\n font-size: 12px;\n line-height: 16px;\n font-weight: 500;\n color: var(--kp-color-slider-value, var(--kp-color-gray-900));\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n pointer-events: none;\n }\n\n .kp-sl__track-wrap {\n position: relative;\n height: var(--kp-sl-thumb-d);\n display: flex;\n align-items: center;\n touch-action: none;\n }\n .kp-sl__track {\n position: absolute;\n left: 0; right: 0;\n height: var(--kp-sl-track-h);\n background: var(--kp-color-slider-track-empty, var(--kp-color-gray-200));\n border-radius: calc(var(--kp-sl-track-h) / 2);\n }\n .kp-sl__track-fill {\n position: absolute;\n height: var(--kp-sl-track-h);\n background: var(--kp-color-slider-track-filled, var(--kp-color-blue-600));\n border-radius: calc(var(--kp-sl-track-h) / 2);\n }\n .kp-sl__tick {\n position: absolute;\n width: 4px;\n height: 4px;\n border-radius: 50%;\n background: var(--kp-color-slider-tick, var(--kp-color-gray-400));\n transform: translateX(-50%);\n pointer-events: none;\n }\n\n .kp-sl__thumb {\n all: unset;\n position: absolute;\n width: var(--kp-sl-thumb-d);\n height: var(--kp-sl-thumb-d);\n border-radius: 50%;\n background: var(--kp-color-slider-thumb-bg, var(--kp-color-white));\n border: 2px solid var(--kp-color-slider-thumb-border, var(--kp-color-blue-600));\n box-shadow: var(--kp-elevation-raised);\n cursor: grab;\n transform: translateX(-50%);\n transition: border-color var(--kp-motion-duration-fast) ease, box-shadow 120ms ease;\n box-sizing: border-box;\n touch-action: none;\n }\n .kp-sl__thumb:active { cursor: grabbing; }\n .kp-sl__thumb:hover { border-color: var(--kp-color-blue-700, var(--kp-color-blue-700)); }\n .kp-sl__thumb:focus-visible {\n box-shadow: 0 0 0 4px var(--kp-color-slider-thumb-ring-focus, var(--kp-color-blue-100)), 0 2px 4px rgba(0, 0, 0, 0.08);\n }\n .kp-sl__thumb[disabled] {\n cursor: not-allowed;\n background: var(--kp-color-gray-100, var(--kp-color-gray-100));\n border-color: var(--kp-color-gray-400, var(--kp-color-gray-400));\n box-shadow: none;\n }\n\n :host(.kp-sl--disabled) .kp-sl__track-fill {\n background: var(--kp-color-gray-400, var(--kp-color-gray-400));\n }\n :host(.kp-sl--disabled) .kp-sl__track {\n background: var(--kp-color-gray-100, var(--kp-color-gray-100));\n }\n\n .kp-sl__labels {\n display: flex;\n justify-content: space-between;\n margin-top: 8px;\n font-size: 12px;\n line-height: 16px;\n color: var(--kp-color-slider-label, var(--kp-color-gray-600));\n font-variant-numeric: tabular-nums;\n }\n `],\n})\nexport class KpSliderComponent implements ControlValueAccessor, OnDestroy {\n @Input() size: KpSliderSize = 'md';\n @Input() mode: KpSliderMode = 'single';\n @Input() min = 0;\n @Input() max = 100;\n @Input() step = 1;\n @Input() showTicks = false;\n @Input() showValueLabel = false;\n @Input() showLabels = false;\n @Input() minLabel: string | null = null;\n @Input() maxLabel: string | null = null;\n @Input() ariaLabel = '';\n @Input() ariaLabelStart = '';\n @Input() ariaLabelEnd = '';\n @Input() valueFormatter: ((v: number) => string) | null = null;\n\n @Input()\n set disabled(v: boolean) { this._disabled = v; }\n get disabled(): boolean { return this._disabled || this.cvaDisabled; }\n private _disabled = false;\n private cvaDisabled = false;\n\n @Input()\n set value(v: KpSliderValue | null | undefined) {\n if (v == null) return;\n this.writeInternal(v);\n }\n get value(): KpSliderValue {\n return this.mode === 'range' ? [this.value0, this.value1] : this.value0;\n }\n\n @Output() readonly valueChange = new EventEmitter<KpSliderValue>();\n\n @ViewChild('track') trackEl?: ElementRef<HTMLElement>;\n\n /** @internal */ value0 = 0;\n /** @internal */ value1 = 100;\n /** @internal */ focusedThumb: 0 | 1 = 0;\n\n private readonly cdr = inject(ChangeDetectorRef);\n private dragIndex: 0 | 1 | null = null;\n private pointerId: number | null = null;\n private onMoveBound = (e: PointerEvent) => this.onPointerMove(e);\n private onUpBound = (e: PointerEvent) => this.onPointerUp(e);\n\n ngOnDestroy(): void { this.endDrag(); }\n\n get hostClasses(): string {\n const c = ['kp-sl', `kp-sl--${this.size}`, `kp-sl--${this.mode}`];\n if (this.disabled) c.push('kp-sl--disabled');\n return c.join(' ');\n }\n\n get fillLeft(): number {\n return this.mode === 'single' ? 0 : this.pct(this.value0);\n }\n get fillWidth(): number {\n return this.mode === 'single'\n ? this.pct(this.value0)\n : this.pct(this.value1) - this.pct(this.value0);\n }\n\n /** Five evenly-distributed positions across the track. */\n readonly tickPercents = [0, 25, 50, 75, 100];\n\n pct(v: number): number {\n const span = this.max - this.min;\n if (span <= 0) return 0;\n return Math.max(0, Math.min(100, ((v - this.min) / span) * 100));\n }\n\n formatValue(v: number): string {\n return this.valueFormatter ? this.valueFormatter(v) : String(v);\n }\n\n onTrackPointerDown(event: PointerEvent): void {\n if (this.disabled) return;\n const target = event.target as HTMLElement;\n // Thumbs handle their own pointerdown; track click only targets bare track.\n if (target.classList.contains('kp-sl__thumb')) return;\n const v = this.valueFromEvent(event);\n const idx = this.nearestThumbIndex(v);\n this.setThumbValue(idx, v, true);\n this.startDrag(idx, event);\n }\n\n onThumbPointerDown(event: PointerEvent, idx: 0 | 1): void {\n if (this.disabled) return;\n this.focusedThumb = idx;\n this.startDrag(idx, event);\n }\n\n private startDrag(idx: 0 | 1, event: PointerEvent): void {\n this.dragIndex = idx;\n this.pointerId = event.pointerId;\n const target = event.target as HTMLElement;\n // Capture so dragging past the track bounds still fires moves here.\n if (target.setPointerCapture) {\n try { target.setPointerCapture(event.pointerId); } catch { /* ignore */ }\n }\n window.addEventListener('pointermove', this.onMoveBound);\n window.addEventListener('pointerup', this.onUpBound);\n window.addEventListener('pointercancel', this.onUpBound);\n event.preventDefault();\n }\n\n private onPointerMove(event: PointerEvent): void {\n if (this.dragIndex == null) return;\n if (this.pointerId != null && event.pointerId !== this.pointerId) return;\n const v = this.valueFromEvent(event);\n this.setThumbValue(this.dragIndex, v, true);\n }\n\n private onPointerUp(event: PointerEvent): void {\n if (this.pointerId != null && event.pointerId !== this.pointerId) return;\n this.endDrag();\n this.onTouched();\n }\n\n private endDrag(): void {\n this.dragIndex = null;\n this.pointerId = null;\n // endDrag is also called from ngOnDestroy; guard for bare-metal SSR where\n // there is no `window` at teardown time.\n if (typeof window !== 'undefined') {\n window.removeEventListener('pointermove', this.onMoveBound);\n window.removeEventListener('pointerup', this.onUpBound);\n window.removeEventListener('pointercancel', this.onUpBound);\n }\n }\n\n onKeyDown(event: KeyboardEvent, idx: 0 | 1): void {\n if (this.disabled) return;\n const big = event.shiftKey ? 10 : 1;\n let next: number | null = null;\n const current = idx === 0 ? this.value0 : this.value1;\n switch (event.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n next = current + this.step * big; break;\n case 'ArrowLeft':\n case 'ArrowDown':\n next = current - this.step * big; break;\n case 'Home': next = this.min; break;\n case 'End': next = this.max; break;\n case 'PageUp': next = current + this.step * 10; break;\n case 'PageDown': next = current - this.step * 10; break;\n default: return;\n }\n event.preventDefault();\n this.setThumbValue(idx, next, true);\n }\n\n onBlur(): void { this.onTouched(); }\n\n private valueFromEvent(event: PointerEvent): number {\n const el = this.trackEl?.nativeElement;\n if (!el) return this.min;\n const rect = el.getBoundingClientRect();\n const ratio = rect.width > 0 ? (event.clientX - rect.left) / rect.width : 0;\n const raw = this.min + ratio * (this.max - this.min);\n return this.snap(raw);\n }\n\n private snap(v: number): number {\n let snapped = Math.round((v - this.min) / this.step) * this.step + this.min;\n // Guard against float drift.\n snapped = Math.round(snapped * 1e6) / 1e6;\n return Math.max(this.min, Math.min(this.max, snapped));\n }\n\n private nearestThumbIndex(v: number): 0 | 1 {\n if (this.mode === 'single') return 0;\n return Math.abs(v - this.value0) <= Math.abs(v - this.value1) ? 0 : 1;\n }\n\n private setThumbValue(idx: 0 | 1, next: number, emit: boolean): void {\n let changed = false;\n const snapped = this.snap(next);\n if (idx === 0) {\n const clamped = this.mode === 'range' ? Math.min(snapped, this.value1) : snapped;\n if (clamped !== this.value0) { this.value0 = clamped; changed = true; }\n } else {\n const clamped = Math.max(snapped, this.value0);\n if (clamped !== this.value1) { this.value1 = clamped; changed = true; }\n }\n if (!changed) return;\n this.cdr.markForCheck();\n if (emit) {\n const v = this.value;\n this.valueChange.emit(v);\n this.onChange(v);\n }\n }\n\n /** Accepts a plain number (single) or a [start, end] tuple (range). */\n private writeInternal(v: KpSliderValue): void {\n if (Array.isArray(v)) {\n const [a, b] = v;\n this.value0 = this.snap(Math.min(a, b));\n this.value1 = this.snap(Math.max(a, b));\n } else if (typeof v === 'number') {\n this.value0 = this.snap(v);\n }\n this.cdr.markForCheck();\n }\n\n // ControlValueAccessor\n private onChange: (v: KpSliderValue) => void = () => { /* no-op */ };\n private onTouched: () => void = () => { /* no-op */ };\n\n writeValue(v: KpSliderValue | null | undefined): void {\n if (v == null) return;\n this.writeInternal(v);\n }\n registerOnChange(fn: (v: KpSliderValue) => void): void { this.onChange = fn; }\n registerOnTouched(fn: () => void): void { this.onTouched = fn; }\n setDisabledState(d: boolean): void { this.cvaDisabled = d; this.cdr.markForCheck(); }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kanso-protocol/slider",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "peerDependencies": {
6
+ "@angular/core": "^18.0.0",
7
+ "@angular/common": "^18.0.0",
8
+ "@angular/forms": "^18.0.0",
9
+ "@kanso-protocol/core": "^0.0.1"
10
+ },
11
+ "description": "Kanso Protocol — slider (component).",
12
+ "author": "GregNBlack",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/GregNBlack/kanso-protocol.git",
16
+ "directory": "packages/components/slider"
17
+ },
18
+ "homepage": "https://gregnblack.github.io/kanso-protocol/?path=/docs/components-slider--docs",
19
+ "bugs": "https://github.com/GregNBlack/kanso-protocol/issues",
20
+ "keywords": [
21
+ "design-system",
22
+ "angular",
23
+ "kanso",
24
+ "slider"
25
+ ],
26
+ "sideEffects": false,
27
+ "module": "fesm2022/kanso-protocol-slider.mjs",
28
+ "typings": "types/kanso-protocol-slider.d.ts",
29
+ "exports": {
30
+ "./package.json": {
31
+ "default": "./package.json"
32
+ },
33
+ ".": {
34
+ "types": "./types/kanso-protocol-slider.d.ts",
35
+ "default": "./fesm2022/kanso-protocol-slider.mjs"
36
+ }
37
+ },
38
+ "type": "module",
39
+ "dependencies": {
40
+ "tslib": "^2.3.0"
41
+ }
42
+ }
@@ -0,0 +1,83 @@
1
+ import * as i0 from '@angular/core';
2
+ import { OnDestroy, EventEmitter, ElementRef } from '@angular/core';
3
+ import { ControlValueAccessor } from '@angular/forms';
4
+
5
+ type KpSliderSize = 'sm' | 'md' | 'lg';
6
+ type KpSliderMode = 'single' | 'range';
7
+ type KpSliderValue = number | readonly [number, number];
8
+ /**
9
+ * Kanso Protocol — Slider
10
+ *
11
+ * Numeric range input with a draggable thumb on a horizontal track.
12
+ * `mode="single"` exposes one thumb; `mode="range"` exposes two.
13
+ *
14
+ * Pointer drag, arrow-key stepping, track-click targeting, and a full
15
+ * ControlValueAccessor for reactive / template-driven forms. In range
16
+ * mode, `ngModel` is a `[number, number]` tuple; in single mode, a plain
17
+ * `number`.
18
+ */
19
+ declare class KpSliderComponent implements ControlValueAccessor, OnDestroy {
20
+ size: KpSliderSize;
21
+ mode: KpSliderMode;
22
+ min: number;
23
+ max: number;
24
+ step: number;
25
+ showTicks: boolean;
26
+ showValueLabel: boolean;
27
+ showLabels: boolean;
28
+ minLabel: string | null;
29
+ maxLabel: string | null;
30
+ ariaLabel: string;
31
+ ariaLabelStart: string;
32
+ ariaLabelEnd: string;
33
+ valueFormatter: ((v: number) => string) | null;
34
+ set disabled(v: boolean);
35
+ get disabled(): boolean;
36
+ private _disabled;
37
+ private cvaDisabled;
38
+ set value(v: KpSliderValue | null | undefined);
39
+ get value(): KpSliderValue;
40
+ readonly valueChange: EventEmitter<KpSliderValue>;
41
+ trackEl?: ElementRef<HTMLElement>;
42
+ /** @internal */ value0: number;
43
+ /** @internal */ value1: number;
44
+ /** @internal */ focusedThumb: 0 | 1;
45
+ private readonly cdr;
46
+ private dragIndex;
47
+ private pointerId;
48
+ private onMoveBound;
49
+ private onUpBound;
50
+ ngOnDestroy(): void;
51
+ get hostClasses(): string;
52
+ get fillLeft(): number;
53
+ get fillWidth(): number;
54
+ /** Five evenly-distributed positions across the track. */
55
+ readonly tickPercents: number[];
56
+ pct(v: number): number;
57
+ formatValue(v: number): string;
58
+ onTrackPointerDown(event: PointerEvent): void;
59
+ onThumbPointerDown(event: PointerEvent, idx: 0 | 1): void;
60
+ private startDrag;
61
+ private onPointerMove;
62
+ private onPointerUp;
63
+ private endDrag;
64
+ onKeyDown(event: KeyboardEvent, idx: 0 | 1): void;
65
+ onBlur(): void;
66
+ private valueFromEvent;
67
+ private snap;
68
+ private nearestThumbIndex;
69
+ private setThumbValue;
70
+ /** Accepts a plain number (single) or a [start, end] tuple (range). */
71
+ private writeInternal;
72
+ private onChange;
73
+ private onTouched;
74
+ writeValue(v: KpSliderValue | null | undefined): void;
75
+ registerOnChange(fn: (v: KpSliderValue) => void): void;
76
+ registerOnTouched(fn: () => void): void;
77
+ setDisabledState(d: boolean): void;
78
+ static ɵfac: i0.ɵɵFactoryDeclaration<KpSliderComponent, never>;
79
+ static ɵcmp: i0.ɵɵComponentDeclaration<KpSliderComponent, "kp-slider", never, { "size": { "alias": "size"; "required": false; }; "mode": { "alias": "mode"; "required": false; }; "min": { "alias": "min"; "required": false; }; "max": { "alias": "max"; "required": false; }; "step": { "alias": "step"; "required": false; }; "showTicks": { "alias": "showTicks"; "required": false; }; "showValueLabel": { "alias": "showValueLabel"; "required": false; }; "showLabels": { "alias": "showLabels"; "required": false; }; "minLabel": { "alias": "minLabel"; "required": false; }; "maxLabel": { "alias": "maxLabel"; "required": false; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; }; "ariaLabelStart": { "alias": "ariaLabelStart"; "required": false; }; "ariaLabelEnd": { "alias": "ariaLabelEnd"; "required": false; }; "valueFormatter": { "alias": "valueFormatter"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "value": { "alias": "value"; "required": false; }; }, { "valueChange": "valueChange"; }, never, never, true, never>;
80
+ }
81
+
82
+ export { KpSliderComponent };
83
+ export type { KpSliderMode, KpSliderSize, KpSliderValue };