ng-hub-ui-forms 22.34.0 → 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 CHANGED
@@ -95,7 +95,7 @@ mode — no Bootstrap dependency.
95
95
 
96
96
  ## 🎯 Features
97
97
 
98
- - **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).
99
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.
100
100
  - **Containers** — `fieldset[hubFieldset]` (or the `<hub-fieldset>` element) / `form[hubForm]` group fields and show their group errors; `hub-legend` renders an accessible legend.
101
101
  - **Configurable** — `provideHubForms({ … })` sets the invalid-feedback templates, datepicker locale/labels, file-input labels and more, app-wide or per instance.
@@ -195,6 +195,57 @@ string, so asking it to carry markup would drop the markup silently.
195
195
  <hub-input formControlName="darkMode" type="switch" label="Dark mode" />
196
196
  ```
197
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
+
198
249
  #### Icon affix & typeahead (search boxes)
199
250
 
200
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:
@@ -597,7 +648,7 @@ Whatever the uploader reports on `done` is kept on the item, so the ids the serv
597
648
  const uploadedIds = fileInput.files().map((item) => (item.response as { id: string }).id);
598
649
  ```
599
650
 
600
- 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.
601
652
 
602
653
  ```html
603
654
  <hub-file-input formControlName="attachments" [multiple]="true">
@@ -607,6 +658,73 @@ Customize it without forking the template: the `--hub-file-input-*` tokens (ever
607
658
  </hub-file-input>
608
659
  ```
609
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
+
610
728
  #### Reproducing your own dropzone
611
729
 
612
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.
@@ -722,6 +840,18 @@ field once it is touched and valid. The invalid state is unaffected — it is al
722
840
  automatic; only success is gated behind this flag. A per-field `showValid` input
723
841
  overrides the global default.
724
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
+
725
855
  ---
726
856
 
727
857
  ## 🎨 Styling
@@ -753,6 +883,11 @@ hub-input {
753
883
  }
754
884
  ```
755
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
+
756
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:
757
892
 
758
893
  ```css
@@ -799,7 +934,9 @@ import { HubSignalFieldControl, hubSignalErrorMessages } from 'ng-hub-ui-forms/s
799
934
 
800
935
  ## ♿ Accessibility
801
936
 
802
- - 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`.
803
940
  - **`labelType="visually-hidden"` names a control that has no room for a visible label.** A
804
941
  toolbar search box or a compact grid cell cannot repeat the same word down every row, and the
805
942
  alternative was a control with no accessible name at all — a placeholder is not a name. The