@cdevhub/ngx-tw 0.2.1 → 0.4.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/README.md +264 -49
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs +11 -7
- package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-alert.mjs +43 -29
- package/fesm2022/cdevhub-ngx-tw-alert.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs +103 -0
- package/fesm2022/cdevhub-ngx-tw-aspect-ratio.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs +56 -31
- package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-badge.mjs +61 -35
- package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +154 -93
- package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-button.mjs +24 -15
- package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +3 -3
- package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs +408 -215
- package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-card.mjs +33 -24
- package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs +144 -79
- package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +99 -47
- package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs +34 -20
- package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +46 -29
- package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs +195 -110
- package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +140 -73
- package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-core.mjs +16 -11
- package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +393 -149
- package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +305 -150
- package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs +56 -43
- package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +51 -33
- package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +905 -0
- package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +49 -26
- package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs +133 -74
- package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs +2 -2
- package/fesm2022/cdevhub-ngx-tw-icon-lucide.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-icon.mjs +28 -17
- package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-input.mjs +37 -21
- package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-item.mjs +33 -24
- package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-menu.mjs +63 -48
- package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs +622 -0
- package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs +137 -71
- package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-popover.mjs +101 -46
- package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +57 -30
- package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-radio.mjs +108 -57
- package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +46 -26
- package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-select.mjs +217 -122
- package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-separator.mjs +21 -12
- package/fesm2022/cdevhub-ngx-tw-separator.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs +58 -44
- package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs +25 -14
- package/fesm2022/cdevhub-ngx-tw-skeleton.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-slider.mjs +155 -79
- package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-sort.mjs +44 -25
- package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs +29 -16
- package/fesm2022/cdevhub-ngx-tw-spinner.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -42
- package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stat.mjs +107 -64
- package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs +113 -47
- package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-switch.mjs +61 -32
- package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +88 -42
- package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-table.mjs +266 -118
- package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs +101 -51
- package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +798 -0
- package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs +11 -7
- package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-theme.mjs +22 -14
- package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +243 -95
- package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs +149 -82
- package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-toast.mjs +89 -55
- package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +50 -28
- package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs +1129 -0
- package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw-tree.mjs +554 -0
- package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -0
- package/fesm2022/cdevhub-ngx-tw.mjs +7 -0
- package/fesm2022/cdevhub-ngx-tw.mjs.map +1 -1
- package/index.json +1 -0
- package/package.json +45 -20
- package/theme/index.css +10 -2
- package/types/cdevhub-ngx-tw-aspect-ratio.d.ts +40 -0
- package/types/cdevhub-ngx-tw-calendar.d.ts +39 -8
- package/types/cdevhub-ngx-tw-command-palette.d.ts +7 -0
- package/types/cdevhub-ngx-tw-date-picker.d.ts +36 -10
- package/types/cdevhub-ngx-tw-file-upload.d.ts +250 -0
- package/types/cdevhub-ngx-tw-form-field.d.ts +22 -3
- package/types/cdevhub-ngx-tw-icon-lucide.d.ts +21 -3
- package/types/cdevhub-ngx-tw-number-input.d.ts +130 -0
- package/types/cdevhub-ngx-tw-popover.d.ts +8 -0
- package/types/cdevhub-ngx-tw-stepper.d.ts +30 -2
- package/types/cdevhub-ngx-tw-tab-nav.d.ts +8 -0
- package/types/cdevhub-ngx-tw-table.d.ts +30 -1
- package/types/cdevhub-ngx-tw-tabs.d.ts +8 -0
- package/types/cdevhub-ngx-tw-tags-input.d.ts +217 -0
- package/types/cdevhub-ngx-tw-time-picker.d.ts +31 -6
- package/types/cdevhub-ngx-tw-transfer.d.ts +330 -0
- package/types/cdevhub-ngx-tw-tree.d.ts +189 -0
- package/types/cdevhub-ngx-tw.d.ts +7 -0
|
@@ -0,0 +1,622 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, ElementRef, input, output, signal, computed, afterNextRender, isDevMode, forwardRef, Directive, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
|
+
import { TW_INPUT_VALUE_ACCESSOR } from '@cdevhub/ngx-tw/input';
|
|
5
|
+
import { tv } from 'tailwind-variants';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Turns a plain `<input twInput twNumberInput>` into a robust numeric field
|
|
9
|
+
* without `type="number"` — fixing the broken-on-mobile, browser-inconsistent
|
|
10
|
+
* native control. It composes the sibling {@link InputDirective}: that directive
|
|
11
|
+
* keeps owning the form-field chrome, error-state, and `aria-invalid` /
|
|
12
|
+
* `aria-required`, while this directive owns the numeric value transport,
|
|
13
|
+
* locale-aware parse/format, spinbutton ARIA, `inputmode`, and keyboard stepping.
|
|
14
|
+
*
|
|
15
|
+
* Pair it with the companion `<tw-number-stepper [for]="ref">` for visible
|
|
16
|
+
* up/down spinner buttons.
|
|
17
|
+
*
|
|
18
|
+
* Forms: implements `ControlValueAccessor`, so the value round-trips as a real
|
|
19
|
+
* `number | null` (never a string) through template-driven, reactive, and
|
|
20
|
+
* signal-based forms.
|
|
21
|
+
*/
|
|
22
|
+
class NumberInputDirective {
|
|
23
|
+
el = inject(ElementRef).nativeElement;
|
|
24
|
+
// ── Inputs ──
|
|
25
|
+
/** Smallest accepted value. Clamps the committed value (on blur, Enter, and stepping) and sets `aria-valuemin`. Does not clamp per keystroke. Defaults to `undefined` (no lower bound). */
|
|
26
|
+
min = input(undefined, /* @ts-ignore */
|
|
27
|
+
...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
|
|
28
|
+
/** Largest accepted value. Clamps the committed value (on blur, Enter, and stepping) and sets `aria-valuemax`. Does not clamp per keystroke. Defaults to `undefined` (no upper bound). */
|
|
29
|
+
max = input(undefined, /* @ts-ignore */
|
|
30
|
+
...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
|
|
31
|
+
/** Amount added or subtracted by ArrowUp/ArrowDown and the stepper buttons. Defaults to `1`. Values `<= 0` or non-finite fall back to `1`. */
|
|
32
|
+
step = input(1, /* @ts-ignore */
|
|
33
|
+
...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
|
|
34
|
+
/** `Intl.NumberFormat` options driving the blurred display (grouping, decimals, currency). The formatter's resolved `maximumFractionDigits` also sets commit-time rounding precision and switches `inputmode` to `'numeric'` when it is `0`. Percent style is not supported in v1. Defaults to `undefined` (locale default number formatting, grouping on). */
|
|
35
|
+
format = input(undefined, /* @ts-ignore */
|
|
36
|
+
...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
|
|
37
|
+
/** BCP-47 locale for `Intl.NumberFormat` formatting and for locale-aware parsing (decimal and group separators). Defaults to `undefined` (the runtime default locale). */
|
|
38
|
+
locale = input(undefined, /* @ts-ignore */
|
|
39
|
+
...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
40
|
+
// ── Output ──
|
|
41
|
+
/** Fires when the committed numeric value changes through user interaction (typing, stepping, clamping on blur/Enter). Does not fire on `writeValue` (programmatic form writes). Useful for non-form / template-ref usage alongside the `value` signal. */
|
|
42
|
+
valueChange = output();
|
|
43
|
+
// ── State ──
|
|
44
|
+
_value = signal(null, /* @ts-ignore */
|
|
45
|
+
...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
46
|
+
cvaDisabled = signal(false, /* @ts-ignore */
|
|
47
|
+
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
48
|
+
readonlySig = signal(false, /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "readonlySig" }] : /* istanbul ignore next */ []));
|
|
50
|
+
/** The current committed numeric value, or `null` when empty / unparseable. Read this from a template ref (`#n="twNumberInput"`) for non-form usage. */
|
|
51
|
+
value = this._value.asReadonly();
|
|
52
|
+
/** @internal Formatted display text surfaced through `TW_INPUT_VALUE_ACCESSOR` so the sibling InputDirective's empty / float-label state tracks the text in the box (not the numeric model). Updated on every field mutation. */
|
|
53
|
+
displayText = signal('', /* @ts-ignore */
|
|
54
|
+
...(ngDevMode ? [{ debugName: "displayText" }] : /* istanbul ignore next */ []));
|
|
55
|
+
/** True when the control is disabled. Reactively tracks the reactive-forms path (`setDisabledState` via `control.disable()`) and a static `disabled` attribute read at mount. A runtime-toggled declarative `[disabled]` binding without reactive forms is NOT reactively tracked (v2 limitation — same class as `readonly`). Read by the companion stepper so its buttons disable in lock-step. */
|
|
56
|
+
disabled = computed(() => this.cvaDisabled() || this.el.disabled, /* @ts-ignore */
|
|
57
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
58
|
+
/** True when the host input carries the `readonly` attribute (static / declarative, seeded after render). A runtime toggle of the bare `readonly` attribute is not reactively tracked (v2). Read by the companion stepper so its buttons disable in lock-step. */
|
|
59
|
+
readonly = this.readonlySig.asReadonly();
|
|
60
|
+
/** @internal Mobile-keyboard hint: `'numeric'` for integer-only formats, else `'decimal'`. */
|
|
61
|
+
inputMode = computed(() => this.format()?.maximumFractionDigits === 0 ? 'numeric' : 'decimal', /* @ts-ignore */
|
|
62
|
+
...(ngDevMode ? [{ debugName: "inputMode" }] : /* istanbul ignore next */ []));
|
|
63
|
+
/** @internal Locale group / decimal separators, memoized per `locale()`. */
|
|
64
|
+
separators = computed(() => {
|
|
65
|
+
try {
|
|
66
|
+
const parts = new Intl.NumberFormat(this.locale()).formatToParts(11000.1);
|
|
67
|
+
return {
|
|
68
|
+
group: parts.find((p) => p.type === 'group')?.value ?? '',
|
|
69
|
+
decimal: parts.find((p) => p.type === 'decimal')?.value ?? '.',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return { group: '', decimal: '.' };
|
|
74
|
+
}
|
|
75
|
+
}, /* @ts-ignore */
|
|
76
|
+
...(ngDevMode ? [{ debugName: "separators" }] : /* istanbul ignore next */ []));
|
|
77
|
+
// ── Plain (non-signal) bookkeeping ──
|
|
78
|
+
_focused = false;
|
|
79
|
+
_lastValue = null;
|
|
80
|
+
_lastDisplay = '';
|
|
81
|
+
onChange = () => { };
|
|
82
|
+
onTouched = () => { };
|
|
83
|
+
constructor() {
|
|
84
|
+
// Seed `readonly` after render. InputDirective mirrors its `readonly` input
|
|
85
|
+
// onto the native attribute via an effect that runs AFTER construction
|
|
86
|
+
// (input.ts:330-337), so a synchronous read here would be stale.
|
|
87
|
+
afterNextRender(() => this.readonlySig.set(this.el.readOnly));
|
|
88
|
+
// Dev-mode: warn once on a bogus step (mirrors time-picker).
|
|
89
|
+
afterNextRender(() => {
|
|
90
|
+
if (!isDevMode())
|
|
91
|
+
return;
|
|
92
|
+
const s = this.step();
|
|
93
|
+
if (s <= 0 || !Number.isFinite(s)) {
|
|
94
|
+
console.warn(`[twNumberInput] step=${s} is invalid. Using 1 instead.`);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
// ── ControlValueAccessor ──
|
|
99
|
+
/** @internal */
|
|
100
|
+
writeValue(value) {
|
|
101
|
+
if (value == null || Number.isNaN(value)) {
|
|
102
|
+
this._value.set(null);
|
|
103
|
+
this.displayText.set('');
|
|
104
|
+
this._lastValue = null;
|
|
105
|
+
this._lastDisplay = '';
|
|
106
|
+
if (!this._focused)
|
|
107
|
+
this.el.value = '';
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Normalize -0 → 0 (never store a negative zero); otherwise keep the value
|
|
111
|
+
// un-rounded — a programmatic write is authoritative and must not silently
|
|
112
|
+
// diverge from the parent FormControl.
|
|
113
|
+
const n = Object.is(value, -0) ? 0 : value;
|
|
114
|
+
const display = this.formatValue(n);
|
|
115
|
+
this._value.set(n);
|
|
116
|
+
this.displayText.set(display);
|
|
117
|
+
// Update the revert anchor even while focused, so an unparseable blur after
|
|
118
|
+
// a programmatic write snaps back to the form's value (not to '').
|
|
119
|
+
this._lastValue = n;
|
|
120
|
+
this._lastDisplay = display;
|
|
121
|
+
// Caret guard: never rewrite el.value while focused (clobbers the caret);
|
|
122
|
+
// defer the visible reformat to the next blur.
|
|
123
|
+
if (!this._focused)
|
|
124
|
+
this.el.value = display;
|
|
125
|
+
}
|
|
126
|
+
/** @internal */
|
|
127
|
+
registerOnChange(fn) {
|
|
128
|
+
this.onChange = fn;
|
|
129
|
+
}
|
|
130
|
+
/** @internal */
|
|
131
|
+
registerOnTouched(fn) {
|
|
132
|
+
this.onTouched = fn;
|
|
133
|
+
}
|
|
134
|
+
/** @internal */
|
|
135
|
+
setDisabledState(isDisabled) {
|
|
136
|
+
this.cvaDisabled.set(isDisabled);
|
|
137
|
+
}
|
|
138
|
+
// ── Public methods ──
|
|
139
|
+
/** Steps the value up: treats an empty field as `0`, adds `step`, clamps to `[min,max]`, rounds to the formatter's resolved precision, formats, and writes both the display and the model. Emits `valueChange`. No-op when the host input is disabled or readonly. Does not move focus. */
|
|
140
|
+
increment() {
|
|
141
|
+
this.stepBy(1);
|
|
142
|
+
}
|
|
143
|
+
/** Steps the value down: treats an empty field as `0`, subtracts `step`, clamps to `[min,max]`, rounds, formats, writes display and model. Emits `valueChange`. No-op when disabled or readonly. Does not move focus. */
|
|
144
|
+
decrement() {
|
|
145
|
+
this.stepBy(-1);
|
|
146
|
+
}
|
|
147
|
+
/** Moves focus to the underlying input element. */
|
|
148
|
+
focus(options) {
|
|
149
|
+
this.el.focus(options);
|
|
150
|
+
}
|
|
151
|
+
// ── Host handlers ──
|
|
152
|
+
/** @internal */
|
|
153
|
+
onInput() {
|
|
154
|
+
const raw = this.el.value;
|
|
155
|
+
this.displayText.set(raw);
|
|
156
|
+
const parsed = this.parseValue(raw);
|
|
157
|
+
this._value.set(parsed);
|
|
158
|
+
this.onChange(parsed);
|
|
159
|
+
this.valueChange.emit(parsed);
|
|
160
|
+
}
|
|
161
|
+
/** @internal */
|
|
162
|
+
onFocus() {
|
|
163
|
+
this._focused = true;
|
|
164
|
+
}
|
|
165
|
+
/** @internal */
|
|
166
|
+
onBlur() {
|
|
167
|
+
this._focused = false;
|
|
168
|
+
this.commitFromElement();
|
|
169
|
+
this.onTouched();
|
|
170
|
+
}
|
|
171
|
+
/** @internal */
|
|
172
|
+
onKeydown(event) {
|
|
173
|
+
if (this.disabled() || this.el.readOnly)
|
|
174
|
+
return;
|
|
175
|
+
switch (event.key) {
|
|
176
|
+
case 'ArrowUp':
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
this.increment();
|
|
179
|
+
break;
|
|
180
|
+
case 'ArrowDown':
|
|
181
|
+
event.preventDefault();
|
|
182
|
+
this.decrement();
|
|
183
|
+
break;
|
|
184
|
+
case 'Home':
|
|
185
|
+
if (this.min() !== undefined) {
|
|
186
|
+
event.preventDefault();
|
|
187
|
+
this.commitNumber(this.min());
|
|
188
|
+
}
|
|
189
|
+
break;
|
|
190
|
+
case 'End':
|
|
191
|
+
if (this.max() !== undefined) {
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
this.commitNumber(this.max());
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
case 'Enter':
|
|
197
|
+
// Do NOT preventDefault — preserve native form submission. Commit in place.
|
|
198
|
+
this.commitFromElement();
|
|
199
|
+
break;
|
|
200
|
+
// All other keys (PageUp/Down, text entry, caret nav, Backspace) → native.
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// ── Internal commit / step logic ──
|
|
204
|
+
stepBy(direction) {
|
|
205
|
+
if (this.disabled() || this.el.readOnly)
|
|
206
|
+
return;
|
|
207
|
+
// Treat an empty field as 0, apply ±step, then clamp — matches native
|
|
208
|
+
// <input type="number">.
|
|
209
|
+
const current = this.parseValue(this.el.value) ?? 0;
|
|
210
|
+
this.commitNumber(current + direction * this.resolvedStep());
|
|
211
|
+
}
|
|
212
|
+
commitFromElement() {
|
|
213
|
+
const raw = this.el.value;
|
|
214
|
+
if (raw.trim() === '') {
|
|
215
|
+
this.commitEmpty();
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const parsed = this.parseValue(raw);
|
|
219
|
+
if (parsed === null) {
|
|
220
|
+
// Non-empty unparseable → snap back to the last committed value.
|
|
221
|
+
this.revertToLastCommitted();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this.commitNumber(parsed);
|
|
225
|
+
}
|
|
226
|
+
commitNumber(n) {
|
|
227
|
+
const rounded = this.roundToPrecision(this.clamp(n));
|
|
228
|
+
const normalized = Object.is(rounded, -0) ? 0 : rounded;
|
|
229
|
+
const display = this.formatValue(normalized);
|
|
230
|
+
const changed = !Object.is(this._value(), normalized);
|
|
231
|
+
this._value.set(normalized);
|
|
232
|
+
this.displayText.set(display);
|
|
233
|
+
this._lastValue = normalized;
|
|
234
|
+
this._lastDisplay = display;
|
|
235
|
+
this.el.value = display;
|
|
236
|
+
if (changed) {
|
|
237
|
+
this.onChange(normalized);
|
|
238
|
+
this.valueChange.emit(normalized);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
commitEmpty() {
|
|
242
|
+
const changed = this._value() !== null;
|
|
243
|
+
this._value.set(null);
|
|
244
|
+
this.displayText.set('');
|
|
245
|
+
this._lastValue = null;
|
|
246
|
+
this._lastDisplay = '';
|
|
247
|
+
this.el.value = '';
|
|
248
|
+
if (changed) {
|
|
249
|
+
this.onChange(null);
|
|
250
|
+
this.valueChange.emit(null);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
revertToLastCommitted() {
|
|
254
|
+
const changed = this._value() !== this._lastValue;
|
|
255
|
+
this._value.set(this._lastValue);
|
|
256
|
+
this.displayText.set(this._lastDisplay);
|
|
257
|
+
this.el.value = this._lastDisplay;
|
|
258
|
+
if (changed) {
|
|
259
|
+
this.onChange(this._lastValue);
|
|
260
|
+
this.valueChange.emit(this._lastValue);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ── Inline numeric helpers ──
|
|
264
|
+
resolvedStep() {
|
|
265
|
+
const s = this.step();
|
|
266
|
+
return s > 0 && Number.isFinite(s) ? s : 1;
|
|
267
|
+
}
|
|
268
|
+
clamp(n) {
|
|
269
|
+
const min = this.min();
|
|
270
|
+
const max = this.max();
|
|
271
|
+
let r = n;
|
|
272
|
+
if (min !== undefined && r < min)
|
|
273
|
+
r = min;
|
|
274
|
+
if (max !== undefined && r > max)
|
|
275
|
+
r = max;
|
|
276
|
+
return r;
|
|
277
|
+
}
|
|
278
|
+
roundToPrecision(n) {
|
|
279
|
+
if (!Number.isFinite(n))
|
|
280
|
+
return n;
|
|
281
|
+
const digits = this.resolvedMaxFractionDigits();
|
|
282
|
+
if (digits === undefined)
|
|
283
|
+
return n;
|
|
284
|
+
const factor = 10 ** digits;
|
|
285
|
+
return Math.round(n * factor) / factor;
|
|
286
|
+
}
|
|
287
|
+
resolvedMaxFractionDigits() {
|
|
288
|
+
try {
|
|
289
|
+
return new Intl.NumberFormat(this.locale(), this.format()).resolvedOptions().maximumFractionDigits;
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
formatValue(n) {
|
|
296
|
+
const normalized = Object.is(n, -0) ? 0 : n;
|
|
297
|
+
try {
|
|
298
|
+
return new Intl.NumberFormat(this.locale(), this.format()).format(normalized);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return String(normalized);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Parse a raw string to `number | null` in a load-bearing order: the `e`/`E`
|
|
306
|
+
* rejection MUST precede stripping (otherwise `'1e3'` would survive as `'13'`).
|
|
307
|
+
*/
|
|
308
|
+
parseValue(raw) {
|
|
309
|
+
const trimmed = raw.trim();
|
|
310
|
+
if (trimmed === '')
|
|
311
|
+
return null;
|
|
312
|
+
// 2. Reject exponent notation before any stripping.
|
|
313
|
+
if (/[eE]/.test(trimmed))
|
|
314
|
+
return null;
|
|
315
|
+
const { group, decimal } = this.separators();
|
|
316
|
+
// 3. Strip all Unicode whitespace (covers NBSP / narrow-NBSP group separators
|
|
317
|
+
// and a user-typed ASCII space alike).
|
|
318
|
+
let s = trimmed.replace(/\s/gu, '');
|
|
319
|
+
// 4. Strip group separators (redundant-but-harmless for space-grouped locales).
|
|
320
|
+
if (group)
|
|
321
|
+
s = s.split(group).join('');
|
|
322
|
+
// 5. Normalize the locale decimal separator to '.'.
|
|
323
|
+
if (decimal && decimal !== '.')
|
|
324
|
+
s = s.split(decimal).join('.');
|
|
325
|
+
// 6. Strip remaining non-numeric chars (currency symbols, '%', leading '+', …)
|
|
326
|
+
// and collapse to a single leading '-'.
|
|
327
|
+
s = s.replace(/[^\d.-]/g, '').replace(/(?!^)-/g, '');
|
|
328
|
+
// 7. Digit-presence + finiteness guard (prevents Number('') === 0 committing 0).
|
|
329
|
+
if (!/\d/.test(s))
|
|
330
|
+
return null;
|
|
331
|
+
const n = Number(s);
|
|
332
|
+
if (!Number.isFinite(n))
|
|
333
|
+
return null;
|
|
334
|
+
// Normalize -0 → 0 so it never enters the model from any path.
|
|
335
|
+
return Object.is(n, -0) ? 0 : n;
|
|
336
|
+
}
|
|
337
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NumberInputDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
338
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.7", type: NumberInputDirective, isStandalone: true, selector: "input[twNumberInput]", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { attributes: { "role": "spinbutton" }, listeners: { "input": "onInput()", "keydown": "onKeydown($event)", "focus": "onFocus()", "blur": "onBlur()" }, properties: { "attr.inputmode": "inputMode()", "attr.aria-valuemin": "min() ?? null", "attr.aria-valuemax": "max() ?? null", "attr.aria-valuenow": "value() ?? null", "attr.aria-valuetext": "displayText() || \"Empty\"" } }, providers: [
|
|
339
|
+
// Custom CVA — transports a real `number | null`, not a string. Registered
|
|
340
|
+
// statically because this directive injects NO `NgControl` (matcher /
|
|
341
|
+
// error-state stay on the sibling InputDirective), so the circular-DI trap
|
|
342
|
+
// that forces runtime registration does not apply. Follows the
|
|
343
|
+
// segmented-control / calendar precedent. A custom accessor takes precedence
|
|
344
|
+
// over the built-in DefaultValueAccessor, so there is no "more than one
|
|
345
|
+
// value accessor" error.
|
|
346
|
+
{
|
|
347
|
+
provide: NG_VALUE_ACCESSOR,
|
|
348
|
+
useExisting: forwardRef(() => NumberInputDirective),
|
|
349
|
+
multi: true,
|
|
350
|
+
},
|
|
351
|
+
// Lets the sibling InputDirective read the formatted *display text* (not the
|
|
352
|
+
// numeric model) for its empty / float-label state. The factory exposes the
|
|
353
|
+
// `displayText` signal so InputDirective's `isSignal(value)` effect stays
|
|
354
|
+
// self-healing. Must NOT be `useExisting` — that would expose the numeric
|
|
355
|
+
// `value` signal and report the field empty during intermediate typing.
|
|
356
|
+
{
|
|
357
|
+
provide: TW_INPUT_VALUE_ACCESSOR,
|
|
358
|
+
useFactory: (dir) => ({ value: dir.displayText }),
|
|
359
|
+
deps: [forwardRef(() => NumberInputDirective)],
|
|
360
|
+
},
|
|
361
|
+
], exportAs: ["twNumberInput"], ngImport: i0 });
|
|
362
|
+
}
|
|
363
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NumberInputDirective, decorators: [{
|
|
364
|
+
type: Directive,
|
|
365
|
+
args: [{
|
|
366
|
+
selector: 'input[twNumberInput]',
|
|
367
|
+
exportAs: 'twNumberInput',
|
|
368
|
+
providers: [
|
|
369
|
+
// Custom CVA — transports a real `number | null`, not a string. Registered
|
|
370
|
+
// statically because this directive injects NO `NgControl` (matcher /
|
|
371
|
+
// error-state stay on the sibling InputDirective), so the circular-DI trap
|
|
372
|
+
// that forces runtime registration does not apply. Follows the
|
|
373
|
+
// segmented-control / calendar precedent. A custom accessor takes precedence
|
|
374
|
+
// over the built-in DefaultValueAccessor, so there is no "more than one
|
|
375
|
+
// value accessor" error.
|
|
376
|
+
{
|
|
377
|
+
provide: NG_VALUE_ACCESSOR,
|
|
378
|
+
useExisting: forwardRef(() => NumberInputDirective),
|
|
379
|
+
multi: true,
|
|
380
|
+
},
|
|
381
|
+
// Lets the sibling InputDirective read the formatted *display text* (not the
|
|
382
|
+
// numeric model) for its empty / float-label state. The factory exposes the
|
|
383
|
+
// `displayText` signal so InputDirective's `isSignal(value)` effect stays
|
|
384
|
+
// self-healing. Must NOT be `useExisting` — that would expose the numeric
|
|
385
|
+
// `value` signal and report the field empty during intermediate typing.
|
|
386
|
+
{
|
|
387
|
+
provide: TW_INPUT_VALUE_ACCESSOR,
|
|
388
|
+
useFactory: (dir) => ({ value: dir.displayText }),
|
|
389
|
+
deps: [forwardRef(() => NumberInputDirective)],
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
host: {
|
|
393
|
+
'role': 'spinbutton',
|
|
394
|
+
'[attr.inputmode]': 'inputMode()',
|
|
395
|
+
'[attr.aria-valuemin]': 'min() ?? null',
|
|
396
|
+
'[attr.aria-valuemax]': 'max() ?? null',
|
|
397
|
+
'[attr.aria-valuenow]': 'value() ?? null',
|
|
398
|
+
'[attr.aria-valuetext]': 'displayText() || "Empty"',
|
|
399
|
+
'(input)': 'onInput()',
|
|
400
|
+
'(keydown)': 'onKeydown($event)',
|
|
401
|
+
'(focus)': 'onFocus()',
|
|
402
|
+
'(blur)': 'onBlur()',
|
|
403
|
+
},
|
|
404
|
+
}]
|
|
405
|
+
}], ctorParameters: () => [], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
406
|
+
|
|
407
|
+
const numberStepper = tv({
|
|
408
|
+
slots: {
|
|
409
|
+
// The column stretches to the field's height (host is `flex self-stretch`)
|
|
410
|
+
// and the two buttons split it via `flex-1`.
|
|
411
|
+
group: 'flex flex-col self-stretch',
|
|
412
|
+
// `flex-1` gives each button HALF the field height. Codified departure
|
|
413
|
+
// from CLAUDE.md "Square interactive targets" (size-6/7/8): stacking two
|
|
414
|
+
// square buttons made the column 2× a button tall — far taller than the
|
|
415
|
+
// text line — which is exactly what bloated the field's height. So the
|
|
416
|
+
// buttons carry a fixed WIDTH only; the chevron glyph's min-content
|
|
417
|
+
// (size-3 = 12px) is the natural floor that keeps the column compact.
|
|
418
|
+
//
|
|
419
|
+
// WCAG 2.5.8 (target-size, 24px — which slider.ts / badge.ts codify):
|
|
420
|
+
// two stacked buttons cannot each be ≥24px tall inside a ~38–42px field
|
|
421
|
+
// without re-bloating it, so each spinner ends up < 24px tall. The
|
|
422
|
+
// equivalent-control exception covers most cases — the value is reachable
|
|
423
|
+
// by typing into the ≥24px spinbutton input and, with a keyboard, via ↑/↓.
|
|
424
|
+
// Known gap: a negative-allowed field on touch — iOS `inputmode`
|
|
425
|
+
// numeric/decimal exposes no minus key and there are no hardware arrows,
|
|
426
|
+
// so the sub-24px decrement button becomes the only path to a negative
|
|
427
|
+
// value; a consumer needing negatives on touch must provide an alternative.
|
|
428
|
+
// Matches the sub-24px stacked-spinner convention of native/Mantine/Chakra.
|
|
429
|
+
button: 'flex-1 inline-flex items-center justify-center text-fg-muted hover:text-fg hover:bg-surface-muted rounded-md transition-colors duration-normal motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500 disabled:opacity-40 disabled:pointer-events-none',
|
|
430
|
+
icon: '',
|
|
431
|
+
},
|
|
432
|
+
variants: {
|
|
433
|
+
// Button WIDTH (the height comes from `flex-1`); glyph sub-scale for the
|
|
434
|
+
// chevrons (per CLAUDE.md "Icon Sizing").
|
|
435
|
+
size: {
|
|
436
|
+
xs: { button: 'w-6', icon: 'size-3' },
|
|
437
|
+
sm: { button: 'w-7', icon: 'size-3' },
|
|
438
|
+
md: { button: 'w-7', icon: 'size-3' },
|
|
439
|
+
lg: { button: 'w-8', icon: 'size-4' },
|
|
440
|
+
xl: { button: 'w-8', icon: 'size-4' },
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
defaultVariants: { size: 'md' },
|
|
444
|
+
}, { twMerge: true });
|
|
445
|
+
/**
|
|
446
|
+
* The visible up/down spinner column for a {@link NumberInputDirective}. A
|
|
447
|
+
* directive cannot emit sibling DOM, so the spinner buttons live in this tiny
|
|
448
|
+
* companion. Bind it to the directive via a template ref and drop it into a
|
|
449
|
+
* `<tw-form-field>`'s `[twSuffix]` slot (or a standalone flex row):
|
|
450
|
+
*
|
|
451
|
+
* ```html
|
|
452
|
+
* <input twInput twNumberInput #qty="twNumberInput" [formControl]="ctrl" />
|
|
453
|
+
* <tw-number-stepper twSuffix [for]="qty" />
|
|
454
|
+
* ```
|
|
455
|
+
*
|
|
456
|
+
* The buttons are kept out of the tab order (`tabindex="-1"`) — the spinbutton
|
|
457
|
+
* input owns value + keyboard semantics — and refocus the input after stepping.
|
|
458
|
+
*/
|
|
459
|
+
class NumberStepperComponent {
|
|
460
|
+
/** The number-input directive instance this stepper controls. Bind to a template ref, e.g. `[for]="qty"` with `#qty="twNumberInput"`. When omitted, the buttons render but do nothing (no-op) and disable. */
|
|
461
|
+
for = input(undefined, /* @ts-ignore */
|
|
462
|
+
...(ngDevMode ? [{ debugName: "for" }] : /* istanbul ignore next */ []));
|
|
463
|
+
/** Button + glyph density. Match the field's `size` for visual alignment. Defaults to `'md'`. */
|
|
464
|
+
size = input('md', /* @ts-ignore */
|
|
465
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
466
|
+
variant = computed(() => numberStepper({ size: this.size() }), /* @ts-ignore */
|
|
467
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
468
|
+
/** @internal */
|
|
469
|
+
groupClasses = computed(() => this.variant().group(), /* @ts-ignore */
|
|
470
|
+
...(ngDevMode ? [{ debugName: "groupClasses" }] : /* istanbul ignore next */ []));
|
|
471
|
+
/** @internal */
|
|
472
|
+
buttonClasses = computed(() => this.variant().button(), /* @ts-ignore */
|
|
473
|
+
...(ngDevMode ? [{ debugName: "buttonClasses" }] : /* istanbul ignore next */ []));
|
|
474
|
+
/** @internal */
|
|
475
|
+
iconClasses = computed(() => this.variant().icon(), /* @ts-ignore */
|
|
476
|
+
...(ngDevMode ? [{ debugName: "iconClasses" }] : /* istanbul ignore next */ []));
|
|
477
|
+
/** @internal Disabled when there is no target, or the target input is disabled / readonly. */
|
|
478
|
+
isDisabled = computed(() => {
|
|
479
|
+
const d = this.for();
|
|
480
|
+
return !d || d.disabled() || d.readonly();
|
|
481
|
+
}, /* @ts-ignore */
|
|
482
|
+
...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
483
|
+
/** @internal Step the bound directive, then refocus the input (the refocus lives here, not in `increment`/`decrement`). */
|
|
484
|
+
onStep(direction) {
|
|
485
|
+
const dir = this.for();
|
|
486
|
+
if (!dir)
|
|
487
|
+
return;
|
|
488
|
+
if (direction === 1) {
|
|
489
|
+
dir.increment();
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
dir.decrement();
|
|
493
|
+
}
|
|
494
|
+
dir.focus();
|
|
495
|
+
}
|
|
496
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NumberStepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
497
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: NumberStepperComponent, isStandalone: true, selector: "tw-number-stepper", inputs: { for: { classPropertyName: "for", publicName: "for", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "flex self-stretch" }, exportAs: ["twNumberStepper"], ngImport: i0, template: `
|
|
498
|
+
<div [class]="groupClasses()">
|
|
499
|
+
<button
|
|
500
|
+
type="button"
|
|
501
|
+
tabindex="-1"
|
|
502
|
+
aria-label="Increase"
|
|
503
|
+
[class]="buttonClasses()"
|
|
504
|
+
[disabled]="isDisabled()"
|
|
505
|
+
(mousedown)="$event.preventDefault()"
|
|
506
|
+
(click)="onStep(1)"
|
|
507
|
+
>
|
|
508
|
+
<ng-content select="[slot=up]">
|
|
509
|
+
<svg
|
|
510
|
+
viewBox="0 0 20 20"
|
|
511
|
+
fill="currentColor"
|
|
512
|
+
aria-hidden="true"
|
|
513
|
+
[class]="iconClasses()"
|
|
514
|
+
>
|
|
515
|
+
<path
|
|
516
|
+
fill-rule="evenodd"
|
|
517
|
+
d="M14.77 12.79a.75.75 0 0 1-1.06-.02L10 8.94l-3.71 3.83a.75.75 0 1 1-1.08-1.04l4.25-4.39a.75.75 0 0 1 1.08 0l4.25 4.39a.75.75 0 0 1-.02 1.06Z"
|
|
518
|
+
clip-rule="evenodd"
|
|
519
|
+
/>
|
|
520
|
+
</svg>
|
|
521
|
+
</ng-content>
|
|
522
|
+
</button>
|
|
523
|
+
<button
|
|
524
|
+
type="button"
|
|
525
|
+
tabindex="-1"
|
|
526
|
+
aria-label="Decrease"
|
|
527
|
+
[class]="buttonClasses()"
|
|
528
|
+
[disabled]="isDisabled()"
|
|
529
|
+
(mousedown)="$event.preventDefault()"
|
|
530
|
+
(click)="onStep(-1)"
|
|
531
|
+
>
|
|
532
|
+
<ng-content select="[slot=down]">
|
|
533
|
+
<svg
|
|
534
|
+
viewBox="0 0 20 20"
|
|
535
|
+
fill="currentColor"
|
|
536
|
+
aria-hidden="true"
|
|
537
|
+
[class]="iconClasses()"
|
|
538
|
+
>
|
|
539
|
+
<path
|
|
540
|
+
fill-rule="evenodd"
|
|
541
|
+
d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.06l3.71-3.83a.75.75 0 1 1 1.08 1.04l-4.25 4.39a.75.75 0 0 1-1.08 0L5.21 8.27a.75.75 0 0 1 .02-1.06Z"
|
|
542
|
+
clip-rule="evenodd"
|
|
543
|
+
/>
|
|
544
|
+
</svg>
|
|
545
|
+
</ng-content>
|
|
546
|
+
</button>
|
|
547
|
+
</div>
|
|
548
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
549
|
+
}
|
|
550
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: NumberStepperComponent, decorators: [{
|
|
551
|
+
type: Component,
|
|
552
|
+
args: [{
|
|
553
|
+
selector: 'tw-number-stepper',
|
|
554
|
+
exportAs: 'twNumberStepper',
|
|
555
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
556
|
+
// `flex` makes the inner group fill the host; `self-stretch` makes the host
|
|
557
|
+
// fill the field height — overriding `items-center` in a `<tw-form-field>`
|
|
558
|
+
// `[twSuffix]` slot, and matching `items-stretch` in a standalone flex row.
|
|
559
|
+
host: {
|
|
560
|
+
'class': 'flex self-stretch',
|
|
561
|
+
},
|
|
562
|
+
template: `
|
|
563
|
+
<div [class]="groupClasses()">
|
|
564
|
+
<button
|
|
565
|
+
type="button"
|
|
566
|
+
tabindex="-1"
|
|
567
|
+
aria-label="Increase"
|
|
568
|
+
[class]="buttonClasses()"
|
|
569
|
+
[disabled]="isDisabled()"
|
|
570
|
+
(mousedown)="$event.preventDefault()"
|
|
571
|
+
(click)="onStep(1)"
|
|
572
|
+
>
|
|
573
|
+
<ng-content select="[slot=up]">
|
|
574
|
+
<svg
|
|
575
|
+
viewBox="0 0 20 20"
|
|
576
|
+
fill="currentColor"
|
|
577
|
+
aria-hidden="true"
|
|
578
|
+
[class]="iconClasses()"
|
|
579
|
+
>
|
|
580
|
+
<path
|
|
581
|
+
fill-rule="evenodd"
|
|
582
|
+
d="M14.77 12.79a.75.75 0 0 1-1.06-.02L10 8.94l-3.71 3.83a.75.75 0 1 1-1.08-1.04l4.25-4.39a.75.75 0 0 1 1.08 0l4.25 4.39a.75.75 0 0 1-.02 1.06Z"
|
|
583
|
+
clip-rule="evenodd"
|
|
584
|
+
/>
|
|
585
|
+
</svg>
|
|
586
|
+
</ng-content>
|
|
587
|
+
</button>
|
|
588
|
+
<button
|
|
589
|
+
type="button"
|
|
590
|
+
tabindex="-1"
|
|
591
|
+
aria-label="Decrease"
|
|
592
|
+
[class]="buttonClasses()"
|
|
593
|
+
[disabled]="isDisabled()"
|
|
594
|
+
(mousedown)="$event.preventDefault()"
|
|
595
|
+
(click)="onStep(-1)"
|
|
596
|
+
>
|
|
597
|
+
<ng-content select="[slot=down]">
|
|
598
|
+
<svg
|
|
599
|
+
viewBox="0 0 20 20"
|
|
600
|
+
fill="currentColor"
|
|
601
|
+
aria-hidden="true"
|
|
602
|
+
[class]="iconClasses()"
|
|
603
|
+
>
|
|
604
|
+
<path
|
|
605
|
+
fill-rule="evenodd"
|
|
606
|
+
d="M5.23 7.21a.75.75 0 0 1 1.06.02L10 11.06l3.71-3.83a.75.75 0 1 1 1.08 1.04l-4.25 4.39a.75.75 0 0 1-1.08 0L5.21 8.27a.75.75 0 0 1 .02-1.06Z"
|
|
607
|
+
clip-rule="evenodd"
|
|
608
|
+
/>
|
|
609
|
+
</svg>
|
|
610
|
+
</ng-content>
|
|
611
|
+
</button>
|
|
612
|
+
</div>
|
|
613
|
+
`,
|
|
614
|
+
}]
|
|
615
|
+
}], propDecorators: { for: [{ type: i0.Input, args: [{ isSignal: true, alias: "for", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Generated bundle index. Do not edit.
|
|
619
|
+
*/
|
|
620
|
+
|
|
621
|
+
export { NumberInputDirective, NumberStepperComponent };
|
|
622
|
+
//# sourceMappingURL=cdevhub-ngx-tw-number-input.mjs.map
|