ng-primitives 0.111.0 → 0.112.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,660 @@
1
+ import * as i0 from '@angular/core';
2
+ import { signal, computed, input, numberAttribute, output, booleanAttribute, Directive, inject, Injector } from '@angular/core';
3
+ import { uniqueId, injectDisposables } from 'ng-primitives/utils';
4
+ import { injectElementRef, explicitEffect } from 'ng-primitives/internal';
5
+ import { createPrimitive, controlled, emitter, attrBinding, dataBinding, deprecatedSetter, listener } from 'ng-primitives/state';
6
+ import { DOCUMENT } from '@angular/common';
7
+ import { ngpFormControl } from 'ng-primitives/form-field';
8
+ import { ngpInteractions } from 'ng-primitives/interactions';
9
+
10
+ const [NgpNumberFieldStateToken, ngpNumberField, injectNumberFieldState, provideNumberFieldState,] = createPrimitive('NgpNumberField', ({ id = signal(uniqueId('ngp-number-field')), value: _value = signal(null), min = signal(-Infinity), max = signal(Infinity), step = signal(1), largeStep: _largeStep = signal(10), disabled: _disabled = signal(false), readonly: _readonly = signal(false), onValueChange, }) => {
11
+ const element = injectElementRef();
12
+ const value = controlled(_value);
13
+ const disabled = controlled(_disabled);
14
+ const readonly = controlled(_readonly);
15
+ const valueChange = emitter();
16
+ const canIncrement = computed(() => {
17
+ if (disabled() || readonly())
18
+ return false;
19
+ if (value() === null)
20
+ return true;
21
+ return value() < max();
22
+ }, ...(ngDevMode ? [{ debugName: "canIncrement" }] : []));
23
+ const canDecrement = computed(() => {
24
+ if (disabled() || readonly())
25
+ return false;
26
+ if (value() === null)
27
+ return true;
28
+ return value() > min();
29
+ }, ...(ngDevMode ? [{ debugName: "canDecrement" }] : []));
30
+ // Host bindings
31
+ attrBinding(element, 'role', () => 'group');
32
+ dataBinding(element, 'data-disabled', disabled);
33
+ dataBinding(element, 'data-readonly', readonly);
34
+ /**
35
+ * Count the number of decimal places in a number.
36
+ */
37
+ function getDecimalPlaces(n) {
38
+ const str = String(n);
39
+ const dotIndex = str.indexOf('.');
40
+ return dotIndex === -1 ? 0 : str.length - dotIndex - 1;
41
+ }
42
+ /**
43
+ * Round a number to a specific number of decimal places to avoid
44
+ * floating point precision issues (e.g. 0.1 + 0.2 = 0.30000000000000004).
45
+ */
46
+ function roundToPrecision(val, precision) {
47
+ if (precision === 0)
48
+ return Math.round(val);
49
+ return parseFloat(val.toFixed(precision));
50
+ }
51
+ function clampAndStep(val) {
52
+ const clamped = Math.min(max(), Math.max(min(), val));
53
+ // Round to nearest step
54
+ if (isFinite(step()) && step() > 0) {
55
+ const base = isFinite(min()) ? min() : 0;
56
+ const precision = Math.max(getDecimalPlaces(step()), getDecimalPlaces(base));
57
+ const stepped = roundToPrecision(Math.round((clamped - base) / step()) * step() + base, precision);
58
+ return Math.min(max(), Math.max(min(), stepped));
59
+ }
60
+ return clamped;
61
+ }
62
+ let suppressEmit = false;
63
+ function setValue(newValue) {
64
+ if (disabled() || readonly())
65
+ return;
66
+ if (newValue !== null && isNaN(newValue))
67
+ return;
68
+ const finalValue = newValue !== null ? clampAndStep(newValue) : null;
69
+ // Skip emit when value is unchanged
70
+ if (finalValue === value())
71
+ return;
72
+ value.set(finalValue);
73
+ if (!suppressEmit) {
74
+ onValueChange?.(finalValue);
75
+ valueChange.emit(finalValue);
76
+ }
77
+ }
78
+ let inputCommitFn = null;
79
+ function registerInputCommit(commitFn) {
80
+ inputCommitFn = commitFn;
81
+ }
82
+ /**
83
+ * Commit any pending input value without emitting change events.
84
+ * This ensures increment/decrement operates on the displayed value
85
+ * while only emitting the final stepped result.
86
+ */
87
+ function commitPendingInputSilently() {
88
+ if (!inputCommitFn)
89
+ return;
90
+ suppressEmit = true;
91
+ try {
92
+ inputCommitFn();
93
+ }
94
+ finally {
95
+ suppressEmit = false;
96
+ }
97
+ }
98
+ function getStepPrecision() {
99
+ const base = isFinite(min()) ? min() : 0;
100
+ return Math.max(getDecimalPlaces(step()), getDecimalPlaces(base));
101
+ }
102
+ function increment(multiplier = 1) {
103
+ if (!canIncrement())
104
+ return;
105
+ const valueBefore = value();
106
+ commitPendingInputSilently();
107
+ const valueAfterCommit = value();
108
+ const current = valueAfterCommit ?? (isFinite(min()) ? min() : 0);
109
+ const precision = getStepPrecision();
110
+ setValue(roundToPrecision(current + step() * multiplier, precision));
111
+ // If the silent commit changed the value but setValue was a no-op
112
+ // (stepped result clamped back to the committed value), emit the change
113
+ // so the parent learns about the new value.
114
+ if (valueBefore !== value() && valueAfterCommit === value()) {
115
+ onValueChange?.(value());
116
+ valueChange.emit(value());
117
+ }
118
+ }
119
+ function decrement(multiplier = 1) {
120
+ if (!canDecrement())
121
+ return;
122
+ const valueBefore = value();
123
+ commitPendingInputSilently();
124
+ const valueAfterCommit = value();
125
+ const current = valueAfterCommit ?? (isFinite(max()) ? max() : 0);
126
+ const precision = getStepPrecision();
127
+ setValue(roundToPrecision(current - step() * multiplier, precision));
128
+ // If the silent commit changed the value but setValue was a no-op
129
+ // (stepped result clamped back to the committed value), emit the change
130
+ // so the parent learns about the new value.
131
+ if (valueBefore !== value() && valueAfterCommit === value()) {
132
+ onValueChange?.(value());
133
+ valueChange.emit(value());
134
+ }
135
+ }
136
+ function setDisabled(isDisabled) {
137
+ disabled.set(isDisabled);
138
+ }
139
+ function setReadonly(isReadonly) {
140
+ readonly.set(isReadonly);
141
+ }
142
+ return {
143
+ id,
144
+ value,
145
+ min,
146
+ max,
147
+ step,
148
+ largeStep: _largeStep,
149
+ disabled: deprecatedSetter(disabled, 'setDisabled'),
150
+ readonly: deprecatedSetter(readonly, 'setReadonly'),
151
+ canIncrement,
152
+ canDecrement,
153
+ valueChange: valueChange.asObservable(),
154
+ setValue,
155
+ increment,
156
+ decrement,
157
+ setDisabled,
158
+ setReadonly,
159
+ registerInputCommit,
160
+ };
161
+ });
162
+
163
+ /**
164
+ * Apply the `ngpNumberField` directive to an element that represents the number field
165
+ * and contains the input, increment, and decrement buttons.
166
+ */
167
+ class NgpNumberField {
168
+ constructor() {
169
+ /**
170
+ * The id of the number field. If not provided, a unique id will be generated.
171
+ */
172
+ this.id = input(uniqueId('ngp-number-field'), ...(ngDevMode ? [{ debugName: "id" }] : []));
173
+ /**
174
+ * The value of the number field.
175
+ */
176
+ this.value = input(null, ...(ngDevMode ? [{ debugName: "value", alias: 'ngpNumberFieldValue',
177
+ transform: (v) => v === null || v === undefined || v === '' ? null : numberAttribute(v) }] : [{
178
+ alias: 'ngpNumberFieldValue',
179
+ transform: (v) => v === null || v === undefined || v === '' ? null : numberAttribute(v),
180
+ }]));
181
+ /**
182
+ * Emits when the value changes.
183
+ */
184
+ this.valueChange = output({
185
+ alias: 'ngpNumberFieldValueChange',
186
+ });
187
+ /**
188
+ * The minimum value.
189
+ */
190
+ this.min = input(-Infinity, ...(ngDevMode ? [{ debugName: "min", alias: 'ngpNumberFieldMin',
191
+ transform: numberAttribute }] : [{
192
+ alias: 'ngpNumberFieldMin',
193
+ transform: numberAttribute,
194
+ }]));
195
+ /**
196
+ * The maximum value.
197
+ */
198
+ this.max = input(Infinity, ...(ngDevMode ? [{ debugName: "max", alias: 'ngpNumberFieldMax',
199
+ transform: numberAttribute }] : [{
200
+ alias: 'ngpNumberFieldMax',
201
+ transform: numberAttribute,
202
+ }]));
203
+ /**
204
+ * The step value.
205
+ */
206
+ this.step = input(1, ...(ngDevMode ? [{ debugName: "step", alias: 'ngpNumberFieldStep',
207
+ transform: numberAttribute }] : [{
208
+ alias: 'ngpNumberFieldStep',
209
+ transform: numberAttribute,
210
+ }]));
211
+ /**
212
+ * The large step value (used with Shift key).
213
+ */
214
+ this.largeStep = input(10, ...(ngDevMode ? [{ debugName: "largeStep", alias: 'ngpNumberFieldLargeStep',
215
+ transform: numberAttribute }] : [{
216
+ alias: 'ngpNumberFieldLargeStep',
217
+ transform: numberAttribute,
218
+ }]));
219
+ /**
220
+ * The disabled state of the number field.
221
+ */
222
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", alias: 'ngpNumberFieldDisabled',
223
+ transform: booleanAttribute }] : [{
224
+ alias: 'ngpNumberFieldDisabled',
225
+ transform: booleanAttribute,
226
+ }]));
227
+ /**
228
+ * The readonly state of the number field.
229
+ */
230
+ this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly", alias: 'ngpNumberFieldReadonly',
231
+ transform: booleanAttribute }] : [{
232
+ alias: 'ngpNumberFieldReadonly',
233
+ transform: booleanAttribute,
234
+ }]));
235
+ /**
236
+ * @internal
237
+ */
238
+ this.state = ngpNumberField({
239
+ id: this.id,
240
+ value: this.value,
241
+ min: this.min,
242
+ max: this.max,
243
+ step: this.step,
244
+ largeStep: this.largeStep,
245
+ disabled: this.disabled,
246
+ readonly: this.readonly,
247
+ onValueChange: value => this.valueChange.emit(value),
248
+ });
249
+ }
250
+ /**
251
+ * Set the value of the number field.
252
+ */
253
+ setValue(value) {
254
+ this.state.setValue(value);
255
+ }
256
+ /**
257
+ * Increment the value.
258
+ */
259
+ increment(multiplier) {
260
+ this.state.increment(multiplier);
261
+ }
262
+ /**
263
+ * Decrement the value.
264
+ */
265
+ decrement(multiplier) {
266
+ this.state.decrement(multiplier);
267
+ }
268
+ /**
269
+ * Set the disabled state.
270
+ */
271
+ setDisabled(disabled) {
272
+ this.state.setDisabled(disabled);
273
+ }
274
+ /**
275
+ * Set the readonly state.
276
+ */
277
+ setReadonly(readonly) {
278
+ this.state.setReadonly(readonly);
279
+ }
280
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberField, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
281
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpNumberField, isStandalone: true, selector: "[ngpNumberField]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "ngpNumberFieldValue", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "ngpNumberFieldMin", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "ngpNumberFieldMax", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "ngpNumberFieldStep", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "ngpNumberFieldLargeStep", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpNumberFieldDisabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "ngpNumberFieldReadonly", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "ngpNumberFieldValueChange" }, providers: [provideNumberFieldState()], exportAs: ["ngpNumberField"], ngImport: i0 }); }
282
+ }
283
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberField, decorators: [{
284
+ type: Directive,
285
+ args: [{
286
+ selector: '[ngpNumberField]',
287
+ exportAs: 'ngpNumberField',
288
+ providers: [provideNumberFieldState()],
289
+ }]
290
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldValue", required: false }] }], valueChange: [{ type: i0.Output, args: ["ngpNumberFieldValueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldMin", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldMax", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldStep", required: false }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldLargeStep", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldDisabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldReadonly", required: false }] }] } });
291
+
292
+ const [NgpNumberFieldInputStateToken, ngpNumberFieldInput, injectNumberFieldInputState, provideNumberFieldInputState,] = createPrimitive('NgpNumberFieldInput', ({ allowWheelScrub = signal(false) }) => {
293
+ const elementRef = injectElementRef();
294
+ const numberField = injectNumberFieldState();
295
+ const document = inject(DOCUMENT);
296
+ // Form control integration — sets id, aria-labelledby, aria-describedby on the input
297
+ ngpFormControl({ id: numberField().id, disabled: numberField().disabled });
298
+ const tabindex = computed(() => (numberField().disabled() ? -1 : 0), ...(ngDevMode ? [{ debugName: "tabindex" }] : []));
299
+ // Host bindings
300
+ const inputMode = computed(() => {
301
+ const minVal = numberField().min();
302
+ const stepVal = numberField().step();
303
+ const allowsNegative = !isFinite(minVal) || minVal < 0;
304
+ const hasDecimals = stepVal % 1 !== 0;
305
+ // Some mobile keyboards can't show both minus sign and decimal point
306
+ if (allowsNegative && hasDecimals)
307
+ return 'text';
308
+ if (hasDecimals)
309
+ return 'decimal';
310
+ if (!allowsNegative)
311
+ return 'numeric';
312
+ return 'text';
313
+ }, ...(ngDevMode ? [{ debugName: "inputMode" }] : []));
314
+ attrBinding(elementRef, 'role', 'spinbutton');
315
+ attrBinding(elementRef, 'type', 'text');
316
+ attrBinding(elementRef, 'inputmode', inputMode);
317
+ attrBinding(elementRef, 'autocomplete', 'off');
318
+ attrBinding(elementRef, 'autocorrect', 'off');
319
+ attrBinding(elementRef, 'spellcheck', 'false');
320
+ attrBinding(elementRef, 'aria-valuemin', () => {
321
+ const min = numberField().min();
322
+ return isFinite(min) ? min.toString() : null;
323
+ });
324
+ attrBinding(elementRef, 'aria-valuemax', () => {
325
+ const max = numberField().max();
326
+ return isFinite(max) ? max.toString() : null;
327
+ });
328
+ attrBinding(elementRef, 'aria-valuenow', () => numberField().value()?.toString() ?? null);
329
+ attrBinding(elementRef, 'tabindex', () => tabindex().toString());
330
+ attrBinding(elementRef, 'readonly', () => (numberField().readonly() ? '' : null));
331
+ dataBinding(elementRef, 'data-readonly', () => numberField().readonly());
332
+ ngpInteractions({
333
+ hover: true,
334
+ focusVisible: true,
335
+ disabled: numberField().disabled,
336
+ });
337
+ let isFocused = false;
338
+ /**
339
+ * Parse text and set the number field value accordingly.
340
+ */
341
+ function parseAndSetValue(text) {
342
+ const trimmed = text.trim();
343
+ if (trimmed === '' || trimmed === '-') {
344
+ numberField().setValue(null);
345
+ }
346
+ else {
347
+ const parsed = parseFloat(trimmed);
348
+ if (!isNaN(parsed)) {
349
+ numberField().setValue(parsed);
350
+ }
351
+ }
352
+ }
353
+ /**
354
+ * Commit the current input text to the number field value.
355
+ * Called before increment/decrement so they operate on the displayed value.
356
+ */
357
+ function commitInputValue() {
358
+ if (!isFocused)
359
+ return;
360
+ parseAndSetValue(elementRef.nativeElement.value);
361
+ }
362
+ // Register the commit function with the number field so buttons can trigger it
363
+ numberField().registerInputCommit(commitInputValue);
364
+ function formatDisplayValue() {
365
+ const val = numberField().value();
366
+ return val !== null ? String(val) : '';
367
+ }
368
+ // Sync input display value when the number field value changes
369
+ // (programmatically, via stepping, or on commit)
370
+ explicitEffect([() => numberField().value()], ([value]) => {
371
+ elementRef.nativeElement.value = value !== null ? String(value) : '';
372
+ });
373
+ listener(elementRef, 'focus', () => {
374
+ isFocused = true;
375
+ });
376
+ listener(elementRef, 'blur', () => {
377
+ isFocused = false;
378
+ parseAndSetValue(elementRef.nativeElement.value);
379
+ // Always sync the display value on blur to show the clamped/stepped value
380
+ elementRef.nativeElement.value = formatDisplayValue();
381
+ });
382
+ // Reject characters that can't form a valid number
383
+ listener(elementRef, 'beforeinput', (event) => {
384
+ if (numberField().disabled() || numberField().readonly())
385
+ return;
386
+ // Only filter insertions (typing, paste, drop)
387
+ const insertTypes = ['insertText', 'insertFromPaste', 'insertFromDrop'];
388
+ if (!insertTypes.includes(event.inputType) || !event.data)
389
+ return;
390
+ const input = elementRef.nativeElement;
391
+ const selStart = input.selectionStart ?? 0;
392
+ const selEnd = input.selectionEnd ?? 0;
393
+ const current = input.value;
394
+ const proposed = current.slice(0, selStart) + event.data + current.slice(selEnd);
395
+ const minVal = numberField().min();
396
+ const allowsNegative = !isFinite(minVal) || minVal < 0;
397
+ // Build a regex for valid partial number input
398
+ const pattern = allowsNegative ? /^-?(\d+\.?\d*|\.\d*)?$/ : /^(\d+\.?\d*|\.\d*)?$/;
399
+ if (!pattern.test(proposed)) {
400
+ event.preventDefault();
401
+ }
402
+ });
403
+ // Keyboard interactions
404
+ listener(elementRef, 'keydown', (event) => {
405
+ if (numberField().disabled() || numberField().readonly())
406
+ return;
407
+ const useLargeStep = event.shiftKey;
408
+ function getLargeStepMultiplier() {
409
+ const s = numberField().step();
410
+ if (!isFinite(s) || s <= 0)
411
+ return 1;
412
+ return numberField().largeStep() / s;
413
+ }
414
+ switch (event.key) {
415
+ case 'ArrowUp':
416
+ event.preventDefault();
417
+ numberField().increment(useLargeStep ? getLargeStepMultiplier() : 1);
418
+ elementRef.nativeElement.value = formatDisplayValue();
419
+ break;
420
+ case 'ArrowDown':
421
+ event.preventDefault();
422
+ numberField().decrement(useLargeStep ? getLargeStepMultiplier() : 1);
423
+ elementRef.nativeElement.value = formatDisplayValue();
424
+ break;
425
+ case 'Home':
426
+ if (isFinite(numberField().min())) {
427
+ event.preventDefault();
428
+ numberField().setValue(numberField().min());
429
+ elementRef.nativeElement.value = formatDisplayValue();
430
+ }
431
+ break;
432
+ case 'End':
433
+ if (isFinite(numberField().max())) {
434
+ event.preventDefault();
435
+ numberField().setValue(numberField().max());
436
+ elementRef.nativeElement.value = formatDisplayValue();
437
+ }
438
+ break;
439
+ case 'Enter':
440
+ parseAndSetValue(elementRef.nativeElement.value);
441
+ elementRef.nativeElement.value = formatDisplayValue();
442
+ break;
443
+ }
444
+ });
445
+ // Mouse wheel support
446
+ listener(elementRef, 'wheel', (event) => {
447
+ if (!allowWheelScrub() || numberField().disabled() || numberField().readonly())
448
+ return;
449
+ // Don't intercept browser zoom (Ctrl+wheel / Cmd+wheel)
450
+ if (event.ctrlKey || event.metaKey)
451
+ return;
452
+ // Only handle when focused
453
+ if (document.activeElement !== elementRef.nativeElement)
454
+ return;
455
+ event.preventDefault();
456
+ if (event.deltaY < 0) {
457
+ numberField().increment();
458
+ }
459
+ else if (event.deltaY > 0) {
460
+ numberField().decrement();
461
+ }
462
+ elementRef.nativeElement.value = formatDisplayValue();
463
+ }, { config: { passive: false } });
464
+ function focus() {
465
+ elementRef.nativeElement.focus({ preventScroll: true });
466
+ }
467
+ return {
468
+ focus,
469
+ };
470
+ });
471
+
472
+ /**
473
+ * Apply the `ngpNumberFieldInput` directive to an input element within a number field.
474
+ */
475
+ class NgpNumberFieldInput {
476
+ constructor() {
477
+ /**
478
+ * Whether mouse wheel changes the value when the input is focused.
479
+ */
480
+ this.allowWheelScrub = input(false, ...(ngDevMode ? [{ debugName: "allowWheelScrub", alias: 'ngpNumberFieldInputAllowWheelScrub',
481
+ transform: booleanAttribute }] : [{
482
+ alias: 'ngpNumberFieldInputAllowWheelScrub',
483
+ transform: booleanAttribute,
484
+ }]));
485
+ ngpNumberFieldInput({
486
+ allowWheelScrub: this.allowWheelScrub,
487
+ });
488
+ }
489
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberFieldInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
490
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpNumberFieldInput, isStandalone: true, selector: "[ngpNumberFieldInput]", inputs: { allowWheelScrub: { classPropertyName: "allowWheelScrub", publicName: "ngpNumberFieldInputAllowWheelScrub", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideNumberFieldInputState()], exportAs: ["ngpNumberFieldInput"], ngImport: i0 }); }
491
+ }
492
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberFieldInput, decorators: [{
493
+ type: Directive,
494
+ args: [{
495
+ selector: '[ngpNumberFieldInput]',
496
+ exportAs: 'ngpNumberFieldInput',
497
+ providers: [provideNumberFieldInputState()],
498
+ }]
499
+ }], ctorParameters: () => [], propDecorators: { allowWheelScrub: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpNumberFieldInputAllowWheelScrub", required: false }] }] } });
500
+
501
+ const [NgpNumberFieldIncrementStateToken, ngpNumberFieldIncrement, injectNumberFieldIncrementState, provideNumberFieldIncrementState,] = createPrimitive('NgpNumberFieldIncrement', ({}) => {
502
+ const elementRef = injectElementRef();
503
+ const numberField = injectNumberFieldState();
504
+ const injector = inject(Injector);
505
+ const disposables = injectDisposables();
506
+ const isDisabled = computed(() => !numberField().canIncrement(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
507
+ // Host bindings
508
+ attrBinding(elementRef, 'type', 'button');
509
+ attrBinding(elementRef, 'tabindex', '-1');
510
+ attrBinding(elementRef, 'disabled', () => (isDisabled() ? '' : null));
511
+ dataBinding(elementRef, 'data-disabled', isDisabled);
512
+ ngpInteractions({
513
+ hover: true,
514
+ focusVisible: true,
515
+ press: true,
516
+ disabled: isDisabled,
517
+ });
518
+ let cleanupRepeat = null;
519
+ function stopRepeat() {
520
+ cleanupRepeat?.();
521
+ cleanupRepeat = null;
522
+ }
523
+ listener(elementRef, 'pointerdown', (event) => {
524
+ event.preventDefault();
525
+ if (isDisabled())
526
+ return;
527
+ numberField().increment();
528
+ // Start auto-repeat: 400ms initial delay, then 60ms interval
529
+ stopRepeat();
530
+ let intervalCleanup = null;
531
+ const delayCleanup = disposables.setTimeout(() => {
532
+ intervalCleanup = disposables.setInterval(() => {
533
+ if (!numberField().canIncrement()) {
534
+ stopRepeat();
535
+ return;
536
+ }
537
+ numberField().increment();
538
+ }, 60);
539
+ }, 400);
540
+ // Set up document-level listeners to stop on pointer release
541
+ const pointerUpCleanup = listener(document, 'pointerup', stopRepeat, {
542
+ config: false,
543
+ injector,
544
+ });
545
+ const pointerCancelCleanup = listener(document, 'pointercancel', stopRepeat, {
546
+ config: false,
547
+ injector,
548
+ });
549
+ cleanupRepeat = () => {
550
+ delayCleanup();
551
+ intervalCleanup?.();
552
+ pointerUpCleanup();
553
+ pointerCancelCleanup();
554
+ };
555
+ });
556
+ return {};
557
+ });
558
+
559
+ /**
560
+ * Apply the `ngpNumberFieldIncrement` directive to a button element that increments the number field value.
561
+ */
562
+ class NgpNumberFieldIncrement {
563
+ constructor() {
564
+ ngpNumberFieldIncrement({});
565
+ }
566
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberFieldIncrement, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
567
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: NgpNumberFieldIncrement, isStandalone: true, selector: "[ngpNumberFieldIncrement]", providers: [provideNumberFieldIncrementState()], exportAs: ["ngpNumberFieldIncrement"], ngImport: i0 }); }
568
+ }
569
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberFieldIncrement, decorators: [{
570
+ type: Directive,
571
+ args: [{
572
+ selector: '[ngpNumberFieldIncrement]',
573
+ exportAs: 'ngpNumberFieldIncrement',
574
+ providers: [provideNumberFieldIncrementState()],
575
+ }]
576
+ }], ctorParameters: () => [] });
577
+
578
+ const [NgpNumberFieldDecrementStateToken, ngpNumberFieldDecrement, injectNumberFieldDecrementState, provideNumberFieldDecrementState,] = createPrimitive('NgpNumberFieldDecrement', ({}) => {
579
+ const elementRef = injectElementRef();
580
+ const numberField = injectNumberFieldState();
581
+ const injector = inject(Injector);
582
+ const disposables = injectDisposables();
583
+ const isDisabled = computed(() => !numberField().canDecrement(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
584
+ // Host bindings
585
+ attrBinding(elementRef, 'type', 'button');
586
+ attrBinding(elementRef, 'tabindex', '-1');
587
+ attrBinding(elementRef, 'disabled', () => (isDisabled() ? '' : null));
588
+ dataBinding(elementRef, 'data-disabled', isDisabled);
589
+ ngpInteractions({
590
+ hover: true,
591
+ focusVisible: true,
592
+ press: true,
593
+ disabled: isDisabled,
594
+ });
595
+ let cleanupRepeat = null;
596
+ function stopRepeat() {
597
+ cleanupRepeat?.();
598
+ cleanupRepeat = null;
599
+ }
600
+ listener(elementRef, 'pointerdown', (event) => {
601
+ event.preventDefault();
602
+ if (isDisabled())
603
+ return;
604
+ numberField().decrement();
605
+ // Start auto-repeat: 400ms initial delay, then 60ms interval
606
+ stopRepeat();
607
+ let intervalCleanup = null;
608
+ const delayCleanup = disposables.setTimeout(() => {
609
+ intervalCleanup = disposables.setInterval(() => {
610
+ if (!numberField().canDecrement()) {
611
+ stopRepeat();
612
+ return;
613
+ }
614
+ numberField().decrement();
615
+ }, 60);
616
+ }, 400);
617
+ // Set up document-level listeners to stop on pointer release
618
+ const pointerUpCleanup = listener(document, 'pointerup', stopRepeat, {
619
+ config: false,
620
+ injector,
621
+ });
622
+ const pointerCancelCleanup = listener(document, 'pointercancel', stopRepeat, {
623
+ config: false,
624
+ injector,
625
+ });
626
+ cleanupRepeat = () => {
627
+ delayCleanup();
628
+ intervalCleanup?.();
629
+ pointerUpCleanup();
630
+ pointerCancelCleanup();
631
+ };
632
+ });
633
+ return {};
634
+ });
635
+
636
+ /**
637
+ * Apply the `ngpNumberFieldDecrement` directive to a button element that decrements the number field value.
638
+ */
639
+ class NgpNumberFieldDecrement {
640
+ constructor() {
641
+ ngpNumberFieldDecrement({});
642
+ }
643
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberFieldDecrement, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
644
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: NgpNumberFieldDecrement, isStandalone: true, selector: "[ngpNumberFieldDecrement]", providers: [provideNumberFieldDecrementState()], exportAs: ["ngpNumberFieldDecrement"], ngImport: i0 }); }
645
+ }
646
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpNumberFieldDecrement, decorators: [{
647
+ type: Directive,
648
+ args: [{
649
+ selector: '[ngpNumberFieldDecrement]',
650
+ exportAs: 'ngpNumberFieldDecrement',
651
+ providers: [provideNumberFieldDecrementState()],
652
+ }]
653
+ }], ctorParameters: () => [] });
654
+
655
+ /**
656
+ * Generated bundle index. Do not edit.
657
+ */
658
+
659
+ export { NgpNumberField, NgpNumberFieldDecrement, NgpNumberFieldIncrement, NgpNumberFieldInput, injectNumberFieldDecrementState, injectNumberFieldIncrementState, injectNumberFieldInputState, injectNumberFieldState, ngpNumberField, ngpNumberFieldDecrement, ngpNumberFieldIncrement, ngpNumberFieldInput, provideNumberFieldDecrementState, provideNumberFieldIncrementState, provideNumberFieldInputState, provideNumberFieldState };
660
+ //# sourceMappingURL=ng-primitives-number-field.mjs.map