@tekus/design-system 5.39.0 → 5.41.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.
- package/fesm2022/tekus-design-system-components-badge.mjs +19 -3
- package/fesm2022/tekus-design-system-components-badge.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-carousel.mjs +27 -6
- package/fesm2022/tekus-design-system-components-carousel.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-counter.mjs +494 -0
- package/fesm2022/tekus-design-system-components-counter.mjs.map +1 -0
- package/fesm2022/tekus-design-system-components-icon.mjs +14 -0
- package/fesm2022/tekus-design-system-components-icon.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-panel.mjs +2 -2
- package/fesm2022/tekus-design-system-components-panel.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-stepper.mjs +2 -2
- package/fesm2022/tekus-design-system-components-stepper.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-table.mjs +94 -3
- package/fesm2022/tekus-design-system-components-table.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-tabs.mjs +1 -1
- package/fesm2022/tekus-design-system-components-tabs.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-toggle.mjs +2 -2
- package/fesm2022/tekus-design-system-components-toggle.mjs.map +1 -1
- package/package.json +5 -1
- package/types/tekus-design-system-components-badge.d.ts +15 -1
- package/types/tekus-design-system-components-carousel.d.ts +21 -2
- package/types/tekus-design-system-components-counter.d.ts +286 -0
- package/types/tekus-design-system-components-icon.d.ts +1 -1
- package/types/tekus-design-system-components-table.d.ts +115 -2
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, DestroyRef, model, input, viewChild, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
|
+
import { NgControl, FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
5
|
+
import { ButtonComponent } from '@tekus/design-system/components/button';
|
|
6
|
+
|
|
7
|
+
/** Delay before a held button starts auto-repeating, in milliseconds. */
|
|
8
|
+
const HOLD_DELAY_MS = 500;
|
|
9
|
+
/** Interval between auto-repeated steps while a button is held, in milliseconds. */
|
|
10
|
+
const HOLD_INTERVAL_MS = 100;
|
|
11
|
+
/** Multiplier applied to `step` for the PageUp / PageDown keys. */
|
|
12
|
+
const PAGE_STEP_MULTIPLIER = 10;
|
|
13
|
+
/** Floor for the value field's width, in characters. */
|
|
14
|
+
const MIN_VALUE_WIDTH_CH = 2;
|
|
15
|
+
/**
|
|
16
|
+
* @component CounterComponent
|
|
17
|
+
* @description
|
|
18
|
+
* Compact control for selecting an integer value within a bounded range using
|
|
19
|
+
* icon-only decrement and increment buttons, with an editable value in the center.
|
|
20
|
+
* It has its own distinct visual identity so it reads as a standalone element rather
|
|
21
|
+
* than blending into the standard input family.
|
|
22
|
+
*
|
|
23
|
+
* This component supports:
|
|
24
|
+
* - `value`: the current integer. Never null; always rendered clamped into `[min, max]`.
|
|
25
|
+
* - `min` / `max`: the inclusive bounds. Buttons **disable** at a limit, never hide,
|
|
26
|
+
* and the value never wraps around.
|
|
27
|
+
* - `step`: the amount added or subtracted per action.
|
|
28
|
+
* - `disabled` / `readonly`: `disabled` blocks interaction and applies a distinct
|
|
29
|
+
* treatment to the value; `readonly` looks normal but cannot change.
|
|
30
|
+
* - Holding a button auto-repeats the step.
|
|
31
|
+
*
|
|
32
|
+
* It implements `ControlValueAccessor`, so it works with `[(ngModel)]` and reactive forms.
|
|
33
|
+
*
|
|
34
|
+
* @usage
|
|
35
|
+
* ### Basic Usage
|
|
36
|
+
* ```html
|
|
37
|
+
* <tk-counter [(ngModel)]="quantity" />
|
|
38
|
+
* <tk-counter [min]="1" [max]="10" [step]="2" [(value)]="servings" />
|
|
39
|
+
* <tk-counter [control]="quantityControl" (valueChange)="onQuantityChange($event)" />
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
class CounterComponent {
|
|
43
|
+
constructor() {
|
|
44
|
+
this.ngControl = inject(NgControl, { self: true, optional: true });
|
|
45
|
+
this.destroyRef = inject(DestroyRef);
|
|
46
|
+
/**
|
|
47
|
+
* @property {ModelSignal<number>} value
|
|
48
|
+
* @description
|
|
49
|
+
* Current integer value. Never null; always rendered clamped into `[min, max]`.
|
|
50
|
+
* Supports two-way binding via `[(value)]` and emits `(valueChange)` after clamping.
|
|
51
|
+
*
|
|
52
|
+
* @default `0`
|
|
53
|
+
*/
|
|
54
|
+
this.value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
55
|
+
/**
|
|
56
|
+
* @property {InputSignal<number>} min
|
|
57
|
+
* @description
|
|
58
|
+
* Lower bound, inclusive. The decrement button disables at this value.
|
|
59
|
+
*
|
|
60
|
+
* @default `0`
|
|
61
|
+
*/
|
|
62
|
+
this.min = input(0, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
63
|
+
/**
|
|
64
|
+
* @property {InputSignal<number>} max
|
|
65
|
+
* @description
|
|
66
|
+
* Upper bound, inclusive. The increment button disables at this value.
|
|
67
|
+
*
|
|
68
|
+
* @default `99`
|
|
69
|
+
*/
|
|
70
|
+
this.max = input(99, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
71
|
+
/**
|
|
72
|
+
* @property {InputSignal<number>} step
|
|
73
|
+
* @description
|
|
74
|
+
* Amount added or subtracted per action. `PageUp` / `PageDown` apply ten times this.
|
|
75
|
+
*
|
|
76
|
+
* @default `1`
|
|
77
|
+
*/
|
|
78
|
+
this.step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
79
|
+
/**
|
|
80
|
+
* @property {ModelSignal<boolean>} disabled
|
|
81
|
+
* @description
|
|
82
|
+
* Disables all interaction and applies the distinct disabled treatment to the value.
|
|
83
|
+
*
|
|
84
|
+
* @default `false`
|
|
85
|
+
*/
|
|
86
|
+
this.disabled = model(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
87
|
+
/**
|
|
88
|
+
* @property {InputSignal<boolean>} readonly
|
|
89
|
+
* @description
|
|
90
|
+
* The value is shown but cannot change. Visually normal, unlike `disabled`.
|
|
91
|
+
*
|
|
92
|
+
* @default `false`
|
|
93
|
+
*/
|
|
94
|
+
this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
95
|
+
/**
|
|
96
|
+
* @property {InputSignal<FormControl | undefined>} control
|
|
97
|
+
* @description
|
|
98
|
+
* External FormControl used to read/set the value. Optional: when omitted the
|
|
99
|
+
* counter keeps its value in the `value` model alone and creates no control.
|
|
100
|
+
* Not needed when using `formControlName` or `[(ngModel)]`.
|
|
101
|
+
*
|
|
102
|
+
* @default `undefined`
|
|
103
|
+
*/
|
|
104
|
+
this.control = input(undefined, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
|
|
105
|
+
/**
|
|
106
|
+
* @property {InputSignal<string>} decreaseLabel
|
|
107
|
+
* @description
|
|
108
|
+
* Accessible label for the decrement button, which carries no visible text.
|
|
109
|
+
*
|
|
110
|
+
* @default `'Decrease'`
|
|
111
|
+
*/
|
|
112
|
+
this.decreaseLabel = input('Decrease', ...(ngDevMode ? [{ debugName: "decreaseLabel" }] : /* istanbul ignore next */ []));
|
|
113
|
+
/**
|
|
114
|
+
* @property {InputSignal<string>} increaseLabel
|
|
115
|
+
* @description
|
|
116
|
+
* Accessible label for the increment button, which carries no visible text.
|
|
117
|
+
*
|
|
118
|
+
* @default `'Increase'`
|
|
119
|
+
*/
|
|
120
|
+
this.increaseLabel = input('Increase', ...(ngDevMode ? [{ debugName: "increaseLabel" }] : /* istanbul ignore next */ []));
|
|
121
|
+
/**
|
|
122
|
+
* @property {InputSignal<string>} ariaLabel
|
|
123
|
+
* @description
|
|
124
|
+
* Accessible name for the value field. The counter renders no visible label, so
|
|
125
|
+
* either this or `ariaLabelledby` must be set unless surrounding text already
|
|
126
|
+
* names the control — otherwise the spinbutton is announced unnamed.
|
|
127
|
+
*/
|
|
128
|
+
this.ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
129
|
+
/**
|
|
130
|
+
* @property {InputSignal<string>} ariaLabelledby
|
|
131
|
+
* @description
|
|
132
|
+
* Id of an existing element that names the value field. Use instead of
|
|
133
|
+
* `ariaLabel` when a visible label already exists elsewhere in the view.
|
|
134
|
+
*/
|
|
135
|
+
this.ariaLabelledby = input('', ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : /* istanbul ignore next */ []));
|
|
136
|
+
/** The value field, used to resync its text after the model clamps a typed entry. */
|
|
137
|
+
this.valueInput = viewChild('valueInput', ...(ngDevMode ? [{ debugName: "valueInput" }] : /* istanbul ignore next */ []));
|
|
138
|
+
/**
|
|
139
|
+
* The effective upper bound. Guards against a `max` lower than `min` so the range
|
|
140
|
+
* is never inverted and `clamp` stays well defined.
|
|
141
|
+
*/
|
|
142
|
+
this.safeMax = computed(() => Math.max(this.min(), this.max()), ...(ngDevMode ? [{ debugName: "safeMax" }] : /* istanbul ignore next */ []));
|
|
143
|
+
/**
|
|
144
|
+
* The effective step. Guards against `0`, a negative, or a non-finite `step`, any
|
|
145
|
+
* of which would make stepping a no-op — and would let a held button repeat forever
|
|
146
|
+
* without ever reaching a limit.
|
|
147
|
+
*/
|
|
148
|
+
this.effectiveStep = computed(() => {
|
|
149
|
+
const step = Math.trunc(Math.abs(this.step()));
|
|
150
|
+
return Number.isFinite(step) && step > 0 ? step : 1;
|
|
151
|
+
}, ...(ngDevMode ? [{ debugName: "effectiveStep" }] : /* istanbul ignore next */ []));
|
|
152
|
+
/** The amount applied by `PageUp` / `PageDown`: ten times `step`. */
|
|
153
|
+
this.pageStep = computed(() => this.effectiveStep() * PAGE_STEP_MULTIPLIER, ...(ngDevMode ? [{ debugName: "pageStep" }] : /* istanbul ignore next */ []));
|
|
154
|
+
/**
|
|
155
|
+
* Character budget for the value field: the widest number the range can produce,
|
|
156
|
+
* never fewer than `MIN_VALUE_WIDTH_CH`.
|
|
157
|
+
*
|
|
158
|
+
* Derived from the **range**, not from the current value, so that every counter
|
|
159
|
+
* sharing a range renders exactly the same width. That is what keeps the buttons
|
|
160
|
+
* aligned down a column when one row shows `1` and the next shows `44` — sizing to
|
|
161
|
+
* the current value instead would make each row a different width.
|
|
162
|
+
*/
|
|
163
|
+
this.valueWidthCh = computed(() => {
|
|
164
|
+
const digits = (bound) => Number.isFinite(bound) ? String(Math.abs(Math.round(bound))).length : 1;
|
|
165
|
+
const widest = Math.max(digits(this.min()), digits(this.safeMax()));
|
|
166
|
+
const signSlot = this.min() < 0 ? 1 : 0;
|
|
167
|
+
return Math.max(MIN_VALUE_WIDTH_CH, widest + signSlot);
|
|
168
|
+
}, ...(ngDevMode ? [{ debugName: "valueWidthCh" }] : /* istanbul ignore next */ []));
|
|
169
|
+
/**
|
|
170
|
+
* The rendered value: always a valid integer inside `[min, safeMax]`, even when a
|
|
171
|
+
* parent binds something out of range. Deliberately a `computed` rather than an
|
|
172
|
+
* `effect` writing back into `value`, which would fight a two-way `[(value)]`
|
|
173
|
+
* binding; the first interaction commits the clamped number and converges the two.
|
|
174
|
+
*/
|
|
175
|
+
this.displayValue = computed(() => this.clamp(this.value()), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
176
|
+
/** Whether the value sits at the lower bound, which disables the decrement button. */
|
|
177
|
+
this.atMin = computed(() => this.displayValue() <= this.min(), ...(ngDevMode ? [{ debugName: "atMin" }] : /* istanbul ignore next */ []));
|
|
178
|
+
/** Whether the value sits at the upper bound, which disables the increment button. */
|
|
179
|
+
this.atMax = computed(() => this.displayValue() >= this.safeMax(), ...(ngDevMode ? [{ debugName: "atMax" }] : /* istanbul ignore next */ []));
|
|
180
|
+
/** Computed host class: the BEM block plus the disabled and readonly modifiers. */
|
|
181
|
+
this.hostClass = computed(() => {
|
|
182
|
+
const classes = ['tk-counter'];
|
|
183
|
+
if (this.disabled()) {
|
|
184
|
+
classes.push('tk-counter--disabled');
|
|
185
|
+
}
|
|
186
|
+
if (this.readonly()) {
|
|
187
|
+
classes.push('tk-counter--readonly');
|
|
188
|
+
}
|
|
189
|
+
return classes.join(' ');
|
|
190
|
+
}, ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
|
|
191
|
+
this.onChange = () => { };
|
|
192
|
+
this.onTouched = () => { };
|
|
193
|
+
this.isWriting = false;
|
|
194
|
+
this.holdTimeout = null;
|
|
195
|
+
this.holdInterval = null;
|
|
196
|
+
if (this.ngControl) {
|
|
197
|
+
this.ngControl.valueAccessor = this;
|
|
198
|
+
}
|
|
199
|
+
this.destroyRef.onDestroy(() => this.stopHold());
|
|
200
|
+
}
|
|
201
|
+
get effectiveControl() {
|
|
202
|
+
const injected = this.ngControl?.control;
|
|
203
|
+
return injected instanceof FormControl ? injected : (this.control() ?? null);
|
|
204
|
+
}
|
|
205
|
+
ngOnInit() {
|
|
206
|
+
const control = this.effectiveControl;
|
|
207
|
+
if (control?.value !== null && control?.value !== undefined) {
|
|
208
|
+
this.value.set(this.clamp(control.value));
|
|
209
|
+
}
|
|
210
|
+
// A `[disabled]="true"` input must win over an externally supplied control's
|
|
211
|
+
// enabled state, otherwise binding it at creation time would be silently reset
|
|
212
|
+
// here. Only that control is treated as the authority — a control reached through
|
|
213
|
+
// `ngControl` is driven by the forms API via `setDisabledState`, not from here.
|
|
214
|
+
if (control && this.control() === control && this.disabled()) {
|
|
215
|
+
control.disable({ emitEvent: false });
|
|
216
|
+
}
|
|
217
|
+
else if (control) {
|
|
218
|
+
this.disabled.set(control.disabled);
|
|
219
|
+
}
|
|
220
|
+
control?.valueChanges
|
|
221
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
222
|
+
.subscribe(val => {
|
|
223
|
+
if (this.isWriting)
|
|
224
|
+
return;
|
|
225
|
+
const next = this.clamp(val);
|
|
226
|
+
if (next !== this.value()) {
|
|
227
|
+
this.value.set(next);
|
|
228
|
+
this.onChange(next);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
control?.statusChanges
|
|
232
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
233
|
+
.subscribe(() => {
|
|
234
|
+
this.disabled.set(control?.disabled ?? false);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* @method writeValue
|
|
239
|
+
* @description
|
|
240
|
+
* Writes a new value to the element, clamped into range.
|
|
241
|
+
* @param value The new value.
|
|
242
|
+
*/
|
|
243
|
+
writeValue(value) {
|
|
244
|
+
this.value.set(this.clamp(value ?? this.min()));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* @method registerOnChange
|
|
248
|
+
* @description
|
|
249
|
+
* Registers a callback called when the value changes in the UI.
|
|
250
|
+
* @param fn The callback function.
|
|
251
|
+
*/
|
|
252
|
+
registerOnChange(fn) {
|
|
253
|
+
this.onChange = fn;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* @method registerOnTouched
|
|
257
|
+
* @description
|
|
258
|
+
* Registers a callback called by the forms API to update the form model on blur.
|
|
259
|
+
* @param fn The callback function.
|
|
260
|
+
*/
|
|
261
|
+
registerOnTouched(fn) {
|
|
262
|
+
this.onTouched = fn;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* @method setDisabledState
|
|
266
|
+
* @description
|
|
267
|
+
* Called by the forms API when the control status changes to or from 'DISABLED'.
|
|
268
|
+
* @param isDisabled The disabled status to set on the element.
|
|
269
|
+
*/
|
|
270
|
+
setDisabledState(isDisabled) {
|
|
271
|
+
this.disabled.set(isDisabled);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* @method stepBy
|
|
275
|
+
* @description
|
|
276
|
+
* Moves the value by `step` in the given direction, clamped into range.
|
|
277
|
+
* @param direction `1` to increment, `-1` to decrement.
|
|
278
|
+
*/
|
|
279
|
+
stepBy(direction) {
|
|
280
|
+
this.commit(this.displayValue() + direction * this.effectiveStep());
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* @method startHold
|
|
284
|
+
* @description
|
|
285
|
+
* Steps once immediately, then begins auto-repeating after `HOLD_DELAY_MS`.
|
|
286
|
+
* No-ops when the counter cannot change or is already at that limit.
|
|
287
|
+
* @param direction `1` to increment, `-1` to decrement.
|
|
288
|
+
*/
|
|
289
|
+
startHold(direction) {
|
|
290
|
+
if (!this.canInteract() || this.isAtLimit(direction))
|
|
291
|
+
return;
|
|
292
|
+
this.stopHold();
|
|
293
|
+
this.stepBy(direction);
|
|
294
|
+
// That single step may already have reached the bound. Arming the repeat here
|
|
295
|
+
// would leave a timer running against a button the template has just disabled,
|
|
296
|
+
// and a disabled button never delivers the `pointerup` that would clear it.
|
|
297
|
+
if (this.isAtLimit(direction))
|
|
298
|
+
return;
|
|
299
|
+
this.holdTimeout = setTimeout(() => {
|
|
300
|
+
this.holdInterval = setInterval(() => {
|
|
301
|
+
if (!this.canInteract() || this.isAtLimit(direction)) {
|
|
302
|
+
this.stopHold();
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
this.stepBy(direction);
|
|
306
|
+
}, HOLD_INTERVAL_MS);
|
|
307
|
+
}, HOLD_DELAY_MS);
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* @method stopHold
|
|
311
|
+
* @description
|
|
312
|
+
* Cancels any pending or running auto-repeat. Safe to call when none is active.
|
|
313
|
+
*/
|
|
314
|
+
stopHold() {
|
|
315
|
+
if (this.holdTimeout !== null) {
|
|
316
|
+
clearTimeout(this.holdTimeout);
|
|
317
|
+
this.holdTimeout = null;
|
|
318
|
+
}
|
|
319
|
+
if (this.holdInterval !== null) {
|
|
320
|
+
clearInterval(this.holdInterval);
|
|
321
|
+
this.holdInterval = null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* @method onButtonKeydown
|
|
326
|
+
* @description
|
|
327
|
+
* Handles keyboard activation of a stepper button. `Enter` and `Space` are handled
|
|
328
|
+
* here — and their default prevented — so the button steps exactly once per press
|
|
329
|
+
* and relies on native key auto-repeat while held.
|
|
330
|
+
* @param event The keyboard event.
|
|
331
|
+
* @param direction `1` to increment, `-1` to decrement.
|
|
332
|
+
*/
|
|
333
|
+
onButtonKeydown(event, direction) {
|
|
334
|
+
if (event.key !== 'Enter' && event.key !== ' ')
|
|
335
|
+
return;
|
|
336
|
+
event.preventDefault();
|
|
337
|
+
if (!this.canInteract() || this.isAtLimit(direction))
|
|
338
|
+
return;
|
|
339
|
+
this.stepBy(direction);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* @method onValueKeydown
|
|
343
|
+
* @description
|
|
344
|
+
* Handles the spinbutton keyboard contract: arrows step by `step`, Home / End jump
|
|
345
|
+
* to the bounds, PageUp / PageDown apply a larger step, and Enter commits the
|
|
346
|
+
* currently typed text.
|
|
347
|
+
* @param event The keyboard event.
|
|
348
|
+
*/
|
|
349
|
+
onValueKeydown(event) {
|
|
350
|
+
const current = this.displayValue();
|
|
351
|
+
let next = null;
|
|
352
|
+
switch (event.key) {
|
|
353
|
+
case 'ArrowUp':
|
|
354
|
+
next = current + this.effectiveStep();
|
|
355
|
+
break;
|
|
356
|
+
case 'ArrowDown':
|
|
357
|
+
next = current - this.effectiveStep();
|
|
358
|
+
break;
|
|
359
|
+
case 'PageUp':
|
|
360
|
+
next = current + this.pageStep();
|
|
361
|
+
break;
|
|
362
|
+
case 'PageDown':
|
|
363
|
+
next = current - this.pageStep();
|
|
364
|
+
break;
|
|
365
|
+
case 'Home':
|
|
366
|
+
next = this.min();
|
|
367
|
+
break;
|
|
368
|
+
case 'End':
|
|
369
|
+
next = this.safeMax();
|
|
370
|
+
break;
|
|
371
|
+
case 'Enter':
|
|
372
|
+
event.preventDefault();
|
|
373
|
+
this.commitTypedValue();
|
|
374
|
+
return;
|
|
375
|
+
default:
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
event.preventDefault();
|
|
379
|
+
this.commit(next);
|
|
380
|
+
this.syncInputText();
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* @method onInput
|
|
384
|
+
* @description
|
|
385
|
+
* Rejects non-numeric characters as they are typed, without clamping — clamping
|
|
386
|
+
* mid-keystroke would fight the user on the way to a valid number (typing `1`
|
|
387
|
+
* toward `12` when `min` is `5`). The value is committed on blur or Enter.
|
|
388
|
+
* @param event The input event.
|
|
389
|
+
*/
|
|
390
|
+
onInput(event) {
|
|
391
|
+
const target = event.target;
|
|
392
|
+
const original = target.value;
|
|
393
|
+
const sanitized = this.sanitize(original);
|
|
394
|
+
if (sanitized === original)
|
|
395
|
+
return;
|
|
396
|
+
// Rewriting `value` sends the caret to the end, which would scramble editing in
|
|
397
|
+
// the middle of the number. Pull it back by however many characters were dropped.
|
|
398
|
+
const caret = target.selectionStart ?? original.length;
|
|
399
|
+
const removed = original.length - sanitized.length;
|
|
400
|
+
const nextCaret = Math.max(0, caret - removed);
|
|
401
|
+
target.value = sanitized;
|
|
402
|
+
target.setSelectionRange(nextCaret, nextCaret);
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* @method onValueBlur
|
|
406
|
+
* @description
|
|
407
|
+
* Commits the typed text, marks the control touched, and resyncs the field so its
|
|
408
|
+
* text always matches the clamped value.
|
|
409
|
+
*/
|
|
410
|
+
onValueBlur() {
|
|
411
|
+
this.commitTypedValue();
|
|
412
|
+
this.onTouched();
|
|
413
|
+
this.effectiveControl?.markAsTouched();
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Keeps digits only, plus a single leading `-` when the range includes negatives.
|
|
417
|
+
* Applied to whatever lands in the field, so it also covers paste and drop.
|
|
418
|
+
*/
|
|
419
|
+
sanitize(text) {
|
|
420
|
+
const negative = this.min() < 0 && text.startsWith('-');
|
|
421
|
+
return (negative ? '-' : '') + text.replaceAll(/\D/g, '');
|
|
422
|
+
}
|
|
423
|
+
/** Whether the counter currently accepts changes. */
|
|
424
|
+
canInteract() {
|
|
425
|
+
return !this.disabled() && !this.readonly();
|
|
426
|
+
}
|
|
427
|
+
/** Whether stepping in `direction` would move past an already-reached bound. */
|
|
428
|
+
isAtLimit(direction) {
|
|
429
|
+
return direction === 1 ? this.atMax() : this.atMin();
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Rounds and clamps an arbitrary value into `[min, safeMax]`. Non-finite input
|
|
433
|
+
* (an empty or unparseable field) falls back to `min`, so the value is never empty.
|
|
434
|
+
*/
|
|
435
|
+
clamp(value) {
|
|
436
|
+
const parsed = Number(value);
|
|
437
|
+
if (value === null || value === undefined || !Number.isFinite(parsed)) {
|
|
438
|
+
return this.min();
|
|
439
|
+
}
|
|
440
|
+
return Math.min(Math.max(Math.round(parsed), this.min()), this.safeMax());
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* The single write path: clamps, updates the model, notifies the forms API, and
|
|
444
|
+
* emits `(valueChange)` — so clamping and emission happen in exactly one place.
|
|
445
|
+
*/
|
|
446
|
+
commit(next) {
|
|
447
|
+
if (!this.canInteract())
|
|
448
|
+
return;
|
|
449
|
+
const clamped = this.clamp(next);
|
|
450
|
+
this.isWriting = true;
|
|
451
|
+
this.value.set(clamped);
|
|
452
|
+
this.onChange(clamped);
|
|
453
|
+
this.effectiveControl?.setValue(clamped, { emitEvent: false });
|
|
454
|
+
this.effectiveControl?.markAsDirty();
|
|
455
|
+
this.isWriting = false;
|
|
456
|
+
}
|
|
457
|
+
/** Parses the field's current text, commits it, and resyncs the displayed text. */
|
|
458
|
+
commitTypedValue() {
|
|
459
|
+
const raw = this.valueInput()?.nativeElement.value ?? '';
|
|
460
|
+
const parsed = raw === '' || raw === '-' ? this.displayValue() : Number(raw);
|
|
461
|
+
this.commit(parsed);
|
|
462
|
+
this.syncInputText();
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Rewrites the field's text from `displayValue`. Required because the `[value]`
|
|
466
|
+
* binding will not update the DOM when the model is unchanged — typing `150` with
|
|
467
|
+
* `max` `99` while the value is already `99` leaves stale text behind otherwise.
|
|
468
|
+
*/
|
|
469
|
+
syncInputText() {
|
|
470
|
+
const input = this.valueInput()?.nativeElement;
|
|
471
|
+
if (!input)
|
|
472
|
+
return;
|
|
473
|
+
const text = String(this.displayValue());
|
|
474
|
+
if (input.value !== text) {
|
|
475
|
+
input.value = text;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CounterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
479
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.17", type: CounterComponent, isStandalone: true, selector: "tk-counter", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, 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 }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, decreaseLabel: { classPropertyName: "decreaseLabel", publicName: "decreaseLabel", isSignal: true, isRequired: false, transformFunction: null }, increaseLabel: { classPropertyName: "increaseLabel", publicName: "increaseLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange" }, host: { attributes: { "role": "group" }, properties: { "class": "hostClass()" } }, viewQueries: [{ propertyName: "valueInput", first: true, predicate: ["valueInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<tk-button\n class=\"tk-counter__button\"\n severity=\"secondary\"\n icon=\"minus\"\n [ariaLabel]=\"decreaseLabel()\"\n [disabled]=\"disabled() || readonly() || atMin()\"\n (pointerdown)=\"startHold(-1)\"\n (pointerup)=\"stopHold()\"\n (pointerleave)=\"stopHold()\"\n (pointercancel)=\"stopHold()\"\n (blur)=\"stopHold()\"\n (keydown)=\"onButtonKeydown($event, -1)\" />\n\n<input\n #valueInput\n class=\"tk-counter__value\"\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n role=\"spinbutton\"\n [style.--tk-counter-value-ch]=\"valueWidthCh()\"\n [value]=\"displayValue()\"\n [attr.aria-valuenow]=\"displayValue()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"safeMax()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-readonly]=\"readonly() || null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onValueKeydown($event)\"\n (blur)=\"onValueBlur()\" />\n\n<tk-button\n class=\"tk-counter__button\"\n severity=\"secondary\"\n icon=\"plus\"\n [ariaLabel]=\"increaseLabel()\"\n [disabled]=\"disabled() || readonly() || atMax()\"\n (pointerdown)=\"startHold(1)\"\n (pointerup)=\"stopHold()\"\n (pointerleave)=\"stopHold()\"\n (pointercancel)=\"stopHold()\"\n (blur)=\"stopHold()\"\n (keydown)=\"onButtonKeydown($event, 1)\" />\n", styles: [":host{display:inline-flex;box-sizing:border-box;align-items:center;gap:var(--tk-spacing-gap-s, .5rem);font-family:var(--tk-font-family, Poppins, sans-serif)}.tk-counter__button{flex-shrink:0}.tk-counter__value{min-width:var(--tk-size-base-250, 2.5rem);width:calc(var(--tk-counter-value-ch, 2) * 1ch);flex-shrink:0;padding:var(--tk-spacing-padding-none, 0);border:none;background-color:var(--tk-color-transparent, transparent);font-family:inherit;font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-l, 1.125rem);line-height:1;color:var(--tk-color-text-default, #191a1b);text-align:center}.tk-counter__value:focus{outline:none}.tk-counter__value:focus-visible{border-radius:var(--tk-borderRadius-theme-xs, var(--tk-borderRadius-xs, .25rem));outline:.125rem solid var(--tk-color-border-focus, #16006f);outline-offset:.125rem}.tk-counter__value:disabled{color:var(--tk-color-text-muted, #8a8a8b);opacity:1}:host ::ng-deep .tk-counter__button .p-button{display:flex;align-items:center;justify-content:center;width:var(--tk-size-base-250, 2.5rem);min-width:var(--tk-size-base-250, 2.5rem);height:var(--tk-size-base-250, 2.5rem);padding:var(--tk-spacing-padding-none, 0);border:none;border-radius:var(--tk-borderRadius-theme-s, var(--tk-borderRadius-s, .5rem));background-color:var(--tk-color-background-soft, #f2f1f1);color:var(--tk-color-text-default, #191a1b);-webkit-user-select:none;user-select:none;touch-action:manipulation;-webkit-touch-callout:none}:host ::ng-deep .tk-counter__button .p-button:not(:disabled):hover{background-color:var(--tk-color-base-surface-200, #e4e4e4);color:var(--tk-color-text-default, #191a1b)}:host ::ng-deep .tk-counter__button .p-button:not(:disabled):active{background-color:var(--tk-color-base-surface-300, #d2d2d2)}:host ::ng-deep .tk-counter__button .p-button:focus-visible{outline:.125rem solid var(--tk-color-border-focus, #16006f);outline-offset:.125rem}:host ::ng-deep .tk-counter__button .p-button:disabled{background-color:var(--tk-color-base-surface-100, #f2f1f1);color:var(--tk-color-text-muted, #8a8a8b);opacity:1}:host ::ng-deep .tk-counter__button .p-button tk-icon svg{width:var(--tk-size-base-100, 1rem);height:var(--tk-size-base-100, 1rem)}:host(.tk-counter--readonly) ::ng-deep .tk-counter__button .p-button:disabled{background-color:var(--tk-color-background-soft, #f2f1f1);color:var(--tk-color-text-default, #191a1b);cursor:default}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: ButtonComponent, selector: "tk-button", inputs: ["label", "disabled", "type", "severity", "variant", "link", "icon", "iconPosition", "tooltipText", "full", "ariaLabel", "size"], outputs: ["clicked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
480
|
+
}
|
|
481
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: CounterComponent, decorators: [{
|
|
482
|
+
type: Component,
|
|
483
|
+
args: [{ selector: 'tk-counter', imports: [ReactiveFormsModule, FormsModule, ButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
484
|
+
'[class]': 'hostClass()',
|
|
485
|
+
role: 'group',
|
|
486
|
+
}, template: "<tk-button\n class=\"tk-counter__button\"\n severity=\"secondary\"\n icon=\"minus\"\n [ariaLabel]=\"decreaseLabel()\"\n [disabled]=\"disabled() || readonly() || atMin()\"\n (pointerdown)=\"startHold(-1)\"\n (pointerup)=\"stopHold()\"\n (pointerleave)=\"stopHold()\"\n (pointercancel)=\"stopHold()\"\n (blur)=\"stopHold()\"\n (keydown)=\"onButtonKeydown($event, -1)\" />\n\n<input\n #valueInput\n class=\"tk-counter__value\"\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n role=\"spinbutton\"\n [style.--tk-counter-value-ch]=\"valueWidthCh()\"\n [value]=\"displayValue()\"\n [attr.aria-valuenow]=\"displayValue()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"safeMax()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-readonly]=\"readonly() || null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onValueKeydown($event)\"\n (blur)=\"onValueBlur()\" />\n\n<tk-button\n class=\"tk-counter__button\"\n severity=\"secondary\"\n icon=\"plus\"\n [ariaLabel]=\"increaseLabel()\"\n [disabled]=\"disabled() || readonly() || atMax()\"\n (pointerdown)=\"startHold(1)\"\n (pointerup)=\"stopHold()\"\n (pointerleave)=\"stopHold()\"\n (pointercancel)=\"stopHold()\"\n (blur)=\"stopHold()\"\n (keydown)=\"onButtonKeydown($event, 1)\" />\n", styles: [":host{display:inline-flex;box-sizing:border-box;align-items:center;gap:var(--tk-spacing-gap-s, .5rem);font-family:var(--tk-font-family, Poppins, sans-serif)}.tk-counter__button{flex-shrink:0}.tk-counter__value{min-width:var(--tk-size-base-250, 2.5rem);width:calc(var(--tk-counter-value-ch, 2) * 1ch);flex-shrink:0;padding:var(--tk-spacing-padding-none, 0);border:none;background-color:var(--tk-color-transparent, transparent);font-family:inherit;font-weight:var(--tk-font-weight-400, 400);font-size:var(--tk-font-size-paragraph-l, 1.125rem);line-height:1;color:var(--tk-color-text-default, #191a1b);text-align:center}.tk-counter__value:focus{outline:none}.tk-counter__value:focus-visible{border-radius:var(--tk-borderRadius-theme-xs, var(--tk-borderRadius-xs, .25rem));outline:.125rem solid var(--tk-color-border-focus, #16006f);outline-offset:.125rem}.tk-counter__value:disabled{color:var(--tk-color-text-muted, #8a8a8b);opacity:1}:host ::ng-deep .tk-counter__button .p-button{display:flex;align-items:center;justify-content:center;width:var(--tk-size-base-250, 2.5rem);min-width:var(--tk-size-base-250, 2.5rem);height:var(--tk-size-base-250, 2.5rem);padding:var(--tk-spacing-padding-none, 0);border:none;border-radius:var(--tk-borderRadius-theme-s, var(--tk-borderRadius-s, .5rem));background-color:var(--tk-color-background-soft, #f2f1f1);color:var(--tk-color-text-default, #191a1b);-webkit-user-select:none;user-select:none;touch-action:manipulation;-webkit-touch-callout:none}:host ::ng-deep .tk-counter__button .p-button:not(:disabled):hover{background-color:var(--tk-color-base-surface-200, #e4e4e4);color:var(--tk-color-text-default, #191a1b)}:host ::ng-deep .tk-counter__button .p-button:not(:disabled):active{background-color:var(--tk-color-base-surface-300, #d2d2d2)}:host ::ng-deep .tk-counter__button .p-button:focus-visible{outline:.125rem solid var(--tk-color-border-focus, #16006f);outline-offset:.125rem}:host ::ng-deep .tk-counter__button .p-button:disabled{background-color:var(--tk-color-base-surface-100, #f2f1f1);color:var(--tk-color-text-muted, #8a8a8b);opacity:1}:host ::ng-deep .tk-counter__button .p-button tk-icon svg{width:var(--tk-size-base-100, 1rem);height:var(--tk-size-base-100, 1rem)}:host(.tk-counter--readonly) ::ng-deep .tk-counter__button .p-button:disabled{background-color:var(--tk-color-background-soft, #f2f1f1);color:var(--tk-color-text-default, #191a1b);cursor:default}\n"] }]
|
|
487
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], 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 }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], decreaseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "decreaseLabel", required: false }] }], increaseLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "increaseLabel", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], valueInput: [{ type: i0.ViewChild, args: ['valueInput', { isSignal: true }] }] } });
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Generated bundle index. Do not edit.
|
|
491
|
+
*/
|
|
492
|
+
|
|
493
|
+
export { CounterComponent };
|
|
494
|
+
//# sourceMappingURL=tekus-design-system-components-counter.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tekus-design-system-components-counter.mjs","sources":["../../../projects/design-system/components/counter/src/counter.component.ts","../../../projects/design-system/components/counter/src/counter.component.html","../../../projects/design-system/components/counter/tekus-design-system-components-counter.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n DestroyRef,\n ElementRef,\n OnInit,\n computed,\n inject,\n input,\n model,\n viewChild,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n ControlValueAccessor,\n FormControl,\n FormsModule,\n NgControl,\n ReactiveFormsModule,\n} from '@angular/forms';\nimport { ButtonComponent } from '@tekus/design-system/components/button';\n\n/** Delay before a held button starts auto-repeating, in milliseconds. */\nconst HOLD_DELAY_MS = 500;\n\n/** Interval between auto-repeated steps while a button is held, in milliseconds. */\nconst HOLD_INTERVAL_MS = 100;\n\n/** Multiplier applied to `step` for the PageUp / PageDown keys. */\nconst PAGE_STEP_MULTIPLIER = 10;\n\n/** Floor for the value field's width, in characters. */\nconst MIN_VALUE_WIDTH_CH = 2;\n\n/**\n * @component CounterComponent\n * @description\n * Compact control for selecting an integer value within a bounded range using\n * icon-only decrement and increment buttons, with an editable value in the center.\n * It has its own distinct visual identity so it reads as a standalone element rather\n * than blending into the standard input family.\n *\n * This component supports:\n * - `value`: the current integer. Never null; always rendered clamped into `[min, max]`.\n * - `min` / `max`: the inclusive bounds. Buttons **disable** at a limit, never hide,\n * and the value never wraps around.\n * - `step`: the amount added or subtracted per action.\n * - `disabled` / `readonly`: `disabled` blocks interaction and applies a distinct\n * treatment to the value; `readonly` looks normal but cannot change.\n * - Holding a button auto-repeats the step.\n *\n * It implements `ControlValueAccessor`, so it works with `[(ngModel)]` and reactive forms.\n *\n * @usage\n * ### Basic Usage\n * ```html\n * <tk-counter [(ngModel)]=\"quantity\" />\n * <tk-counter [min]=\"1\" [max]=\"10\" [step]=\"2\" [(value)]=\"servings\" />\n * <tk-counter [control]=\"quantityControl\" (valueChange)=\"onQuantityChange($event)\" />\n * ```\n */\n@Component({\n selector: 'tk-counter',\n imports: [ReactiveFormsModule, FormsModule, ButtonComponent],\n templateUrl: './counter.component.html',\n styleUrl: './counter.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClass()',\n role: 'group',\n },\n})\nexport class CounterComponent implements ControlValueAccessor, OnInit {\n readonly ngControl = inject(NgControl, { self: true, optional: true });\n private readonly destroyRef = inject(DestroyRef);\n\n constructor() {\n if (this.ngControl) {\n this.ngControl.valueAccessor = this;\n }\n\n this.destroyRef.onDestroy(() => this.stopHold());\n }\n\n /**\n * @property {ModelSignal<number>} value\n * @description\n * Current integer value. Never null; always rendered clamped into `[min, max]`.\n * Supports two-way binding via `[(value)]` and emits `(valueChange)` after clamping.\n *\n * @default `0`\n */\n value = model<number>(0);\n\n /**\n * @property {InputSignal<number>} min\n * @description\n * Lower bound, inclusive. The decrement button disables at this value.\n *\n * @default `0`\n */\n min = input<number>(0);\n\n /**\n * @property {InputSignal<number>} max\n * @description\n * Upper bound, inclusive. The increment button disables at this value.\n *\n * @default `99`\n */\n max = input<number>(99);\n\n /**\n * @property {InputSignal<number>} step\n * @description\n * Amount added or subtracted per action. `PageUp` / `PageDown` apply ten times this.\n *\n * @default `1`\n */\n step = input<number>(1);\n\n /**\n * @property {ModelSignal<boolean>} disabled\n * @description\n * Disables all interaction and applies the distinct disabled treatment to the value.\n *\n * @default `false`\n */\n disabled = model<boolean>(false);\n\n /**\n * @property {InputSignal<boolean>} readonly\n * @description\n * The value is shown but cannot change. Visually normal, unlike `disabled`.\n *\n * @default `false`\n */\n readonly = input<boolean>(false);\n\n /**\n * @property {InputSignal<FormControl | undefined>} control\n * @description\n * External FormControl used to read/set the value. Optional: when omitted the\n * counter keeps its value in the `value` model alone and creates no control.\n * Not needed when using `formControlName` or `[(ngModel)]`.\n *\n * @default `undefined`\n */\n control = input<FormControl | undefined>(undefined);\n\n /**\n * @property {InputSignal<string>} decreaseLabel\n * @description\n * Accessible label for the decrement button, which carries no visible text.\n *\n * @default `'Decrease'`\n */\n decreaseLabel = input<string>('Decrease');\n\n /**\n * @property {InputSignal<string>} increaseLabel\n * @description\n * Accessible label for the increment button, which carries no visible text.\n *\n * @default `'Increase'`\n */\n increaseLabel = input<string>('Increase');\n\n /**\n * @property {InputSignal<string>} ariaLabel\n * @description\n * Accessible name for the value field. The counter renders no visible label, so\n * either this or `ariaLabelledby` must be set unless surrounding text already\n * names the control — otherwise the spinbutton is announced unnamed.\n */\n ariaLabel = input<string>('');\n\n /**\n * @property {InputSignal<string>} ariaLabelledby\n * @description\n * Id of an existing element that names the value field. Use instead of\n * `ariaLabel` when a visible label already exists elsewhere in the view.\n */\n ariaLabelledby = input<string>('');\n\n /** The value field, used to resync its text after the model clamps a typed entry. */\n private readonly valueInput =\n viewChild<ElementRef<HTMLInputElement>>('valueInput');\n\n /**\n * The effective upper bound. Guards against a `max` lower than `min` so the range\n * is never inverted and `clamp` stays well defined.\n */\n readonly safeMax = computed(() => Math.max(this.min(), this.max()));\n\n /**\n * The effective step. Guards against `0`, a negative, or a non-finite `step`, any\n * of which would make stepping a no-op — and would let a held button repeat forever\n * without ever reaching a limit.\n */\n readonly effectiveStep = computed(() => {\n const step = Math.trunc(Math.abs(this.step()));\n return Number.isFinite(step) && step > 0 ? step : 1;\n });\n\n /** The amount applied by `PageUp` / `PageDown`: ten times `step`. */\n readonly pageStep = computed(() => this.effectiveStep() * PAGE_STEP_MULTIPLIER);\n\n /**\n * Character budget for the value field: the widest number the range can produce,\n * never fewer than `MIN_VALUE_WIDTH_CH`.\n *\n * Derived from the **range**, not from the current value, so that every counter\n * sharing a range renders exactly the same width. That is what keeps the buttons\n * aligned down a column when one row shows `1` and the next shows `44` — sizing to\n * the current value instead would make each row a different width.\n */\n readonly valueWidthCh = computed(() => {\n const digits = (bound: number): number =>\n Number.isFinite(bound) ? String(Math.abs(Math.round(bound))).length : 1;\n const widest = Math.max(digits(this.min()), digits(this.safeMax()));\n const signSlot = this.min() < 0 ? 1 : 0;\n return Math.max(MIN_VALUE_WIDTH_CH, widest + signSlot);\n });\n\n /**\n * The rendered value: always a valid integer inside `[min, safeMax]`, even when a\n * parent binds something out of range. Deliberately a `computed` rather than an\n * `effect` writing back into `value`, which would fight a two-way `[(value)]`\n * binding; the first interaction commits the clamped number and converges the two.\n */\n readonly displayValue = computed(() => this.clamp(this.value()));\n\n /** Whether the value sits at the lower bound, which disables the decrement button. */\n readonly atMin = computed(() => this.displayValue() <= this.min());\n\n /** Whether the value sits at the upper bound, which disables the increment button. */\n readonly atMax = computed(() => this.displayValue() >= this.safeMax());\n\n /** Computed host class: the BEM block plus the disabled and readonly modifiers. */\n readonly hostClass = computed(() => {\n const classes = ['tk-counter'];\n if (this.disabled()) {\n classes.push('tk-counter--disabled');\n }\n if (this.readonly()) {\n classes.push('tk-counter--readonly');\n }\n return classes.join(' ');\n });\n\n get effectiveControl(): FormControl | null {\n const injected = this.ngControl?.control;\n return injected instanceof FormControl ? injected : (this.control() ?? null);\n }\n\n onChange: (value: number) => void = () => {};\n onTouched: () => void = () => {};\n\n private isWriting = false;\n private holdTimeout: ReturnType<typeof setTimeout> | null = null;\n private holdInterval: ReturnType<typeof setInterval> | null = null;\n\n ngOnInit(): void {\n const control = this.effectiveControl;\n\n if (control?.value !== null && control?.value !== undefined) {\n this.value.set(this.clamp(control.value));\n }\n\n // A `[disabled]=\"true\"` input must win over an externally supplied control's\n // enabled state, otherwise binding it at creation time would be silently reset\n // here. Only that control is treated as the authority — a control reached through\n // `ngControl` is driven by the forms API via `setDisabledState`, not from here.\n if (control && this.control() === control && this.disabled()) {\n control.disable({ emitEvent: false });\n } else if (control) {\n this.disabled.set(control.disabled);\n }\n\n control?.valueChanges\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(val => {\n if (this.isWriting) return;\n const next = this.clamp(val);\n if (next !== this.value()) {\n this.value.set(next);\n this.onChange(next);\n }\n });\n\n control?.statusChanges\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => {\n this.disabled.set(control?.disabled ?? false);\n });\n }\n\n /**\n * @method writeValue\n * @description\n * Writes a new value to the element, clamped into range.\n * @param value The new value.\n */\n writeValue(value: number | null): void {\n this.value.set(this.clamp(value ?? this.min()));\n }\n\n /**\n * @method registerOnChange\n * @description\n * Registers a callback called when the value changes in the UI.\n * @param fn The callback function.\n */\n registerOnChange(fn: (value: number) => void): void {\n this.onChange = fn;\n }\n\n /**\n * @method registerOnTouched\n * @description\n * Registers a callback called by the forms API to update the form model on blur.\n * @param fn The callback function.\n */\n registerOnTouched(fn: () => void): void {\n this.onTouched = fn;\n }\n\n /**\n * @method setDisabledState\n * @description\n * Called by the forms API when the control status changes to or from 'DISABLED'.\n * @param isDisabled The disabled status to set on the element.\n */\n setDisabledState(isDisabled: boolean): void {\n this.disabled.set(isDisabled);\n }\n\n /**\n * @method stepBy\n * @description\n * Moves the value by `step` in the given direction, clamped into range.\n * @param direction `1` to increment, `-1` to decrement.\n */\n stepBy(direction: 1 | -1): void {\n this.commit(this.displayValue() + direction * this.effectiveStep());\n }\n\n /**\n * @method startHold\n * @description\n * Steps once immediately, then begins auto-repeating after `HOLD_DELAY_MS`.\n * No-ops when the counter cannot change or is already at that limit.\n * @param direction `1` to increment, `-1` to decrement.\n */\n startHold(direction: 1 | -1): void {\n if (!this.canInteract() || this.isAtLimit(direction)) return;\n\n this.stopHold();\n this.stepBy(direction);\n\n // That single step may already have reached the bound. Arming the repeat here\n // would leave a timer running against a button the template has just disabled,\n // and a disabled button never delivers the `pointerup` that would clear it.\n if (this.isAtLimit(direction)) return;\n\n this.holdTimeout = setTimeout(() => {\n this.holdInterval = setInterval(() => {\n if (!this.canInteract() || this.isAtLimit(direction)) {\n this.stopHold();\n return;\n }\n this.stepBy(direction);\n }, HOLD_INTERVAL_MS);\n }, HOLD_DELAY_MS);\n }\n\n /**\n * @method stopHold\n * @description\n * Cancels any pending or running auto-repeat. Safe to call when none is active.\n */\n stopHold(): void {\n if (this.holdTimeout !== null) {\n clearTimeout(this.holdTimeout);\n this.holdTimeout = null;\n }\n if (this.holdInterval !== null) {\n clearInterval(this.holdInterval);\n this.holdInterval = null;\n }\n }\n\n /**\n * @method onButtonKeydown\n * @description\n * Handles keyboard activation of a stepper button. `Enter` and `Space` are handled\n * here — and their default prevented — so the button steps exactly once per press\n * and relies on native key auto-repeat while held.\n * @param event The keyboard event.\n * @param direction `1` to increment, `-1` to decrement.\n */\n onButtonKeydown(event: KeyboardEvent, direction: 1 | -1): void {\n if (event.key !== 'Enter' && event.key !== ' ') return;\n event.preventDefault();\n if (!this.canInteract() || this.isAtLimit(direction)) return;\n this.stepBy(direction);\n }\n\n /**\n * @method onValueKeydown\n * @description\n * Handles the spinbutton keyboard contract: arrows step by `step`, Home / End jump\n * to the bounds, PageUp / PageDown apply a larger step, and Enter commits the\n * currently typed text.\n * @param event The keyboard event.\n */\n onValueKeydown(event: KeyboardEvent): void {\n const current = this.displayValue();\n let next: number | null = null;\n\n switch (event.key) {\n case 'ArrowUp':\n next = current + this.effectiveStep();\n break;\n case 'ArrowDown':\n next = current - this.effectiveStep();\n break;\n case 'PageUp':\n next = current + this.pageStep();\n break;\n case 'PageDown':\n next = current - this.pageStep();\n break;\n case 'Home':\n next = this.min();\n break;\n case 'End':\n next = this.safeMax();\n break;\n case 'Enter':\n event.preventDefault();\n this.commitTypedValue();\n return;\n default:\n return;\n }\n\n event.preventDefault();\n this.commit(next);\n this.syncInputText();\n }\n\n /**\n * @method onInput\n * @description\n * Rejects non-numeric characters as they are typed, without clamping — clamping\n * mid-keystroke would fight the user on the way to a valid number (typing `1`\n * toward `12` when `min` is `5`). The value is committed on blur or Enter.\n * @param event The input event.\n */\n onInput(event: Event): void {\n const target = event.target as HTMLInputElement;\n const original = target.value;\n const sanitized = this.sanitize(original);\n if (sanitized === original) return;\n\n // Rewriting `value` sends the caret to the end, which would scramble editing in\n // the middle of the number. Pull it back by however many characters were dropped.\n const caret = target.selectionStart ?? original.length;\n const removed = original.length - sanitized.length;\n const nextCaret = Math.max(0, caret - removed);\n\n target.value = sanitized;\n target.setSelectionRange(nextCaret, nextCaret);\n }\n\n /**\n * @method onValueBlur\n * @description\n * Commits the typed text, marks the control touched, and resyncs the field so its\n * text always matches the clamped value.\n */\n onValueBlur(): void {\n this.commitTypedValue();\n this.onTouched();\n this.effectiveControl?.markAsTouched();\n }\n\n /**\n * Keeps digits only, plus a single leading `-` when the range includes negatives.\n * Applied to whatever lands in the field, so it also covers paste and drop.\n */\n private sanitize(text: string): string {\n const negative = this.min() < 0 && text.startsWith('-');\n return (negative ? '-' : '') + text.replaceAll(/\\D/g, '');\n }\n\n /** Whether the counter currently accepts changes. */\n private canInteract(): boolean {\n return !this.disabled() && !this.readonly();\n }\n\n /** Whether stepping in `direction` would move past an already-reached bound. */\n private isAtLimit(direction: 1 | -1): boolean {\n return direction === 1 ? this.atMax() : this.atMin();\n }\n\n /**\n * Rounds and clamps an arbitrary value into `[min, safeMax]`. Non-finite input\n * (an empty or unparseable field) falls back to `min`, so the value is never empty.\n */\n private clamp(value: number | null | undefined): number {\n const parsed = Number(value);\n if (value === null || value === undefined || !Number.isFinite(parsed)) {\n return this.min();\n }\n return Math.min(Math.max(Math.round(parsed), this.min()), this.safeMax());\n }\n\n /**\n * The single write path: clamps, updates the model, notifies the forms API, and\n * emits `(valueChange)` — so clamping and emission happen in exactly one place.\n */\n private commit(next: number): void {\n if (!this.canInteract()) return;\n\n const clamped = this.clamp(next);\n this.isWriting = true;\n this.value.set(clamped);\n this.onChange(clamped);\n this.effectiveControl?.setValue(clamped, { emitEvent: false });\n this.effectiveControl?.markAsDirty();\n this.isWriting = false;\n }\n\n /** Parses the field's current text, commits it, and resyncs the displayed text. */\n private commitTypedValue(): void {\n const raw = this.valueInput()?.nativeElement.value ?? '';\n const parsed = raw === '' || raw === '-' ? this.displayValue() : Number(raw);\n this.commit(parsed);\n this.syncInputText();\n }\n\n /**\n * Rewrites the field's text from `displayValue`. Required because the `[value]`\n * binding will not update the DOM when the model is unchanged — typing `150` with\n * `max` `99` while the value is already `99` leaves stale text behind otherwise.\n */\n private syncInputText(): void {\n const input = this.valueInput()?.nativeElement;\n if (!input) return;\n\n const text = String(this.displayValue());\n if (input.value !== text) {\n input.value = text;\n }\n }\n}\n","<tk-button\n class=\"tk-counter__button\"\n severity=\"secondary\"\n icon=\"minus\"\n [ariaLabel]=\"decreaseLabel()\"\n [disabled]=\"disabled() || readonly() || atMin()\"\n (pointerdown)=\"startHold(-1)\"\n (pointerup)=\"stopHold()\"\n (pointerleave)=\"stopHold()\"\n (pointercancel)=\"stopHold()\"\n (blur)=\"stopHold()\"\n (keydown)=\"onButtonKeydown($event, -1)\" />\n\n<input\n #valueInput\n class=\"tk-counter__value\"\n type=\"text\"\n inputmode=\"numeric\"\n autocomplete=\"off\"\n role=\"spinbutton\"\n [style.--tk-counter-value-ch]=\"valueWidthCh()\"\n [value]=\"displayValue()\"\n [attr.aria-valuenow]=\"displayValue()\"\n [attr.aria-valuemin]=\"min()\"\n [attr.aria-valuemax]=\"safeMax()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby() || null\"\n [disabled]=\"disabled()\"\n [readOnly]=\"readonly()\"\n [attr.aria-readonly]=\"readonly() || null\"\n (input)=\"onInput($event)\"\n (keydown)=\"onValueKeydown($event)\"\n (blur)=\"onValueBlur()\" />\n\n<tk-button\n class=\"tk-counter__button\"\n severity=\"secondary\"\n icon=\"plus\"\n [ariaLabel]=\"increaseLabel()\"\n [disabled]=\"disabled() || readonly() || atMax()\"\n (pointerdown)=\"startHold(1)\"\n (pointerup)=\"stopHold()\"\n (pointerleave)=\"stopHold()\"\n (pointercancel)=\"stopHold()\"\n (blur)=\"stopHold()\"\n (keydown)=\"onButtonKeydown($event, 1)\" />\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -668,6 +668,19 @@ const plusIcon = {
|
|
|
668
668
|
}
|
|
669
669
|
};
|
|
670
670
|
|
|
671
|
+
const minusIcon = {
|
|
672
|
+
'minus': {
|
|
673
|
+
name: 'faMinus',
|
|
674
|
+
tags: ['basic'],
|
|
675
|
+
source: 'Font awesome',
|
|
676
|
+
styles: {
|
|
677
|
+
light: import('@fortawesome/pro-light-svg-icons').then(m => m.faMinus),
|
|
678
|
+
regular: import('@fortawesome/pro-regular-svg-icons').then(m => m.faMinus),
|
|
679
|
+
solid: import('@fortawesome/pro-solid-svg-icons').then(m => m.faMinus)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
|
|
671
684
|
const linkIcon = {
|
|
672
685
|
'link': {
|
|
673
686
|
name: 'faLink',
|
|
@@ -1150,6 +1163,7 @@ const IconCatalog = {
|
|
|
1150
1163
|
...locationIcon,
|
|
1151
1164
|
...layerIcon,
|
|
1152
1165
|
...plusIcon,
|
|
1166
|
+
...minusIcon,
|
|
1153
1167
|
...linkIcon,
|
|
1154
1168
|
...checkIcon,
|
|
1155
1169
|
...xMarkIcon,
|