@bitforgehq/ui 0.3.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/CHANGELOG.md +3665 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/ai/MANIFEST.index.json +383 -0
- package/ai/MANIFEST.json +7528 -0
- package/ai/REFERENCE.index.json +1461 -0
- package/ai/REFERENCE.json +7213 -0
- package/ai/UI_GUIDE.md +1159 -0
- package/ai/components/action-bar.md +209 -0
- package/ai/components/action-sheet.md +526 -0
- package/ai/components/alert-dialog.md +509 -0
- package/ai/components/alert.md +376 -0
- package/ai/components/avatar.md +146 -0
- package/ai/components/back-button.md +286 -0
- package/ai/components/badge.md +171 -0
- package/ai/components/button.md +431 -0
- package/ai/components/card.md +303 -0
- package/ai/components/checkbox.md +418 -0
- package/ai/components/chip-group.md +205 -0
- package/ai/components/chip.md +300 -0
- package/ai/components/content.md +474 -0
- package/ai/components/desktop-toast.md +420 -0
- package/ai/components/dialog.md +962 -0
- package/ai/components/empty-state.md +146 -0
- package/ai/components/field.md +319 -0
- package/ai/components/header.md +809 -0
- package/ai/components/icon-button.md +221 -0
- package/ai/components/icon.md +176 -0
- package/ai/components/input.md +455 -0
- package/ai/components/list-item.md +326 -0
- package/ai/components/list.md +178 -0
- package/ai/components/menu-button.md +377 -0
- package/ai/components/menu.md +661 -0
- package/ai/components/modal.md +613 -0
- package/ai/components/number-input.md +423 -0
- package/ai/components/page.md +299 -0
- package/ai/components/progress.md +215 -0
- package/ai/components/radio-group.md +432 -0
- package/ai/components/refresher.md +377 -0
- package/ai/components/segmented.md +517 -0
- package/ai/components/select.md +510 -0
- package/ai/components/sheet.md +845 -0
- package/ai/components/spinner.md +171 -0
- package/ai/components/stack-outlet.md +661 -0
- package/ai/components/tab-bar.md +427 -0
- package/ai/components/tab.md +295 -0
- package/ai/components/tabs.md +408 -0
- package/ai/components/textarea.md +371 -0
- package/ai/components/toast.md +522 -0
- package/ai/components/toggle.md +387 -0
- package/ai/reference/bf-action-directive.md +21 -0
- package/ai/reference/bf-action-sheet-action-role.md +27 -0
- package/ai/reference/bf-action-sheet-action.md +28 -0
- package/ai/reference/bf-action-sheet-options.md +28 -0
- package/ai/reference/bf-action-sheet-service.md +33 -0
- package/ai/reference/bf-alert-dialog-button-role.md +27 -0
- package/ai/reference/bf-alert-dialog-button.md +27 -0
- package/ai/reference/bf-alert-dialog-options.md +29 -0
- package/ai/reference/bf-alert-dialog-service.md +34 -0
- package/ai/reference/bf-alert-variant.md +28 -0
- package/ai/reference/bf-autocapitalize.md +30 -0
- package/ai/reference/bf-avatar-size.md +27 -0
- package/ai/reference/bf-avatar-variant.md +28 -0
- package/ai/reference/bf-back-at-root.md +28 -0
- package/ai/reference/bf-back-button-service.md +40 -0
- package/ai/reference/bf-badge-variant.md +29 -0
- package/ai/reference/bf-button-size.md +29 -0
- package/ai/reference/bf-button-type.md +28 -0
- package/ai/reference/bf-button-variant.md +29 -0
- package/ai/reference/bf-card-padding.md +26 -0
- package/ai/reference/bf-card-variant.md +28 -0
- package/ai/reference/bf-chip-group-mode.md +27 -0
- package/ai/reference/bf-chip-variant.md +26 -0
- package/ai/reference/bf-compare-with.md +24 -0
- package/ai/reference/bf-component-overlay-ref.md +33 -0
- package/ai/reference/bf-confirm-button-role.md +26 -0
- package/ai/reference/bf-confirm-button.md +27 -0
- package/ai/reference/bf-confirm-options.md +31 -0
- package/ai/reference/bf-confirm-size.md +26 -0
- package/ai/reference/bf-content-scroll-event.md +21 -0
- package/ai/reference/bf-defaults-options.md +32 -0
- package/ai/reference/bf-defaults.md +28 -0
- package/ai/reference/bf-desktop-toast-config.md +28 -0
- package/ai/reference/bf-desktop-toast-harness.md +33 -0
- package/ai/reference/bf-desktop-toast-options.md +28 -0
- package/ai/reference/bf-desktop-toast-position.md +28 -0
- package/ai/reference/bf-desktop-toast-service.md +42 -0
- package/ai/reference/bf-dialog-config.md +44 -0
- package/ai/reference/bf-dialog-harness.md +52 -0
- package/ai/reference/bf-dialog-ref.md +44 -0
- package/ai/reference/bf-dialog-role.md +26 -0
- package/ai/reference/bf-dialog-service.md +55 -0
- package/ai/reference/bf-dialog-size.md +31 -0
- package/ai/reference/bf-dialog-template-context.md +32 -0
- package/ai/reference/bf-empty-state-heading-level.md +26 -0
- package/ai/reference/bf-enter-key-hint.md +31 -0
- package/ai/reference/bf-field-control-class.md +32 -0
- package/ai/reference/bf-field-control-state.md +28 -0
- package/ai/reference/bf-field-size.md +29 -0
- package/ai/reference/bf-haptics-impact-style.md +27 -0
- package/ai/reference/bf-haptics-notification-type.md +27 -0
- package/ai/reference/bf-haptics-service.md +41 -0
- package/ai/reference/bf-harness-filters.md +29 -0
- package/ai/reference/bf-horizontal-scroll-directive.md +129 -0
- package/ai/reference/bf-icon-button-variant.md +28 -0
- package/ai/reference/bf-icon-size.md +27 -0
- package/ai/reference/bf-input-autocapitalize.md +36 -0
- package/ai/reference/bf-input-enter-key-hint.md +38 -0
- package/ai/reference/bf-input-input-mode.md +38 -0
- package/ai/reference/bf-input-mode.md +32 -0
- package/ai/reference/bf-input-type.md +35 -0
- package/ai/reference/bf-jsdom-shim-options.md +22 -0
- package/ai/reference/bf-keyboard-assist-behavior.md +26 -0
- package/ai/reference/bf-keyboard-assist-directive.md +185 -0
- package/ai/reference/bf-keyboard-phase.md +30 -0
- package/ai/reference/bf-keyboard-service.md +50 -0
- package/ai/reference/bf-labels-overrides.md +26 -0
- package/ai/reference/bf-labels.md +44 -0
- package/ai/reference/bf-menu-button-harness.md +40 -0
- package/ai/reference/bf-menu-button-variant.md +27 -0
- package/ai/reference/bf-menu-divider-directive.md +13 -0
- package/ai/reference/bf-menu-group-directive.md +14 -0
- package/ai/reference/bf-menu-harness.md +55 -0
- package/ai/reference/bf-menu-item-checkbox-directive.md +31 -0
- package/ai/reference/bf-menu-item-directive.md +31 -0
- package/ai/reference/bf-menu-item-harness.md +52 -0
- package/ai/reference/bf-menu-item-radio-directive.md +29 -0
- package/ai/reference/bf-menu-placement.md +29 -0
- package/ai/reference/bf-menu-trigger-directive.md +46 -0
- package/ai/reference/bf-modal-options.md +30 -0
- package/ai/reference/bf-modal-presentation.md +26 -0
- package/ai/reference/bf-modal-service.md +38 -0
- package/ai/reference/bf-nav-controller.md +59 -0
- package/ai/reference/bf-nav-direction.md +23 -0
- package/ai/reference/bf-nav-target.md +21 -0
- package/ai/reference/bf-number-input-mode.md +28 -0
- package/ai/reference/bf-number-input-step.md +25 -0
- package/ai/reference/bf-overlay-auto-focus.md +29 -0
- package/ai/reference/bf-overlay-controller.md +22 -0
- package/ai/reference/bf-overlay-host.md +36 -0
- package/ai/reference/bf-overlay-options.md +53 -0
- package/ai/reference/bf-overlay-position.md +30 -0
- package/ai/reference/bf-overlay-ref.md +53 -0
- package/ai/reference/bf-overlay-role.md +27 -0
- package/ai/reference/bf-overlay-service.md +53 -0
- package/ai/reference/bf-overlay-stack-options.md +24 -0
- package/ai/reference/bf-overlay-stack.md +81 -0
- package/ai/reference/bf-overlay-template-context.md +24 -0
- package/ai/reference/bf-overscroll-stretch-directive.md +163 -0
- package/ai/reference/bf-page-context.md +24 -0
- package/ai/reference/bf-platform-service.md +53 -0
- package/ai/reference/bf-platform.md +28 -0
- package/ai/reference/bf-progress-size.md +26 -0
- package/ai/reference/bf-progress-tone.md +28 -0
- package/ai/reference/bf-radio-group-harness.md +49 -0
- package/ai/reference/bf-radio-group-orientation.md +26 -0
- package/ai/reference/bf-radio-option.md +30 -0
- package/ai/reference/bf-refresher-refresh-event.md +22 -0
- package/ai/reference/bf-segment-option.md +29 -0
- package/ai/reference/bf-segmented-harness.md +48 -0
- package/ai/reference/bf-segmented-size.md +29 -0
- package/ai/reference/bf-select-harness.md +70 -0
- package/ai/reference/bf-select-option.md +29 -0
- package/ai/reference/bf-sheet-auto-focus.md +29 -0
- package/ai/reference/bf-sheet-options.md +32 -0
- package/ai/reference/bf-sheet-ref.md +45 -0
- package/ai/reference/bf-sheet-service.md +34 -0
- package/ai/reference/bf-shell-config-mode.md +28 -0
- package/ai/reference/bf-shell-config.md +38 -0
- package/ai/reference/bf-shell-dynamic-type-limit.md +21 -0
- package/ai/reference/bf-shell-dynamic-type.md +25 -0
- package/ai/reference/bf-shell-mode.md +28 -0
- package/ai/reference/bf-shell-testing-options.md +26 -0
- package/ai/reference/bf-size.md +45 -0
- package/ai/reference/bf-spinner-size.md +27 -0
- package/ai/reference/bf-stack-change.md +22 -0
- package/ai/reference/bf-stack-reuse-strategy.md +32 -0
- package/ai/reference/bf-stack-transition-event.md +26 -0
- package/ai/reference/bf-stack-transition.md +26 -0
- package/ai/reference/bf-stacked-overlay.md +23 -0
- package/ai/reference/bf-system-bars-style.md +38 -0
- package/ai/reference/bf-tab-bar-controller.md +51 -0
- package/ai/reference/bf-tab-bar-host.md +52 -0
- package/ai/reference/bf-tab-context.md +32 -0
- package/ai/reference/bf-tab-links.md +37 -0
- package/ai/reference/bf-testing-options.md +27 -0
- package/ai/reference/bf-text-scale-service.md +35 -0
- package/ai/reference/bf-theme.md +27 -0
- package/ai/reference/bf-toast-action.md +25 -0
- package/ai/reference/bf-toast-controller.md +17 -0
- package/ai/reference/bf-toast-dismiss-reason.md +29 -0
- package/ai/reference/bf-toast-options.md +29 -0
- package/ai/reference/bf-toast-position.md +26 -0
- package/ai/reference/bf-toast-ref.md +41 -0
- package/ai/reference/bf-toast-service.md +39 -0
- package/ai/reference/bf-toggle-label-position.md +26 -0
- package/ai/reference/bf-transition-kind.md +28 -0
- package/ai/reference/bf-validation-message.md +31 -0
- package/ai/reference/bf-validation-messages.md +33 -0
- package/ai/reference/bf-view-event.md +28 -0
- package/ai/reference/bf-view-lifecycle.md +41 -0
- package/ai/reference/bf-view-state.md +28 -0
- package/ai/reference/bf_default_labels.md +21 -0
- package/ai/reference/bf_default_values.md +21 -0
- package/ai/reference/bf_defaults.md +21 -0
- package/ai/reference/bf_desktop_toast_defaults.md +21 -0
- package/ai/reference/bf_dialog_data.md +17 -0
- package/ai/reference/bf_labels.md +21 -0
- package/ai/reference/bf_overlay_data.md +19 -0
- package/ai/reference/bf_overlay_host.md +21 -0
- package/ai/reference/bf_sheet_default_snap_points.md +17 -0
- package/ai/reference/bf_shell_config.md +21 -0
- package/ai/reference/bf_shell_defaults.md +21 -0
- package/ai/reference/bf_tab_bar.md +21 -0
- package/ai/reference/bf_tab_bar_controller.md +21 -0
- package/ai/reference/bf_tab_context.md +21 -0
- package/ai/reference/bf_tab_links.md +21 -0
- package/ai/reference/bf_theme_storage.md +19 -0
- package/ai/reference/create-transition.md +36 -0
- package/ai/reference/duration_fast.md +17 -0
- package/ai/reference/duration_md.md +17 -0
- package/ai/reference/duration_overlay.md +17 -0
- package/ai/reference/duration_push.md +17 -0
- package/ai/reference/fade-down.md +26 -0
- package/ai/reference/fade-in.md +25 -0
- package/ai/reference/fade-out.md +25 -0
- package/ai/reference/fade-up.md +26 -0
- package/ai/reference/inject-shell-mode.md +23 -0
- package/ai/reference/install-bf-jsdom-shims.md +31 -0
- package/ai/reference/ios_duration.md +17 -0
- package/ai/reference/ios_easing.md +17 -0
- package/ai/reference/is-text-entry.md +25 -0
- package/ai/reference/max_complete_ms.md +17 -0
- package/ai/reference/md-transition.md +31 -0
- package/ai/reference/md_duration.md +17 -0
- package/ai/reference/md_easing.md +17 -0
- package/ai/reference/overlay_duration.md +21 -0
- package/ai/reference/overlay_easing.md +19 -0
- package/ai/reference/overlay_reduced_duration.md +17 -0
- package/ai/reference/pop-frames-at.md +25 -0
- package/ai/reference/prefers-reduced-motion.md +25 -0
- package/ai/reference/provide-bf-defaults.md +33 -0
- package/ai/reference/provide-bf-desktop-toast.md +28 -0
- package/ai/reference/provide-bf-labels.md +38 -0
- package/ai/reference/provide-bf-shell-testing.md +34 -0
- package/ai/reference/provide-bf-shell.md +41 -0
- package/ai/reference/provide-bf-testing.md +33 -0
- package/ai/reference/reduced-transition.md +28 -0
- package/ai/reference/reduced_duration.md +17 -0
- package/ai/reference/resolve-shell-mode.md +33 -0
- package/ai/reference/scale-in.md +25 -0
- package/ai/reference/settle-duration.md +25 -0
- package/ai/reference/settle-pop.md +33 -0
- package/ai/reference/settle_easing.md +17 -0
- package/ai/reference/should-complete-pop.md +26 -0
- package/ai/reference/slide-down.md +26 -0
- package/ai/reference/slide-up.md +26 -0
- package/ai/reference/with-bf-shell-routing.md +19 -0
- package/bin/bf-ui.js +81 -0
- package/fesm2022/bitforgehq-ui-icons.mjs +230 -0
- package/fesm2022/bitforgehq-ui-icons.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs +5530 -0
- package/fesm2022/bitforgehq-ui-mobile.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs +680 -0
- package/fesm2022/bitforgehq-ui-number-input.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs +2675 -0
- package/fesm2022/bitforgehq-ui-overlay.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui-testing.mjs +402 -0
- package/fesm2022/bitforgehq-ui-testing.mjs.map +1 -0
- package/fesm2022/bitforgehq-ui.mjs +7765 -0
- package/fesm2022/bitforgehq-ui.mjs.map +1 -0
- package/llms-full.txt +25394 -0
- package/llms.txt +329 -0
- package/package.json +94 -0
- package/theme.css +253 -0
- package/types/bitforgehq-ui-icons.d.ts +50 -0
- package/types/bitforgehq-ui-mobile.d.ts +2914 -0
- package/types/bitforgehq-ui-number-input.d.ts +282 -0
- package/types/bitforgehq-ui-overlay.d.ts +1514 -0
- package/types/bitforgehq-ui-testing.d.ts +495 -0
- package/types/bitforgehq-ui.d.ts +3499 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
# bf-number-input
|
|
2
|
+
|
|
3
|
+
Number field that binds number or null, with native min, max and step
|
|
4
|
+
|
|
5
|
+
`BfNumberInputComponent` from `@bitforgehq/ui/number-input` · Forms · since 0.3.0
|
|
6
|
+
|
|
7
|
+
## Example
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { Component, computed, signal } from '@angular/core';
|
|
11
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The bf-number-input overview.
|
|
15
|
+
*
|
|
16
|
+
* Live demo of `bf-number-input`: a bound quantity with min, max and step and an echo, a price
|
|
17
|
+
* with a 0.05 step and a unit in the end slot, and a read-only field; then the sm, md and lg sizes
|
|
18
|
+
* in a row.
|
|
19
|
+
*/
|
|
20
|
+
@Component({
|
|
21
|
+
selector: 'app-example-number-input-overview',
|
|
22
|
+
imports: [BfNumberInputComponent],
|
|
23
|
+
template: `
|
|
24
|
+
<div class="flex flex-col gap-3">
|
|
25
|
+
<bf-number-input
|
|
26
|
+
label="Quantity"
|
|
27
|
+
helpText="1 to 99, in steps of 1."
|
|
28
|
+
class="max-w-60"
|
|
29
|
+
[min]="1"
|
|
30
|
+
[max]="99"
|
|
31
|
+
[step]="1"
|
|
32
|
+
[(value)]="quantity"
|
|
33
|
+
/>
|
|
34
|
+
<p data-status aria-live="polite" class="text-caption text-text-muted">{{ status() }}</p>
|
|
35
|
+
<bf-number-input
|
|
36
|
+
label="Hourly rate"
|
|
37
|
+
placeholder="0.00"
|
|
38
|
+
class="max-w-60"
|
|
39
|
+
[min]="0"
|
|
40
|
+
[step]="0.05"
|
|
41
|
+
[iconEnd]="true"
|
|
42
|
+
[(value)]="rate"
|
|
43
|
+
>
|
|
44
|
+
<span bfSlot="icon-end" class="text-caption text-text-muted">CHF</span>
|
|
45
|
+
</bf-number-input>
|
|
46
|
+
<bf-number-input label="Seats included" class="max-w-60" [value]="5" [readonly]="true" />
|
|
47
|
+
<div data-sizes class="flex flex-wrap items-center gap-3">
|
|
48
|
+
<bf-number-input
|
|
49
|
+
size="sm"
|
|
50
|
+
ariaLabel="Small number field"
|
|
51
|
+
placeholder="sm · 32 px"
|
|
52
|
+
class="w-40"
|
|
53
|
+
/>
|
|
54
|
+
<bf-number-input ariaLabel="Medium number field" placeholder="md · 40 px" class="w-40" />
|
|
55
|
+
<bf-number-input
|
|
56
|
+
size="lg"
|
|
57
|
+
ariaLabel="Large number field"
|
|
58
|
+
placeholder="lg · 48 px"
|
|
59
|
+
class="w-40"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
`,
|
|
64
|
+
})
|
|
65
|
+
export class NumberInputOverviewExampleComponent {
|
|
66
|
+
/** A number, `null` while the field is empty. */
|
|
67
|
+
readonly quantity = signal<number | null>(3);
|
|
68
|
+
readonly rate = signal<number | null>(null);
|
|
69
|
+
|
|
70
|
+
readonly status = computed(() => {
|
|
71
|
+
const quantity = this.quantity();
|
|
72
|
+
return quantity === null ? 'Quantity: empty (null)' : `Quantity: ${quantity} (a number)`;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Overview
|
|
78
|
+
|
|
79
|
+
Number field with the chrome of the text field that binds `number | null`: an empty field is
|
|
80
|
+
`null`, text the browser cannot read as a number is a `parse` error, and Signal Forms `min()` and
|
|
81
|
+
`max()` become the native `min` and `max`.
|
|
82
|
+
|
|
83
|
+
A number field with the look of `bf-input` that binds `number | null` instead of a string. It is a native `<input type="number">`, so the arrow keys and spin buttons step the value and `min`, `max` and `step` are native attributes. An empty field is `null`.
|
|
84
|
+
|
|
85
|
+
It has its own entry point, `@bitforgehq/ui/number-input` (`import { BfNumberInputComponent } from '@bitforgehq/ui/number-input'`), because it parses its text with Angular's `transformedValue()` and so loads Angular Forms (`@angular/forms`, a required peer of the package). The main `@bitforgehq/ui` entry loads no Angular Forms code, so an app without forms never ships it.
|
|
86
|
+
|
|
87
|
+
The decimal separator the field accepts follows the browser's own language, not the page's `lang` or Angular's `LOCALE_ID`, and differs between engines: under a German locale, WebKit reads `1,5` as `1.5` but treats a trailing `1.` as unreadable, while Chromium follows its UI language (with a German UI it read `1,5` as `15`). Test the locales your users run.
|
|
88
|
+
|
|
89
|
+
## When to use
|
|
90
|
+
|
|
91
|
+
Use `bf-number-input` for a quantity, a price, an age or another number you bind as `number | null`. Use [bf-input](https://bitforge-ui.web.app/components/input) instead for codes that only look numeric (postal codes, phone and card numbers), since a number field drops leading zeros.
|
|
92
|
+
|
|
93
|
+
## Basic usage
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { Component, signal } from '@angular/core';
|
|
97
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
98
|
+
|
|
99
|
+
/** A quantity from 1 to 99, bound as a number. */
|
|
100
|
+
@Component({
|
|
101
|
+
selector: 'app-example-number-input-basic',
|
|
102
|
+
imports: [BfNumberInputComponent],
|
|
103
|
+
template: `
|
|
104
|
+
<div class="flex flex-col gap-2">
|
|
105
|
+
<bf-number-input label="Quantity" [min]="1" [max]="99" [(value)]="quantity" />
|
|
106
|
+
<p class="text-caption text-text-muted">quantity: {{ quantity() ?? 'null' }}</p>
|
|
107
|
+
</div>
|
|
108
|
+
`,
|
|
109
|
+
})
|
|
110
|
+
export class NumberInputBasicExampleComponent {
|
|
111
|
+
readonly quantity = signal<number | null>(1);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Typing `42` stores the number `42`; clearing the field stores `null`, so type the bound signal or field `number | null` (a plain `number` field compiles, but receives `null` once cleared). Text the browser cannot read as a number (a lone `-`, `1e`) keeps the last number in the model and raises a `parse` error, "Enter a valid number", shown once the field is left, with or without a form. A form `reset()` or a new value replaces that text with the number.
|
|
116
|
+
|
|
117
|
+
`(changed)` emits the new value (`null` when cleared) right after a user edit that changes the
|
|
118
|
+
model; unreadable text changes nothing, so it does not emit.
|
|
119
|
+
|
|
120
|
+
`min`, `max` and `step` limit the arrow keys and spin buttons only. Typed values outside them are stored without an error: range errors come from Signal Forms `min()`/`max()` (or Reactive `Validators.min()`/`max()`), and a step mismatch is not reported.
|
|
121
|
+
|
|
122
|
+
## With Signal Forms
|
|
123
|
+
|
|
124
|
+
Bind the field with `[formField]`. Signal Forms `min()` and `max()` flow into the native `min` and `max` attributes, and their errors show the default text for their kind ("Enter 18 or more", "Enter 120 or less") unless the rule passes a `message`. A parse error is reported to the field as a `parse` error, so it blocks `submit()` too.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { Component, signal } from '@angular/core';
|
|
128
|
+
import { FormField, FormRoot, form, max, min, required } from '@angular/forms/signals';
|
|
129
|
+
import { BfButtonComponent } from '@bitforgehq/ui';
|
|
130
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* An age field on Signal Forms.
|
|
134
|
+
*
|
|
135
|
+
* `required`, `min(18)` and `max(120)` without messages show the default text for each kind.
|
|
136
|
+
*/
|
|
137
|
+
@Component({
|
|
138
|
+
selector: 'app-example-number-input-signal-forms',
|
|
139
|
+
imports: [FormField, FormRoot, BfButtonComponent, BfNumberInputComponent],
|
|
140
|
+
template: `
|
|
141
|
+
<form [formRoot]="profile" class="flex flex-col items-start gap-3">
|
|
142
|
+
<bf-number-input label="Age" class="self-stretch" [formField]="profile.age" />
|
|
143
|
+
<bf-button type="submit">Save</bf-button>
|
|
144
|
+
</form>
|
|
145
|
+
`,
|
|
146
|
+
})
|
|
147
|
+
export class NumberInputSignalFormsExampleComponent {
|
|
148
|
+
readonly model = signal<{ age: number | null }>({ age: null });
|
|
149
|
+
readonly profile = form(
|
|
150
|
+
this.model,
|
|
151
|
+
(p) => {
|
|
152
|
+
required(p.age);
|
|
153
|
+
min(p.age, 18);
|
|
154
|
+
max(p.age, 120);
|
|
155
|
+
},
|
|
156
|
+
{ submission: { action: () => Promise.resolve(undefined) } },
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Reactive Forms
|
|
162
|
+
|
|
163
|
+
The control works in Reactive Forms through Angular's Signal Forms interop (no `ControlValueAccessor`). Bind a `FormControl<number | null>`; `Validators.min()`/`max()` errors show the same default text, with the limit read from the error's `context`. Reactive validators do not reach the control's inputs, so pass `[min]`/`[max]` as well for the native attributes. The consumer-smoke fixture below binds it with `formControlName` inside a `FormGroup`; a standalone `[formControl]` works the same way.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { Component } from '@angular/core';
|
|
167
|
+
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
168
|
+
import {
|
|
169
|
+
BfButtonComponent,
|
|
170
|
+
BfCheckboxComponent,
|
|
171
|
+
BfInputComponent,
|
|
172
|
+
BfSelectComponent,
|
|
173
|
+
type BfSelectOption,
|
|
174
|
+
} from '@bitforgehq/ui';
|
|
175
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The same widgets in a Reactive Forms app: Angular 22 binds `formControlName` to them with no
|
|
179
|
+
* ControlValueAccessor. Controls are typed like the widgets' values: `number | null` for a select
|
|
180
|
+
* of numeric ids with nothing chosen yet and for a number field. `Validators.*` errors carry no
|
|
181
|
+
* message, so each touched invalid field shows the default text for the error's kind ("Fill in
|
|
182
|
+
* this field", "Enter 1 or more"), translated by `provideBfLabels()`; pass `errorMessage` only for
|
|
183
|
+
* text of your own.
|
|
184
|
+
*/
|
|
185
|
+
@Component({
|
|
186
|
+
selector: 'app-reactive-forms-example',
|
|
187
|
+
imports: [
|
|
188
|
+
ReactiveFormsModule,
|
|
189
|
+
BfButtonComponent,
|
|
190
|
+
BfCheckboxComponent,
|
|
191
|
+
BfInputComponent,
|
|
192
|
+
BfNumberInputComponent,
|
|
193
|
+
BfSelectComponent,
|
|
194
|
+
],
|
|
195
|
+
template: `
|
|
196
|
+
<form [formGroup]="order" class="flex flex-col gap-4" novalidate (ngSubmit)="save()">
|
|
197
|
+
<bf-input label="Email" type="email" formControlName="email" />
|
|
198
|
+
<bf-select
|
|
199
|
+
label="Team"
|
|
200
|
+
placeholder="Choose a team"
|
|
201
|
+
[options]="teams"
|
|
202
|
+
formControlName="team"
|
|
203
|
+
/>
|
|
204
|
+
<bf-number-input label="Quantity" formControlName="quantity" [min]="1" />
|
|
205
|
+
<bf-checkbox label="I accept the terms" formControlName="terms" />
|
|
206
|
+
<bf-button type="submit" class="self-start">Place order</bf-button>
|
|
207
|
+
</form>
|
|
208
|
+
`,
|
|
209
|
+
})
|
|
210
|
+
export class ReactiveFormsExampleComponent {
|
|
211
|
+
protected readonly teams: BfSelectOption<number>[] = [
|
|
212
|
+
{ value: 1, label: 'Design' },
|
|
213
|
+
{ value: 2, label: 'Engineering' },
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
protected readonly order = new FormGroup({
|
|
217
|
+
email: new FormControl('', {
|
|
218
|
+
nonNullable: true,
|
|
219
|
+
validators: [Validators.required, Validators.email],
|
|
220
|
+
}),
|
|
221
|
+
team: new FormControl<number | null>(null, Validators.required),
|
|
222
|
+
quantity: new FormControl<number | null>(null, [Validators.required, Validators.min(1)]),
|
|
223
|
+
terms: new FormControl(false, { nonNullable: true, validators: Validators.requiredTrue }),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
protected save(): void {
|
|
227
|
+
if (this.order.invalid) {
|
|
228
|
+
this.order.markAllAsTouched();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Send this.order.getRawValue() to your API.
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Step and keyboard
|
|
237
|
+
|
|
238
|
+
`step` sets the arrow-key and spin-button increment: `1` for whole numbers, `0.05` for a price, `any` for no grid. On phones, `inputMode` picks the virtual keyboard. Unset, a field that rules out negatives (`min` 0 or more) gets `numeric` for a whole-number step (or none) and `decimal` for a fractional one; any other field gets no `inputmode`, because neither the iOS numeric nor decimal pad has a minus key, and the full keyboard does. Set `inputMode` explicitly to override.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
import { Component, signal } from '@angular/core';
|
|
242
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
243
|
+
|
|
244
|
+
/** An hourly rate in 5-centime steps, with a currency in the icon-end slot. */
|
|
245
|
+
@Component({
|
|
246
|
+
selector: 'app-example-number-input-step',
|
|
247
|
+
imports: [BfNumberInputComponent],
|
|
248
|
+
template: `
|
|
249
|
+
<bf-number-input label="Hourly rate" [min]="0" [step]="0.05" [iconEnd]="true" [(value)]="rate">
|
|
250
|
+
<span bfSlot="icon-end" class="text-text-muted">CHF</span>
|
|
251
|
+
</bf-number-input>
|
|
252
|
+
`,
|
|
253
|
+
})
|
|
254
|
+
export class NumberInputStepExampleComponent {
|
|
255
|
+
protected readonly rate = signal<number | null>(120);
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Sizes
|
|
260
|
+
|
|
261
|
+
`sm` 32 px, `md` 40 px (default), `lg` 48 px — the `BfSize` scale of `bf-input`. Unset, `size` follows `provideBfDefaults()`'s `fieldSize`, `md` by default. Fields get no touch extension: `sm`/`md` fields stay 32/40 px under a coarse pointer, so touch apps use `provideBfDefaults({ fieldSize: 'lg' })`.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { Component, signal } from '@angular/core';
|
|
265
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
266
|
+
|
|
267
|
+
/** The sm, md and lg number field sizes. */
|
|
268
|
+
@Component({
|
|
269
|
+
selector: 'app-example-number-input-sizes',
|
|
270
|
+
imports: [BfNumberInputComponent],
|
|
271
|
+
template: `
|
|
272
|
+
<div class="flex flex-col gap-3">
|
|
273
|
+
<bf-number-input size="sm" ariaLabel="Quantity" [(value)]="quantity" />
|
|
274
|
+
<bf-number-input label="Quantity" [(value)]="quantity" />
|
|
275
|
+
<bf-number-input size="lg" label="Quantity" [(value)]="quantity" />
|
|
276
|
+
</div>
|
|
277
|
+
`,
|
|
278
|
+
})
|
|
279
|
+
export class NumberInputSizesExampleComponent {
|
|
280
|
+
protected readonly quantity = signal<number | null>(2);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Read-only
|
|
285
|
+
|
|
286
|
+
`readonly` uses the native attribute: the number can be selected and copied, the field stays in the tab order on the muted surface, and the arrow keys do not change it.
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
import { Component } from '@angular/core';
|
|
290
|
+
import { BfNumberInputComponent } from '@bitforgehq/ui/number-input';
|
|
291
|
+
|
|
292
|
+
/** A read-only number field. */
|
|
293
|
+
@Component({
|
|
294
|
+
selector: 'app-example-number-input-readonly',
|
|
295
|
+
imports: [BfNumberInputComponent],
|
|
296
|
+
template: `<bf-number-input label="Seats included" [value]="5" [readonly]="true" />`,
|
|
297
|
+
})
|
|
298
|
+
export class NumberInputReadonlyExampleComponent {}
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Desktop
|
|
302
|
+
|
|
303
|
+
The field has no hover state; the text cursor shows over it. The `primary` border and the focus ring show on every focus, by mouse or keyboard, because a focused field takes typing. Fields default to `md` (40 px); `provideBfDefaults({ fieldSize: 'sm' })`, or `{ size: 'sm' }` in a component's `providers`, gives 32 px fields for a dense filter bar or table ([density guide](https://bitforge-ui.web.app/density)). The browser's spin buttons step the value with the mouse, within `min`, `max` and `step`.
|
|
304
|
+
|
|
305
|
+
## Theming
|
|
306
|
+
|
|
307
|
+
The same tokens as `bf-input`:
|
|
308
|
+
|
|
309
|
+
- Border: `border-strong`, switching to `danger-border` on error; focus: `primary` border with a `ring` ring.
|
|
310
|
+
- Disabled: `surface-sunken` border, `surface-muted` background, `text-disabled` text. Read-only: `surface-muted` background. Placeholder: `text-muted`. Icon slots: `text-subtle` (an icon's 3:1); give unit text such as `CHF` `text-text-muted` for 4.5:1.
|
|
311
|
+
- Label, help and error text come from the `bf-field` wrapper (see its Theming section).
|
|
312
|
+
|
|
313
|
+
## Accessibility
|
|
314
|
+
|
|
315
|
+
Native `<input type="number">`, exposed as a spinbutton with its value, `min` and `max`; the arrow keys step it. `bf-field` renders the `<label for>`; the input wires `aria-describedby` to the help paragraph and the error paragraph under it (both while shown, help first) and, while it is invalid, `aria-errormessage` to the error paragraph, and `aria-invalid` is set once the field is touched (or left, for a parse error) and invalid. `aria-label` and `aria-labelledby` carry `ariaLabel` and `ariaLabelledby` only while there is no visible `label`. `focus()` focuses the native input; Signal Forms calls it from `focusBoundControl()`. Some browsers step a focused number field when the mouse wheel turns over it; the component leaves that native behaviour alone.
|
|
316
|
+
|
|
317
|
+
### Keyboard
|
|
318
|
+
|
|
319
|
+
| Key | Action |
|
|
320
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
321
|
+
| <kbd>Tab</kbd> / <kbd>Shift</kbd>+<kbd>Tab</kbd> | Moves focus to and from the field; a `readonly` one stays in the tab order |
|
|
322
|
+
| <kbd>ArrowUp</kbd> / <kbd>ArrowDown</kbd> | Steps the value up / down by `step`, within `min` and `max`; nothing while `readonly` |
|
|
323
|
+
| <kbd>Enter</kbd> | Inside a `<form>` with a submit button, submits the form |
|
|
324
|
+
|
|
325
|
+
## Inputs
|
|
326
|
+
|
|
327
|
+
| Name | Type | Default | Description |
|
|
328
|
+
| ---------------- | ------------------------------------------------------------------------------------------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
329
|
+
| `min` | `number \| undefined` | `undefined` | Lowest value (native `min`); bound by Signal Forms `min()`. Set by `[formField]`; do not bind it yourself. |
|
|
330
|
+
| `max` | `number \| undefined` | `undefined` | Highest value (native `max`); bound by Signal Forms `max()`. Set by `[formField]`; do not bind it yourself. |
|
|
331
|
+
| `step` | `BfNumberInputStep \| undefined` (`number \| 'any' \| undefined`) | `undefined` | Step of the arrow keys and spin buttons, and the native step check (e.g. `0.05` or `any`). |
|
|
332
|
+
| `size` | `BfSize \| undefined` (`'sm' \| 'md' \| 'lg' \| undefined`) | `undefined` | Height step: sm 32, md 40, lg 48 px; unset follows `provideBfDefaults()` (`fieldSize`, `md` by default). |
|
|
333
|
+
| `label` | `string` | `''` | Visible label above the control. |
|
|
334
|
+
| `ariaLabel` | `string` | `''` | Accessible name when no visible label is rendered. Ignored when `label` is set. |
|
|
335
|
+
| `ariaLabelledby` | `string` | `''` | Id(s) of the element(s) that name the field when no visible label is rendered. Ignored when `label` is set. |
|
|
336
|
+
| `placeholder` | `string` | `''` | Example value shown while empty. |
|
|
337
|
+
| `helpText` | `string` | `''` | Helper text under the control. |
|
|
338
|
+
| `errorMessage` | `string` | `''` | Explicit error text; takes precedence over Signal Forms and parse errors. |
|
|
339
|
+
| `disabled` | `boolean` | `false` | Disables the control. Set by `[formField]`; do not bind it yourself. |
|
|
340
|
+
| `readonly` | `boolean` | `false` | Makes the control read-only. Set by `[formField]`; do not bind it yourself. |
|
|
341
|
+
| `required` | `boolean` | `false` | Marks the control required (label marker and native attribute). Set by `[formField]`; do not bind it yourself. |
|
|
342
|
+
| `invalid` | `boolean` | `false` | Field validity from Signal Forms. Set by `[formField]`; do not bind it yourself. |
|
|
343
|
+
| `touched` | `boolean` | `false` | Whether the user has left the field; errors show only once touched. Set by `[formField]`; do not bind it yourself. |
|
|
344
|
+
| `errors` | `readonly ValidationError.WithOptionalFieldTree[]` | `[]` | Validation errors from Signal Forms; the first message is shown once touched. Set by `[formField]`; do not bind it yourself. |
|
|
345
|
+
| `name` | `string` | `''` | Native name attribute. Set by `[formField]`; do not bind it yourself. |
|
|
346
|
+
| `id` | `string` | `generated` | Control id; generated when not set. |
|
|
347
|
+
| `iconStart` | `boolean` | `false` | Reserve space for an icon or unit in the `icon-start` slot. |
|
|
348
|
+
| `iconEnd` | `boolean` | `false` | Reserve space for an icon or unit in the `icon-end` slot. |
|
|
349
|
+
| `autocomplete` | `string` | `''` | Native autocomplete hint. |
|
|
350
|
+
| `inputMode` | `BfNumberInputMode` (`'numeric' \| 'decimal' \| ''`) | `''` | Virtual keyboard; unset, a field with `min >= 0` gets `numeric` for an integer (or no) step and `decimal` otherwise, and any other field none (the full keyboard, which has a minus key). |
|
|
351
|
+
| `enterKeyHint` | `BfInputEnterKeyHint` (`'enter' \| 'done' \| 'go' \| 'next' \| 'previous' \| 'search' \| 'send' \| ''`) | `''` | Label of the virtual keyboard's Enter key (native `enterkeyhint`). |
|
|
352
|
+
| `value` | `number \| null` | `null` | The number, `null` while the field is empty; two-way bindable and bound by `[formField]`. Two-way: `[(value)]`. |
|
|
353
|
+
|
|
354
|
+
## Outputs
|
|
355
|
+
|
|
356
|
+
| Name | Payload | Description |
|
|
357
|
+
| --------- | ---------------- | ------------------------------------------------------------------------------------------------------- |
|
|
358
|
+
| `changed` | `number \| null` | Emitted with the new value (`null` when emptied) right after the user changes it. |
|
|
359
|
+
| `focused` | `FocusEvent` | Emitted when the control gains focus. |
|
|
360
|
+
| `blurred` | `FocusEvent` | Emitted when the control loses focus. |
|
|
361
|
+
| `touch` | `void` | Emitted on blur so Signal Forms marks the field touched. Set by `[formField]`; do not bind it yourself. |
|
|
362
|
+
|
|
363
|
+
## Slots
|
|
364
|
+
|
|
365
|
+
- `icon-start`: An icon inside the field, before the number.
|
|
366
|
+
- `icon-end`: An icon inside the field, after the number.
|
|
367
|
+
|
|
368
|
+
## Methods
|
|
369
|
+
|
|
370
|
+
### `focus(options?: FocusOptions): void`
|
|
371
|
+
|
|
372
|
+
Focuses the native input; Signal Forms calls it from `focusBoundControl()`.
|
|
373
|
+
|
|
374
|
+
| Parameter | Type | Description |
|
|
375
|
+
| ---------- | -------------- | --------------------------------------------------------------------- |
|
|
376
|
+
| `options?` | `FocusOptions` | Passed on to the native `focus()`, such as `{ preventScroll: true }`. |
|
|
377
|
+
|
|
378
|
+
## Notes
|
|
379
|
+
|
|
380
|
+
### Do
|
|
381
|
+
|
|
382
|
+
- Use it for quantities, prices, ages and numeric ids that are typed, instead of `bf-input type="number"` with a string model.
|
|
383
|
+
- Give a `step` that matches the data (`0.05` for a price), so stepping and the native check agree.
|
|
384
|
+
|
|
385
|
+
### Don't
|
|
386
|
+
|
|
387
|
+
- Don't use it for codes that only look numeric (postal codes, phone numbers, card numbers): leading zeros are lost; use `bf-input` with `inputMode="numeric"`.
|
|
388
|
+
- Don't bind both `[(value)]` and `[formField]`.
|
|
389
|
+
|
|
390
|
+
## Migration notes
|
|
391
|
+
|
|
392
|
+
### 0.3.0
|
|
393
|
+
|
|
394
|
+
New `changed` output: the new `number | null` right after a user edit that changes the model
|
|
395
|
+
(unreadable text changes nothing and does not emit). `step` is typed with the new exported
|
|
396
|
+
`BfNumberInputStep` (`number | 'any'`) and `inputMode` with `BfNumberInputMode`
|
|
397
|
+
(`'numeric' | 'decimal' | ''`); `enterKeyHint` takes `BfInputEnterKeyHint` (`BfEnterKeyHint | ''`).
|
|
398
|
+
|
|
399
|
+
New `bf-number-input`: a native `<input type="number">` with the chrome, sizes (`fieldSize`), slots
|
|
400
|
+
and states of `bf-input` that implements `FormValueControl<number | null>` through Angular's
|
|
401
|
+
`transformedValue()`. An empty field is `null`; text the browser cannot read as a number keeps the
|
|
402
|
+
model and raises a `parse` error ("Enter a valid number", `provideBfLabels()`'s
|
|
403
|
+
`validation.parse`), shown once the field is left, with or without a form. `min`, `max` and
|
|
404
|
+
`step` are native attributes that limit stepping, bound by Signal Forms `min()`/`max()` (whose
|
|
405
|
+
errors and default messages are the only range errors; a step mismatch is not reported). Unset,
|
|
406
|
+
`inputMode` is `numeric` for an integer (or no) step and `decimal` otherwise only when `min >= 0`;
|
|
407
|
+
without such a `min` it stays off, so phone keyboards keep a minus key. A form `reset()` replaces
|
|
408
|
+
unreadable text with the number. Works
|
|
409
|
+
with Reactive Forms `FormControl<number | null>` through the Signal Forms interop. See Migration 8.
|
|
410
|
+
It ships in its own entry point, `import { BfNumberInputComponent } from
|
|
411
|
+
'@bitforgehq/ui/number-input'`, because it parses with Angular's `transformedValue()` and so loads
|
|
412
|
+
Angular Forms (`@angular/forms`, a required peer); a parse error reaches the bound Signal Forms
|
|
413
|
+
field (blocking `submit()`) or Reactive `FormControl` (invalid) with no setup. The widgets entry
|
|
414
|
+
`@bitforgehq/ui` loads no `@angular/forms` code at runtime, so an app without forms ships none of
|
|
415
|
+
it.
|
|
416
|
+
|
|
417
|
+
JSDoc of `number-input.component.ts` for the API reference: `@slot` docs for `icon-start`,
|
|
418
|
+
`icon-end`; `@param` docs; template handlers and framework hooks marked `@internal`, so the
|
|
419
|
+
reference leaves them out. No behaviour change.
|
|
420
|
+
|
|
421
|
+
**Behaviour:** the help text stays while an error shows, with the error under it (F-020):
|
|
422
|
+
`aria-describedby` lists both (`"<id>-help <id>-error"`), and `aria-errormessage` points at the
|
|
423
|
+
error paragraph while `aria-invalid="true"`. See Migration 25.
|