ng-hub-ui-forms 22.33.2 → 22.35.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 +195 -6
- package/fesm2022/ng-hub-ui-forms.mjs +1636 -101
- package/fesm2022/ng-hub-ui-forms.mjs.map +1 -1
- package/package.json +7 -1
- package/styles/_group-addons.scss +11 -0
- package/styles/_tokens.scss +129 -2
- package/types/ng-hub-ui-forms.d.ts +710 -47
package/README.md
CHANGED
|
@@ -51,10 +51,12 @@ npm install ng-hub-ui-forms
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
`ng-hub-ui-utils` is a peer dependency (the datepicker opens its panel through its overlay
|
|
54
|
-
service, and every field renders its tooltip helper text with `hubTooltip`)
|
|
54
|
+
service, and every field renders its tooltip helper text with `hubTooltip`). `ng-hub-ui-ds`
|
|
55
|
+
carries the design tokens the fields read; it is optional, because every token is read with a
|
|
56
|
+
fallback, but without it the fields fall back to their own defaults instead of your palette:
|
|
55
57
|
|
|
56
58
|
```bash
|
|
57
|
-
npm install ng-hub-ui-utils
|
|
59
|
+
npm install ng-hub-ui-utils ng-hub-ui-ds
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
### 2. Import
|
|
@@ -93,7 +95,7 @@ mode — no Bootstrap dependency.
|
|
|
93
95
|
|
|
94
96
|
## 🎯 Features
|
|
95
97
|
|
|
96
|
-
- **Fields** — `hub-input` (text/number/email/password/color/switch/checkbox/counter, with input-group addons & masks, projected in-field affixes, a built-in `clearable` button, the mixed `indeterminate` state on checkboxes and debounced typeahead `search`; the `file` format is **deprecated** → use `hub-file-input`), `hub-otp-input`, `hub-textarea` (+ `hubAutoresize`), `hub-slider` (single / dual thumb, gradient fill), `hub-segmented` (segmented control field — single & multiple selection, horizontal & vertical, with label + validation), `hub-select` (dropdown format, grouping, client-side search via `searchable` **and** server-side async typeahead via a `typeahead` Subject, tag creation with `addTag`, custom templates, `prepend` / `append` group addons and attached icons/buttons via `hubPrepend` / `hubAppend`; the `buttons` / `checkbox` / `radio` formats are **deprecated** → use `hub-segmented`), `hub-datepicker` (single & range at any granularity from a year to a second, time picking, min/max down to the minute, keyboard nav, i18n), `hub-timepicker` (a time of day as `HH:MM`, on the platform's own time control, with `min` / `max` / `step`), `hub-file-input` (drag & drop, clipboard paste, type/size limits, previews, optional upload progress).
|
|
98
|
+
- **Fields** — `hub-input` (text/number/email/password/color/switch/checkbox/counter, the colour format as a hex field or, given a palette, a grid of swatches, with input-group addons & masks, projected in-field affixes, a built-in `clearable` button, the mixed `indeterminate` state on checkboxes and debounced typeahead `search`; the `file` format is **deprecated** → use `hub-file-input`), `hub-otp-input`, `hub-textarea` (+ `hubAutoresize`), `hub-slider` (single / dual thumb, gradient fill), `hub-segmented` (segmented control field — single & multiple selection, horizontal & vertical, with label + validation), `hub-select` (dropdown format, grouping, client-side search via `searchable` **and** server-side async typeahead via a `typeahead` Subject, tag creation with `addTag`, custom templates, `prepend` / `append` group addons and attached icons/buttons via `hubPrepend` / `hubAppend`; the `buttons` / `checkbox` / `radio` formats are **deprecated** → use `hub-segmented`), `hub-datepicker` (single & range at any granularity from a year to a second, time picking, min/max down to the minute, keyboard nav, i18n), `hub-timepicker` (a time of day as `HH:MM`, on the platform's own time control, with `min` / `max` / `step`), `hub-file-input` (drag & drop, clipboard paste, type/size limits, previews as a list, as tiles or inside the field together with the files a record already has, optional upload progress).
|
|
97
99
|
- **Automatic error display** — bind a field and its control errors render below it; `fieldset[hubFieldset]`, `form[hubForm]` and `hub-legend` surface group- and form-level (cross-field) errors the same way, with zero wiring.
|
|
98
100
|
- **Containers** — `fieldset[hubFieldset]` (or the `<hub-fieldset>` element) / `form[hubForm]` group fields and show their group errors; `hub-legend` renders an accessible legend.
|
|
99
101
|
- **Configurable** — `provideHubForms({ … })` sets the invalid-feedback templates, datepicker locale/labels, file-input labels and more, app-wide or per instance.
|
|
@@ -131,7 +133,7 @@ section.
|
|
|
131
133
|
## 📦 Installation
|
|
132
134
|
|
|
133
135
|
```bash
|
|
134
|
-
npm install ng-hub-ui-forms ng-hub-ui-utils
|
|
136
|
+
npm install ng-hub-ui-forms ng-hub-ui-utils ng-hub-ui-ds
|
|
135
137
|
```
|
|
136
138
|
|
|
137
139
|
### Peer Dependencies
|
|
@@ -142,6 +144,7 @@ npm install ng-hub-ui-forms ng-hub-ui-utils
|
|
|
142
144
|
"@angular/core": ">=21.0.0",
|
|
143
145
|
"@angular/forms": ">=21.0.0",
|
|
144
146
|
"@angular/platform-browser": ">=21.0.0",
|
|
147
|
+
"ng-hub-ui-ds": ">=22.0.0",
|
|
145
148
|
"ng-hub-ui-utils": ">=22.12.0"
|
|
146
149
|
}
|
|
147
150
|
```
|
|
@@ -192,6 +195,57 @@ string, so asking it to carry markup would drop the markup silently.
|
|
|
192
195
|
<hub-input formControlName="darkMode" type="switch" label="Dark mode" />
|
|
193
196
|
```
|
|
194
197
|
|
|
198
|
+
#### Colour fields
|
|
199
|
+
|
|
200
|
+
`type="color"` is a text field for the hex code, with the colour in a square at its start. The square
|
|
201
|
+
opens the browser's picker. The text takes a colour typed with or without `#`, in three or six digits,
|
|
202
|
+
and the form stores it as lowercase `#rrggbb`, the one notation the native picker reads. Invalid text
|
|
203
|
+
leaves the value alone and goes back to the last valid colour on blur.
|
|
204
|
+
|
|
205
|
+
Give the field a list of colours and it becomes a grid of swatches, one row the height of a field:
|
|
206
|
+
|
|
207
|
+
```html
|
|
208
|
+
<hub-input formControlName="status" type="color" label="Status colour" [swatches]="palettes.status" />
|
|
209
|
+
|
|
210
|
+
<hub-input
|
|
211
|
+
formControlName="tag"
|
|
212
|
+
type="color"
|
|
213
|
+
label="Tag"
|
|
214
|
+
[swatches]="['#ef4444', { value: '#22c55e', label: 'Done' }]"
|
|
215
|
+
[allowCustomColor]="false"
|
|
216
|
+
/>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { HUB_COLOR_PALETTES } from 'ng-hub-ui-forms';
|
|
221
|
+
|
|
222
|
+
readonly palettes = HUB_COLOR_PALETTES;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
- A swatch is any CSS colour that `parseColor` from `ng-hub-ui-utils` reads (hex, `rgb()`, `hsl()`,
|
|
226
|
+
`oklch()`, `oklab()`, a named colour), bare or as `{ value, label }`. The label is what a screen reader
|
|
227
|
+
says, so name the colours that have a name. The control receives the string exactly as written. An
|
|
228
|
+
entry that is not a colour is dropped, with a warning in development builds.
|
|
229
|
+
- The last cell opens the native picker for a colour outside the list. `[allowCustomColor]="false"`
|
|
230
|
+
leaves it out for a closed palette; `customColorLabel` names it.
|
|
231
|
+
- The cells share the row down to `--hub-input-swatch-min-width`, then wrap onto more rows. Once they
|
|
232
|
+
wrap the field drops its box; `--hub-input-swatch-wrapped-border-color` and `-wrapped-bg` bring it back.
|
|
233
|
+
- The grid is a radio group named by the field label, with one Tab stop; the arrows, Home and End move
|
|
234
|
+
the selection.
|
|
235
|
+
- `HUB_COLOR_PALETTES` has five frozen lists of lowercase hex, each swatch named in English: `tailwind`
|
|
236
|
+
(17), `material` (19), `pastel` (17), `neutral` (11) and `status` (5).
|
|
237
|
+
|
|
238
|
+
Which field is drawn:
|
|
239
|
+
|
|
240
|
+
| `swatches` | Application palette (`provideHubForms`) | Result |
|
|
241
|
+
| ---------------- | --------------------------------------- | -------------------------------- |
|
|
242
|
+
| `null` (default) | none (default) | hex field |
|
|
243
|
+
| `null` | a list | grid with the application palette |
|
|
244
|
+
| `[]` | any | hex field |
|
|
245
|
+
| a list | any | grid with the field's list |
|
|
246
|
+
|
|
247
|
+
A list in which no entry is a colour also leaves the hex field.
|
|
248
|
+
|
|
195
249
|
#### Icon affix & typeahead (search boxes)
|
|
196
250
|
|
|
197
251
|
Project a leading / trailing icon **inside** the field, emit a debounced term on every keystroke, and let the field render its own clear button:
|
|
@@ -315,6 +369,38 @@ much room is left to type and so promises typing.
|
|
|
315
369
|
<hub-select formControlName="city" label="City" [items]="cities" bindLabel="name" bindValue="id" groupBy="country" />
|
|
316
370
|
```
|
|
317
371
|
|
|
372
|
+
#### Customization slots
|
|
373
|
+
|
|
374
|
+
Every part of the panel can be re-drawn from a template. The slots carry this library's own name;
|
|
375
|
+
the `ng-*-tmp` attributes of the vendored engine underneath are deprecated and disappear in 23.0.0.
|
|
376
|
+
|
|
377
|
+
```html
|
|
378
|
+
<hub-select formControlName="assignee" label="Assignee" [items]="people" bindLabel="name">
|
|
379
|
+
<ng-template hubSelectLabel let-item="item">{{ item.emoji }} {{ item.name }}</ng-template>
|
|
380
|
+
<ng-template hubSelectOption let-item="item">
|
|
381
|
+
<strong>{{ item.name }}</strong>
|
|
382
|
+
<small>{{ item.role }}</small>
|
|
383
|
+
</ng-template>
|
|
384
|
+
</hub-select>
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
| Slot | Draws | Context |
|
|
388
|
+
| --- | --- | --- |
|
|
389
|
+
| `hubSelectOption` | one option in the list | `item`, `item$`, `index`, `searchTerm` |
|
|
390
|
+
| `hubSelectOptgroup` | a group header, with `groupBy` | `item`, `item$`, `index`, `searchTerm` |
|
|
391
|
+
| `hubSelectLabel` | the selected value, single mode | `item`, `label`, `clear` |
|
|
392
|
+
| `hubSelectMultiLabel` | all selected values at once, multiple mode | `items`, `clear` |
|
|
393
|
+
| `hubSelectHeader` | a fixed block above the list | `searchTerm` |
|
|
394
|
+
| `hubSelectFooter` | a fixed block below the list | `searchTerm` |
|
|
395
|
+
| `hubSelectNotFound` | the "no items found" message | `searchTerm` |
|
|
396
|
+
| `hubSelectTypeToSearch` | the "type to search" hint | — |
|
|
397
|
+
| `hubSelectLoadingText` | the "loading…" message | `searchTerm` |
|
|
398
|
+
| `hubSelectLoadingSpinner` | the spinner in the control | — |
|
|
399
|
+
| `hubSelectTag` | the "add \<term\>" row, with `addTag` | `searchTerm` |
|
|
400
|
+
| `hubSelectClearButton` | the clear (×) control | — |
|
|
401
|
+
|
|
402
|
+
Import the directive you use — `HubSelectOptionDirective`, `HubSelectLabelDirective` and so on.
|
|
403
|
+
|
|
318
404
|
#### Floating label
|
|
319
405
|
|
|
320
406
|
```html
|
|
@@ -562,7 +648,7 @@ Whatever the uploader reports on `done` is kept on the item, so the ids the serv
|
|
|
562
648
|
const uploadedIds = fileInput.files().map((item) => (item.response as { id: string }).id);
|
|
563
649
|
```
|
|
564
650
|
|
|
565
|
-
Customize it without forking the template: the `--hub-file-input-*` tokens (every icon is a swappable CSS mask), the `hub-file-input-theme(...)` mixin, and three projection slots.
|
|
651
|
+
Customize it without forking the template: the `--hub-file-input-*` tokens (every icon is a swappable CSS mask), the `hub-file-input-theme(...)` mixin, and three projection slots. `hubFileIcon` applies to `preview="list"`; the tiles of `grid` and `inline` draw the family icons described below.
|
|
566
652
|
|
|
567
653
|
```html
|
|
568
654
|
<hub-file-input formControlName="attachments" [multiple]="true">
|
|
@@ -572,6 +658,73 @@ Customize it without forking the template: the `--hub-file-input-*` tokens (ever
|
|
|
572
658
|
</hub-file-input>
|
|
573
659
|
```
|
|
574
660
|
|
|
661
|
+
#### Inline preview and stored files
|
|
662
|
+
|
|
663
|
+
`preview="inline"` puts the file inside the field. One tile fills it: the image when the browser can
|
|
664
|
+
paint it, otherwise the icon of its family and its name. Hover, keyboard focus or a drag over the tile
|
|
665
|
+
raise a "Replace" pill, and a button in the corner removes the file. With `multiple` the tiles form a
|
|
666
|
+
grid inside the field that ends in a tile for adding more, and `maxFiles` adds a "3 of 5 files"
|
|
667
|
+
counter; at the limit the add tile goes away.
|
|
668
|
+
|
|
669
|
+
`currentFile` shows what the record already has:
|
|
670
|
+
|
|
671
|
+
```html
|
|
672
|
+
<hub-file-input
|
|
673
|
+
formControlName="logo"
|
|
674
|
+
label="Logo"
|
|
675
|
+
accept="image/*"
|
|
676
|
+
preview="inline"
|
|
677
|
+
[currentFile]="company.logoUrl"
|
|
678
|
+
(currentFileRemoved)="markForDeletion($event)"
|
|
679
|
+
/>
|
|
680
|
+
|
|
681
|
+
<hub-file-input
|
|
682
|
+
formControlName="contract"
|
|
683
|
+
label="Signed contract"
|
|
684
|
+
preview="inline"
|
|
685
|
+
[currentFile]="{ url: '/api/contracts/42/file', name: 'contract.pdf', type: 'application/pdf' }"
|
|
686
|
+
/>
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
- A bare URL gives the name from its last segment, when that has an extension, and the type from a
|
|
690
|
+
`data:` URL. Pass a `HubCurrentFile` when the URL reveals neither, and a list with `multiple`.
|
|
691
|
+
- A stored file is only shown: the form value stays a `File`, a `File[]` or `null`. When the user
|
|
692
|
+
removes it, or replaces it with a picked file, `currentFileRemoved` emits it. That is the moment to
|
|
693
|
+
delete it on the server.
|
|
694
|
+
- A click on a tile opens its file: a picked image in a native `<dialog>`, a stored file or any other
|
|
695
|
+
picked file in a new tab. Delete or Backspace on a focused tile removes it.
|
|
696
|
+
- `readonly` keeps the files in view and openable but blocks every change. `[clearable]="false"` keeps
|
|
697
|
+
the user from removing files. `[imagePreview]="false"` draws every file as its icon and creates no
|
|
698
|
+
object URLs.
|
|
699
|
+
|
|
700
|
+
`preview="grid"` draws the same tiles under the dropzone. An avatar takes five tokens:
|
|
701
|
+
|
|
702
|
+
```css
|
|
703
|
+
.avatar-field {
|
|
704
|
+
--hub-file-input-inline-width: 8rem;
|
|
705
|
+
--hub-file-input-inline-aspect-ratio: 1;
|
|
706
|
+
--hub-file-input-tile-radius: 50%;
|
|
707
|
+
--hub-file-input-tile-fit: cover;
|
|
708
|
+
--hub-file-input-tile-padding: 0;
|
|
709
|
+
}
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
Each family icon (`pdf`, `document`, `spreadsheet`, `presentation`, `archive`, `audio`, `video`, `code`,
|
|
713
|
+
`image`, `generic`) is a mask token, so replacing one is one line, and the tile's `data-file-kind`
|
|
714
|
+
attribute scopes a colour to one family:
|
|
715
|
+
|
|
716
|
+
```css
|
|
717
|
+
.my-form {
|
|
718
|
+
--hub-file-input-kind-pdf-icon: url('/icons/pdf.svg');
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
.my-form .hub-file-input__tile[data-file-kind='pdf'] {
|
|
722
|
+
--hub-file-input-kind-icon-color: #dc2626;
|
|
723
|
+
}
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
The built-in drawings are [Bootstrap Icons](https://icons.getbootstrap.com) 1.13.1, under the MIT License.
|
|
727
|
+
|
|
575
728
|
#### Reproducing your own dropzone
|
|
576
729
|
|
|
577
730
|
The dropzone is built from a glyph, an invitation and a browse action, each themeable on its own — so a design system reproduces its own without forking the template.
|
|
@@ -630,6 +783,23 @@ form would have written anyway.
|
|
|
630
783
|
The attribute is restricted to `<fieldset>` on purpose: on a `<div>` it would draw a legend over
|
|
631
784
|
a group with none of the semantics assistive technology reads from a real fieldset.
|
|
632
785
|
|
|
786
|
+
#### One way to write the legend
|
|
787
|
+
|
|
788
|
+
`legend="…"` is shorthand: it builds a `<hub-legend>` for you. When the legend needs more than a
|
|
789
|
+
string — a required marker, an icon, a badge — project the element yourself and it is lifted into
|
|
790
|
+
the same native `<legend>`, with the same classes.
|
|
791
|
+
|
|
792
|
+
```html
|
|
793
|
+
<fieldset hubFieldset [group]="form.controls.address">
|
|
794
|
+
<hub-legend [required]="true" [invalid]="form.controls.address.invalid">Shipping address</hub-legend>
|
|
795
|
+
…
|
|
796
|
+
</fieldset>
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
The older `<ng-template hubLegend>` slot still works and is deprecated: it existed only so a legend
|
|
800
|
+
could carry markup, and `<hub-legend>` carries markup without an `ng-template` and without a second
|
|
801
|
+
directive to import. It is removed in 23.0.0.
|
|
802
|
+
|
|
633
803
|
### Validation states (invalid is automatic, valid is opt-in)
|
|
634
804
|
|
|
635
805
|
The **invalid** state is always automatic: a touched, invalid field shows its
|
|
@@ -670,6 +840,18 @@ field once it is touched and valid. The invalid state is unaffected — it is al
|
|
|
670
840
|
automatic; only success is gated behind this flag. A per-field `showValid` input
|
|
671
841
|
overrides the global default.
|
|
672
842
|
|
|
843
|
+
`color` sets an application palette for every colour field without `swatches` of its own (there is
|
|
844
|
+
none by default) and the two accessible names the colour field adds: `customColorLabel`
|
|
845
|
+
(`'Custom color'`) for the grid's last cell and `pickerLabel` (`'Choose color'`) for the hex field's
|
|
846
|
+
square. `fileInput` carries the file-input labels, among them `removeFile(name)`, `open(name)`,
|
|
847
|
+
`replace`, `replaceFile(name)`, `close`, `count(count, max)` and `currentFile`.
|
|
848
|
+
|
|
849
|
+
```ts
|
|
850
|
+
provideHubForms({
|
|
851
|
+
color: { swatches: HUB_COLOR_PALETTES.tailwind, customColorLabel: 'Other colour' }
|
|
852
|
+
});
|
|
853
|
+
```
|
|
854
|
+
|
|
673
855
|
---
|
|
674
856
|
|
|
675
857
|
## 🎨 Styling
|
|
@@ -701,6 +883,11 @@ hub-input {
|
|
|
701
883
|
}
|
|
702
884
|
```
|
|
703
885
|
|
|
886
|
+
**Colour field** — `--hub-input-color-size` is the width of the hex field's square; its height is
|
|
887
|
+
always the field's, and the default, the field's inner height, keeps it square. The grid of swatches
|
|
888
|
+
reads the `--hub-input-swatch-*` tokens, and `--hub-input-swatch-mark-color` forces one colour for every
|
|
889
|
+
check mark (unset, each mark is black or white, whichever reads on its swatch).
|
|
890
|
+
|
|
704
891
|
**`hub-slider`** — `--hub-slider-track-fill` takes a full `<image>` (e.g. a `linear-gradient(to right, …)`) for the filled part of the track, which renders intact clipped to the current percentage; `--hub-slider-value-space` is the value-bubble headroom and collapses to `0` on a `[showValue]="false"` (flush) slider:
|
|
705
892
|
|
|
706
893
|
```css
|
|
@@ -747,7 +934,9 @@ import { HubSignalFieldControl, hubSignalErrorMessages } from 'ng-hub-ui-forms/s
|
|
|
747
934
|
|
|
748
935
|
## ♿ Accessibility
|
|
749
936
|
|
|
750
|
-
- Labels are associated with their control (`for`/`id`); required fields are marked.
|
|
937
|
+
- Labels are associated with their control (`for`/`id`); required fields are marked. The colour
|
|
938
|
+
grid is a radio group, which a `<label for>` cannot name, so it points at the label's `id` through
|
|
939
|
+
`aria-labelledby`.
|
|
751
940
|
- **`labelType="visually-hidden"` names a control that has no room for a visible label.** A
|
|
752
941
|
toolbar search box or a compact grid cell cannot repeat the same word down every row, and the
|
|
753
942
|
alternative was a control with no accessible name at all — a placeholder is not a name. The
|