ng-hub-ui-forms 22.35.2 → 22.37.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ng-hub-ui-forms",
3
- "version": "22.35.2",
3
+ "version": "22.37.0",
4
4
  "license": "MIT",
5
5
  "description": "Accessible, signal-based form fields for Angular (input, textarea, slider, segmented, select, datepicker, file input with drag & drop and upload progress) with automatic error display for controls, FormGroups and FormArrays. Reactive Forms today, Signal Forms ready. Part of the ng-hub-ui family.",
6
6
  "author": "Carlos Morcillo Fernández <carlos.morcillo@me.com> (https://www.carlosmorcillo.com)",
@@ -36,10 +36,10 @@
36
36
  "angular21"
37
37
  ],
38
38
  "peerDependencies": {
39
- "@angular/common": ">=19.0.0",
40
- "@angular/core": ">=19.0.0",
41
- "@angular/forms": ">=19.0.0",
42
- "@angular/platform-browser": ">=19.0.0",
39
+ "@angular/common": ">=21.0.0",
40
+ "@angular/core": ">=21.0.0",
41
+ "@angular/forms": ">=21.0.0",
42
+ "@angular/platform-browser": ">=21.0.0",
43
43
  "ng-hub-ui-ds": ">=22.0.0",
44
44
  "ng-hub-ui-utils": ">=22.12.0"
45
45
  },
@@ -32,6 +32,15 @@ hub-timepicker {
32
32
  }
33
33
 
34
34
  .hub-field {
35
+ // The field is the containing block for anything absolute inside it. Only the
36
+ // visually-hidden label is absolute and has no closer positioned ancestor of its own —
37
+ // and with none at all it measured from the page, which in a scroll container put it
38
+ // thousands of pixels down and stretched the scroll height to reach it.
39
+ // The field is the containing block for anything absolute inside it. Only the
40
+ // visually-hidden label is absolute and has no closer positioned ancestor of its own —
41
+ // and with none at all it measured from the page, which in a scroll container put it
42
+ // thousands of pixels down and stretched the scroll height to reach it.
43
+ position: relative;
35
44
  display: flex;
36
45
  flex-direction: column;
37
46
  gap: var(--hub-form-field-gap);
@@ -21,6 +21,16 @@
21
21
  --hub-form-fieldset-padding-y: var(--hub-ref-space-3, 1rem);
22
22
  --hub-form-label-horizontal-max-width: 12rem; // horizontal label caps here, then ellipsizes
23
23
 
24
+ // The height every control in a form row stands at: one line of text between the input's two
25
+ // paddings and its two borders. An `<input>` reaches it by construction and needs nobody to
26
+ // say so; a control built out of divs — the select, the segmented bar — reaches it only if it
27
+ // is told, and each one guessing left a row of fields whose tops and bottoms never met.
28
+ // Written as the arithmetic rather than as a number so it follows the input scale on its own.
29
+ --hub-field-control-min-height: calc(
30
+ var(--hub-input-line-height) * var(--hub-input-font-size) + 2 * var(--hub-input-padding-y) + 2 *
31
+ var(--hub-input-border-width)
32
+ );
33
+
24
34
  --hub-form-invalid-color: var(--hub-sys-color-danger, #dc3545);
25
35
  --hub-form-invalid-border-color: var(--hub-sys-color-danger, #dc3545);
26
36
  --hub-form-invalid-focus-ring-color: var(--hub-sys-color-danger-subtle, rgba(220, 53, 69, 0.25));
@@ -254,6 +264,13 @@
254
264
  --hub-file-input-padding-x: var(--hub-ref-space-3, 1rem);
255
265
  --hub-file-input-padding-y: var(--hub-ref-space-4, 1.5rem);
256
266
  --hub-file-input-min-height: 7rem;
267
+ // `appearance="compact"`: the same field on one row. It stands at the shared control height,
268
+ // so a logo field lines up with the text inputs beside it instead of towering over them, and
269
+ // takes the input's own padding rather than the panel's.
270
+ --hub-file-input-compact-min-height: var(--hub-field-control-min-height);
271
+ --hub-file-input-compact-padding-x: var(--hub-input-padding-x);
272
+ --hub-file-input-compact-padding-y: var(--hub-input-padding-y);
273
+ --hub-file-input-compact-gap: var(--hub-ref-space-2, 0.5rem);
257
274
  --hub-file-input-gap: var(--hub-ref-space-2, 0.5rem);
258
275
  --hub-file-input-transition: var(--hub-form-transition);
259
276
  --hub-file-input-hover-border-color: var(--hub-sys-color-primary, #0d6efd);
@@ -448,6 +465,34 @@
448
465
  --hub-file-input-progress-bar-bg: var(--hub-sys-color-primary, #0d6efd);
449
466
  --hub-file-input-progress-indeterminate-duration: 1.2s;
450
467
 
468
+ // ── Page / container drop zone (`hubFileDrop`) ───────────────────────────
469
+ // The card raised over a whole screen while files are dragged onto it. Its own family rather
470
+ // than the dropzone's: this one covers something, and what it covers decides how it is drawn.
471
+ --hub-file-drop-veil-bg: color-mix(in oklch, var(--hub-sys-surface-page, #fff) 75%, transparent);
472
+ --hub-file-drop-padding: var(--hub-ref-space-4, 1.5rem);
473
+ --hub-file-drop-gap: var(--hub-ref-space-2, 0.5rem);
474
+ --hub-file-drop-card-bg: var(--hub-sys-surface-page, #fff);
475
+ --hub-file-drop-card-padding: var(--hub-ref-space-5, 2rem) var(--hub-ref-space-6, 3rem);
476
+ --hub-file-drop-card-max-width: 28rem;
477
+ --hub-file-drop-card-shadow: var(--hub-sys-shadow, 0 0.5rem 1rem rgba(0, 0, 0, 0.15));
478
+ --hub-file-drop-color: var(--hub-sys-text-primary, #212529);
479
+ --hub-file-drop-border-width: 2px;
480
+ --hub-file-drop-border-style: dashed;
481
+ --hub-file-drop-border-color: var(--hub-sys-color-primary, #0d6efd);
482
+ --hub-file-drop-radius: var(--hub-ref-radius-lg, 0.5rem);
483
+ --hub-file-drop-icon: var(--hub-file-input-icon);
484
+ --hub-file-drop-icon-size: 2.5rem;
485
+ --hub-file-drop-icon-color: var(--hub-sys-color-primary, #0d6efd);
486
+ --hub-file-drop-text-color: var(--hub-sys-text-primary, #212529);
487
+ --hub-file-drop-text-font-size: var(--hub-ref-font-size-lg, 1.125rem);
488
+ --hub-file-drop-text-font-weight: var(--hub-ref-font-weight-medium, 500);
489
+ --hub-file-drop-subtext-color: var(--hub-sys-text-muted, #6c757d);
490
+ --hub-file-drop-subtext-font-size: var(--hub-ref-font-size-sm, 0.875rem);
491
+ --hub-file-drop-transition: 0.15s ease-in-out;
492
+ // Above a modal, because a screen can be a modal: a drop zone that lands under the dialog it
493
+ // belongs to is a drop zone nobody can see.
494
+ --hub-file-drop-zindex: calc(var(--hub-sys-zindex-modal, 1055) + 10);
495
+
451
496
  // ── Textarea (inherits input) ──────────────────────────────────────────────
452
497
  --hub-textarea-padding-x: var(--hub-input-padding-x);
453
498
  --hub-textarea-padding-y: var(--hub-input-padding-y);
@@ -514,15 +559,11 @@
514
559
  --hub-select-focus-box-shadow: var(--hub-input-focus-box-shadow);
515
560
  --hub-select-placeholder-color: var(--hub-input-placeholder-color);
516
561
  --hub-select-padding-x: var(--hub-input-padding-x);
517
- // The height an input reaches by construction — one line of text between two paddings
518
- // and two borders. The select is a div and inherits none of that, so the arithmetic is
519
- // written out rather than approximated by a number: at 2.5rem it stood 2px taller than
520
- // every other field, which a floating label only made more visible by carrying the gap
521
- // into a taller box.
522
- --hub-select-min-height: calc(
523
- var(--hub-input-line-height) * var(--hub-input-font-size) + 2 * var(--hub-input-padding-y) + 2 *
524
- var(--hub-input-border-width)
525
- );
562
+ // The shared control height. Kept as its own token so a dense select can still be tuned on
563
+ // its own, but no longer a second copy of the arithmetic: at 2.5rem it stood 2px taller than
564
+ // every other field, which a floating label only made more visible by carrying the gap into
565
+ // a taller box.
566
+ --hub-select-min-height: var(--hub-field-control-min-height);
526
567
  // Group addons (`prepend` / `append`), chained to the input's so a currency or a
527
568
  // unit looks the same whichever field it is attached to — and still overridable
528
569
  // on its own when a select needs to differ.
@@ -546,7 +587,11 @@
546
587
  --hub-select-option-color: var(--hub-sys-text-primary, #212529);
547
588
  --hub-select-option-padding-x: var(--hub-ref-space-3, 1rem);
548
589
  --hub-select-option-padding-y: var(--hub-ref-space-2, 0.5rem);
549
- --hub-select-option-marked-bg: var(--hub-sys-surface-elevated, #f8f9fa);
590
+ // The row under the cursor, derived from the panel it sits in rather than named outright.
591
+ // It used to read the elevated surface, which is a surface and not a relation: in a theme
592
+ // with a grey page and a white elevated surface the marked row came out the only white one
593
+ // in the panel, so it read as the background and everything else as highlighted.
594
+ --hub-select-option-marked-bg: color-mix(in oklch, var(--hub-select-option-color) 8%, var(--hub-select-dropdown-bg));
550
595
  --hub-select-option-selected-bg: var(--hub-sys-color-primary, #0d6efd);
551
596
  // Text on the selected option sits on the primary accent — read the derived
552
597
  // contrast token so a light/custom primary keeps the label legible.
@@ -625,7 +670,16 @@
625
670
  --hub-segmented-radius: var(--hub-ref-radius-md, 0.375rem);
626
671
  --hub-segmented-gap: 0.25rem;
627
672
  --hub-segmented-padding-x: var(--hub-ref-space-3, 1rem);
628
- --hub-segmented-padding-y: var(--hub-ref-space-2, 0.5rem);
673
+ --hub-segmented-padding-y: var(--hub-ref-space-1, 0.25rem);
674
+ // The bar stands as tall as any other control in the row, which is the whole reason it has a
675
+ // height of its own at all: a segmented control lives beside a select, and it measured 43px
676
+ // against the select's 38 — close enough to look like a mistake and far enough to be one.
677
+ // The track's own padding is taken off here, so the bar comes out at the field height exactly
678
+ // rather than at the field height plus its frame.
679
+ --hub-segmented-min-height: var(--hub-field-control-min-height);
680
+ // How far `sm` and `lg` step either side of that height, so the three sizes stay a scale and
681
+ // the middle one keeps its promise.
682
+ --hub-segmented-size-step: var(--hub-ref-space-2, 0.5rem);
629
683
  // Duration/easing of the single-mode sliding indicator as it moves between options.
630
684
  --hub-segmented-indicator-transition: 0.2s ease;
631
685
  }
@@ -457,6 +457,18 @@ interface HubFileItem {
457
457
  * `inline` shows the files a record already has, through `currentFile`.
458
458
  */
459
459
  type HubFilePreview = 'none' | 'list' | 'grid' | 'inline';
460
+ /**
461
+ * How much room the field takes before it holds anything.
462
+ *
463
+ * `dropzone` is the invitation everybody knows: a tall panel with a glyph, a line asking for a
464
+ * drag and a browse control. It earns its height when files are the point of the screen.
465
+ *
466
+ * `compact` is the same field on one row — the browse control and the constraints, no glyph and
467
+ * no drag copy — for the case the tall one is wrong for: a logo beside four text inputs in a
468
+ * dialog, where the panel took half the height for one small file. It still takes a drop, and
469
+ * the drag copy would be a lie if it did not.
470
+ */
471
+ type HubFileInputAppearance = 'dropzone' | 'compact';
460
472
  /**
461
473
  * A file the record already has on the server, shown by `preview="inline"` while nothing is picked.
462
474
  *
@@ -1923,6 +1935,12 @@ declare class HubSelectComponent extends HubFieldControl {
1923
1935
  readonly clearable: _angular_core.InputSignalWithTransform<boolean, unknown>;
1924
1936
  /** Whether the dropdown closes after a selection. */
1925
1937
  readonly closeOnSelect: _angular_core.InputSignalWithTransform<boolean, unknown>;
1938
+ /**
1939
+ * Whether the search box empties after each selection. Left undefined so the engine keeps its
1940
+ * own rule — it falls back to `closeOnSelect`, which is right until the two are wanted apart:
1941
+ * a tag field that stays open to take the next value still has to clear the term it just used.
1942
+ */
1943
+ readonly clearSearchOnAdd: _angular_core.InputSignal<boolean | undefined>;
1926
1944
  /**
1927
1945
  * Keep the placeholder visible even when a value is selected. Defaults to `false` (the
1928
1946
  * placeholder hides on selection) — ng-select v23 defaults this to `true`, which is not the
@@ -1977,8 +1995,17 @@ declare class HubSelectComponent extends HubFieldControl {
1977
1995
  readonly classlist: _angular_core.InputSignal<string>;
1978
1996
  /** ARIA attributes forwarded to the vendor's combobox search input. */
1979
1997
  protected readonly a11yInputAttrs: _angular_core.Signal<Record<string, string>>;
1980
- /** Emits whenever the value changes. */
1998
+ /** Emits whenever the value changes. Carries the bound value, the one the form holds. */
1981
1999
  readonly valueChange: _angular_core.OutputEmitterRef<any>;
2000
+ /**
2001
+ * Emits the selected ITEM — the whole object, not the bound value — whenever the selection
2002
+ * changes, which is what `ng-select` has always emitted under this name.
2003
+ *
2004
+ * It exists as a real output because otherwise `(change)` on `<hub-select>` is a DOM listener:
2005
+ * it caught whatever the inner search input happened to bubble, never fired for a value added
2006
+ * by an asynchronous `addTag`, and compiled without a word of warning either way.
2007
+ */
2008
+ readonly change: _angular_core.OutputEmitterRef<any>;
1982
2009
  /** Emits when the control gains focus (dropdown format). */
1983
2010
  readonly onFocus: _angular_core.OutputEmitterRef<any>;
1984
2011
  /** Emits when the control loses focus (dropdown format). */
@@ -2079,7 +2106,7 @@ declare class HubSelectComponent extends HubFieldControl {
2079
2106
  */
2080
2107
  protected toggleItem(item: any): void;
2081
2108
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubSelectComponent, never>;
2082
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubSelectComponent, "hub-select", never, { "prepend": { "alias": "prepend"; "required": false; "isSignal": true; }; "append": { "alias": "append"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "vertical": { "alias": "vertical"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "bindLabel": { "alias": "bindLabel"; "required": false; "isSignal": true; }; "bindValue": { "alias": "bindValue"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "searchFn": { "alias": "searchFn"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelType": { "alias": "labelType"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "fixedPlaceholder": { "alias": "fixedPlaceholder"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "notFoundText": { "alias": "notFoundText"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "addTag": { "alias": "addTag"; "required": false; "isSignal": true; }; "addTagText": { "alias": "addTagText"; "required": false; "isSignal": true; }; "minTermLength": { "alias": "minTermLength"; "required": false; "isSignal": true; }; "typeahead": { "alias": "typeahead"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; "classlist": { "alias": "classlist"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "onFocus": "onFocus"; "onBlur": "onBlur"; "onOpen": "onOpen"; "onClose": "onClose"; "onClear": "onClear"; "onSearch": "onSearch"; "onAdd": "onAdd"; "onRemove": "onRemove"; "scroll": "scroll"; "scrollToEnd": "scrollToEnd"; }, ["_hubOptionTpl", "_ngOptionTpl", "_hubOptgroupTpl", "_ngOptgroupTpl", "_hubLabelTpl", "_ngLabelTpl", "_hubMultiLabelTpl", "_ngMultiLabelTpl", "_hubHeaderTpl", "_ngHeaderTpl", "_hubFooterTpl", "_ngFooterTpl", "_hubNotFoundTpl", "_ngNotFoundTpl", "_hubTypeToSearchTpl", "_ngTypeToSearchTpl", "_hubLoadingTextTpl", "_ngLoadingTextTpl", "_hubLoadingSpinnerTpl", "_ngLoadingSpinnerTpl", "_hubTagTpl", "_ngTagTpl", "_hubClearButtonTpl", "_ngClearButtonTpl", "_legacySuffixTpl", "_prependTpl", "_genericAppendTpl"], ["*"], true, never>;
2109
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubSelectComponent, "hub-select", never, { "prepend": { "alias": "prepend"; "required": false; "isSignal": true; }; "append": { "alias": "append"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "vertical": { "alias": "vertical"; "required": false; "isSignal": true; }; "items": { "alias": "items"; "required": false; "isSignal": true; }; "bindLabel": { "alias": "bindLabel"; "required": false; "isSignal": true; }; "bindValue": { "alias": "bindValue"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "searchFn": { "alias": "searchFn"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelType": { "alias": "labelType"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "clearSearchOnAdd": { "alias": "clearSearchOnAdd"; "required": false; "isSignal": true; }; "fixedPlaceholder": { "alias": "fixedPlaceholder"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "notFoundText": { "alias": "notFoundText"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "addTag": { "alias": "addTag"; "required": false; "isSignal": true; }; "addTagText": { "alias": "addTagText"; "required": false; "isSignal": true; }; "minTermLength": { "alias": "minTermLength"; "required": false; "isSignal": true; }; "typeahead": { "alias": "typeahead"; "required": false; "isSignal": true; }; "compareWith": { "alias": "compareWith"; "required": false; "isSignal": true; }; "classlist": { "alias": "classlist"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "change": "change"; "onFocus": "onFocus"; "onBlur": "onBlur"; "onOpen": "onOpen"; "onClose": "onClose"; "onClear": "onClear"; "onSearch": "onSearch"; "onAdd": "onAdd"; "onRemove": "onRemove"; "scroll": "scroll"; "scrollToEnd": "scrollToEnd"; }, ["_hubOptionTpl", "_ngOptionTpl", "_hubOptgroupTpl", "_ngOptgroupTpl", "_hubLabelTpl", "_ngLabelTpl", "_hubMultiLabelTpl", "_ngMultiLabelTpl", "_hubHeaderTpl", "_ngHeaderTpl", "_hubFooterTpl", "_ngFooterTpl", "_hubNotFoundTpl", "_ngNotFoundTpl", "_hubTypeToSearchTpl", "_ngTypeToSearchTpl", "_hubLoadingTextTpl", "_ngLoadingTextTpl", "_hubLoadingSpinnerTpl", "_ngLoadingSpinnerTpl", "_hubTagTpl", "_ngTagTpl", "_hubClearButtonTpl", "_ngClearButtonTpl", "_legacySuffixTpl", "_prependTpl", "_genericAppendTpl"], ["*"], true, never>;
2083
2110
  }
2084
2111
 
2085
2112
  /** A calendar cell enriched with its selection / range / disabled / focus state. */
@@ -2743,6 +2770,21 @@ declare class HubFileInputComponent extends HubFieldControl {
2743
2770
  * the tiles form a grid whose last tile adds more files.
2744
2771
  */
2745
2772
  readonly preview: _angular_core.InputSignal<HubFilePreview>;
2773
+ /**
2774
+ * How much room the empty field takes: the tall dropzone, or one row.
2775
+ *
2776
+ * `compact` is for the field that is not the point of the screen — a logo among four text
2777
+ * inputs, where the panel and its glyph took half a dialog for one small file. It drops the
2778
+ * glyph and the drag copy and keeps the browse control and the constraints; the drop still
2779
+ * works, so the copy is what goes, not the behaviour. An inline grid's add tile is unaffected:
2780
+ * it is already a tile and has no panel to shrink.
2781
+ *
2782
+ * It is the answer for a field that only picks. A field that has to SHOW what it holds gets
2783
+ * its height from `preview="inline"` instead, which needs room for the picture and is sized
2784
+ * through its own `--hub-file-input-inline-*` tokens — the avatar in the inline example is the
2785
+ * compact version of that shape. Asking for both leaves the inline geometry in charge.
2786
+ */
2787
+ readonly appearance: _angular_core.InputSignal<HubFileInputAppearance>;
2746
2788
  /**
2747
2789
  * The files the record already has, shown by `preview="inline"` among the picked ones: a URL,
2748
2790
  * whose name and type are read from the URL itself, a {@link HubCurrentFile} when the URL does not
@@ -2830,6 +2872,8 @@ declare class HubFileInputComponent extends HubFieldControl {
2830
2872
  protected readonly _canChange: _angular_core.Signal<boolean>;
2831
2873
  /** Whether the user may remove what the field holds. */
2832
2874
  protected readonly _canRemove: _angular_core.Signal<boolean>;
2875
+ /** Whether the empty field is drawn as one row rather than as a panel. */
2876
+ protected readonly _isCompact: _angular_core.Signal<boolean>;
2833
2877
  /** Whether the files sit inside the field (`preview="inline"`), single or multiple. */
2834
2878
  protected readonly _isInline: _angular_core.Signal<boolean>;
2835
2879
  /** One file filling the whole field. */
@@ -3013,7 +3057,28 @@ declare class HubFileInputComponent extends HubFieldControl {
3013
3057
  */
3014
3058
  protected guardNativeClick(event: Event): void;
3015
3059
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubFileInputComponent, never>;
3016
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubFileInputComponent, "hub-file-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelType": { "alias": "labelType"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "minSize": { "alias": "minSize"; "required": false; "isSignal": true; }; "maxTotalSize": { "alias": "maxTotalSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "dragDrop": { "alias": "dragDrop"; "required": false; "isSignal": true; }; "paste": { "alias": "paste"; "required": false; "isSignal": true; }; "preview": { "alias": "preview"; "required": false; "isSignal": true; }; "currentFile": { "alias": "currentFile"; "required": false; "isSignal": true; }; "imagePreview": { "alias": "imagePreview"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "allowDuplicates": { "alias": "allowDuplicates"; "required": false; "isSignal": true; }; "capture": { "alias": "capture"; "required": false; "isSignal": true; }; "autoUpload": { "alias": "autoUpload"; "required": false; "isSignal": true; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; "isSignal": true; }; "dropText": { "alias": "dropText"; "required": false; "isSignal": true; }; "dropSubtext": { "alias": "dropSubtext"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "classlist": { "alias": "classlist"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "rejected": "rejected"; "fileRemoved": "fileRemoved"; "currentFileRemoved": "currentFileRemoved"; "uploadStateChange": "uploadStateChange"; }, ["iconTpt", "previewTpt", "noticeTpt"], never, true, never>;
3060
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubFileInputComponent, "hub-file-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "labelType": { "alias": "labelType"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "minSize": { "alias": "minSize"; "required": false; "isSignal": true; }; "maxTotalSize": { "alias": "maxTotalSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "dragDrop": { "alias": "dragDrop"; "required": false; "isSignal": true; }; "paste": { "alias": "paste"; "required": false; "isSignal": true; }; "preview": { "alias": "preview"; "required": false; "isSignal": true; }; "appearance": { "alias": "appearance"; "required": false; "isSignal": true; }; "currentFile": { "alias": "currentFile"; "required": false; "isSignal": true; }; "imagePreview": { "alias": "imagePreview"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "allowDuplicates": { "alias": "allowDuplicates"; "required": false; "isSignal": true; }; "capture": { "alias": "capture"; "required": false; "isSignal": true; }; "autoUpload": { "alias": "autoUpload"; "required": false; "isSignal": true; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; "isSignal": true; }; "dropText": { "alias": "dropText"; "required": false; "isSignal": true; }; "dropSubtext": { "alias": "dropSubtext"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "classlist": { "alias": "classlist"; "required": false; "isSignal": true; }; }, { "valueChange": "valueChange"; "rejected": "rejected"; "fileRemoved": "fileRemoved"; "currentFileRemoved": "currentFileRemoved"; "uploadStateChange": "uploadStateChange"; }, ["iconTpt", "previewTpt", "noticeTpt"], never, true, never>;
3061
+ }
3062
+
3063
+ /**
3064
+ * The card a {@link HubFileDropDirective} raises while files are being dragged over its target.
3065
+ *
3066
+ * Created by the directive rather than written by the consumer, so turning a page into a drop
3067
+ * zone stays one attribute. It is `aria-hidden` on purpose: dragging a file is a pointer gesture
3068
+ * and there is nothing here for a screen reader to do — the file field the drop feeds is what
3069
+ * answers to the keyboard, and it is still there.
3070
+ */
3071
+ declare class HubFileDropOverlayComponent {
3072
+ /** Whether a drag is currently over the target. */
3073
+ readonly visible: _angular_core.InputSignal<boolean>;
3074
+ /** Whether the card covers the viewport (window scope) rather than its container. */
3075
+ readonly fixed: _angular_core.InputSignal<boolean>;
3076
+ /** The invitation. */
3077
+ readonly text: _angular_core.InputSignal<string>;
3078
+ /** The second line, usually the restrictions. */
3079
+ readonly subtext: _angular_core.InputSignal<string>;
3080
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubFileDropOverlayComponent, never>;
3081
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<HubFileDropOverlayComponent, "hub-file-drop-overlay", never, { "visible": { "alias": "visible"; "required": false; "isSignal": true; }; "fixed": { "alias": "fixed"; "required": false; "isSignal": true; }; "text": { "alias": "text"; "required": false; "isSignal": true; }; "subtext": { "alias": "subtext"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
3017
3082
  }
3018
3083
 
3019
3084
  /**
@@ -3438,6 +3503,69 @@ declare class HubSelectSuffixDirective {
3438
3503
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HubSelectSuffixDirective, "[hubSelectSuffix]", never, {}, {}, never, never, true, never>;
3439
3504
  }
3440
3505
 
3506
+ /** What the drag is watched on: the element carrying the directive, or the whole window. */
3507
+ type HubFileDropScope = 'element' | 'window';
3508
+ /**
3509
+ * Turns a container — or the whole window — into a zone that takes dropped files.
3510
+ *
3511
+ * `<hub-file-input>` only accepts a drop inside its own box, which is right for a field and
3512
+ * wrong for a screen: on a list of expenses the natural gesture is to drop the receipt anywhere
3513
+ * on the page, and nothing in the library listened at that level. So every application that
3514
+ * wanted it wrote the same component again — the window listeners, the card over the page, the
3515
+ * type and size checks by hand — and got the same three details wrong, the worst of which is
3516
+ * silent: a file dropped outside a drop target makes the browser navigate away from the page,
3517
+ * unsaved form and all.
3518
+ *
3519
+ * The directive raises its own card while the drag is over the target, applies the same
3520
+ * constraints a file field applies (`accept`, `maxSize`, `minSize`, `maxFiles`) with the same
3521
+ * rejection shape, and hands back what passed.
3522
+ *
3523
+ * It is an addition to a file field and not a replacement: dragging is a pointer gesture, so the
3524
+ * card is `aria-hidden` and the keyboard route stays the field's.
3525
+ *
3526
+ * @example
3527
+ * ```html
3528
+ * <section hubFileDrop="window" accept="image/*" [maxSize]="5_000_000"
3529
+ * (filesDropped)="attach($event)" (rejected)="warn($event)">
3530
+ * …the screen…
3531
+ * </section>
3532
+ * ```
3533
+ */
3534
+ declare class HubFileDropDirective {
3535
+ #private;
3536
+ /**
3537
+ * Where the drag is watched. `element` is the host and its subtree; `window` is the whole
3538
+ * page, which is the one that also stops the browser from opening a file dropped beside the
3539
+ * target instead of on it.
3540
+ */
3541
+ readonly hubFileDrop: _angular_core.InputSignal<"" | HubFileDropScope>;
3542
+ /** Accepted types, in the same grammar as a file field's `accept`. */
3543
+ readonly accept: _angular_core.InputSignal<string>;
3544
+ /** Largest accepted file, in bytes. */
3545
+ readonly maxSize: _angular_core.InputSignal<number | null>;
3546
+ /** Smallest accepted file, in bytes. */
3547
+ readonly minSize: _angular_core.InputSignal<number | null>;
3548
+ /** How many files one drop may carry. Anything beyond it is rejected, not silently dropped. */
3549
+ readonly maxFiles: _angular_core.InputSignalWithTransform<number | null, unknown>;
3550
+ /** Whether the zone is currently accepting drops. */
3551
+ readonly dropDisabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
3552
+ /** Whether the card is drawn. Off leaves the behaviour and lets the consumer draw its own. */
3553
+ readonly overlay: _angular_core.InputSignalWithTransform<boolean, unknown>;
3554
+ /** The card's invitation. Falls back to the global file-input labels. */
3555
+ readonly dropText: _angular_core.InputSignal<string | null>;
3556
+ /** The card's second line. Falls back to the constraints, written out. */
3557
+ readonly dropSubtext: _angular_core.InputSignal<string | null>;
3558
+ /** The files that passed every constraint. Never emitted empty. */
3559
+ readonly filesDropped: _angular_core.OutputEmitterRef<File[]>;
3560
+ /** The files that did not, each with the reason and the limit it broke. */
3561
+ readonly rejected: _angular_core.OutputEmitterRef<HubFileRejection[]>;
3562
+ /** Whether a file drag is currently over the target. */
3563
+ readonly dragActive: _angular_core.WritableSignal<boolean>;
3564
+ constructor();
3565
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<HubFileDropDirective, never>;
3566
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<HubFileDropDirective, "[hubFileDrop]", never, { "hubFileDrop": { "alias": "hubFileDrop"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "minSize": { "alias": "minSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "dropDisabled": { "alias": "dropDisabled"; "required": false; "isSignal": true; }; "overlay": { "alias": "overlay"; "required": false; "isSignal": true; }; "dropText": { "alias": "dropText"; "required": false; "isSignal": true; }; "dropSubtext": { "alias": "dropSubtext"; "required": false; "isSignal": true; }; }, { "filesDropped": "filesDropped"; "rejected": "rejected"; }, never, never, true, never>;
3567
+ }
3568
+
3441
3569
  /**
3442
3570
  * Inverts a colour, or returns the best contrasting black/white when `bw` is enabled.
3443
3571
  */
@@ -3891,5 +4019,5 @@ interface HubFormControlAdapter {
3891
4019
  */
3892
4020
  declare const hubFormControlAdapter: HubFormControlAdapter;
3893
4021
 
3894
- export { FormTextTypes, HUB_COLOR_PALETTES, HUB_FILE_UPLOADER, HUB_FORMS_CONFIG, HubAppendDirective, HubAutoresizeDirective, HubDatepickerComponent, HubFieldControl, HubFieldsetComponent, HubFileDropzoneNoticeDirective, HubFileIconDirective, HubFileInputComponent, HubFilePreviewDirective, HubFormComponent, HubFormControl, HubFormTextDirective, HubGroupControl, HubInputComponent, HubInputFormats, HubInputPrefixDirective, HubInputSuffixDirective, HubInvertColorPipe, HubJoinButLastPipe, HubLabelTypes, HubLegendComponent, HubLegendDirective, HubMapPipe, HubOtpInputComponent, HubPrependDirective, HubSafeUrlPipe, HubSegmentedComponent, HubSegmentedOptionDirective, HubSelectClearButtonDirective, HubSelectComponent, HubSelectFooterDirective, HubSelectFormats, HubSelectHeaderDirective, HubSelectLabelDirective, HubSelectLoadingSpinnerDirective, HubSelectLoadingTextDirective, HubSelectMultiLabelDirective, HubSelectNotFoundDirective, HubSelectOptgroupDirective, HubSelectOptionDirective, HubSelectSuffixDirective, HubSelectTagDirective, HubSelectTypeToSearchDirective, HubSliderComponent, HubSnakeUpperPipe, HubTextareaComponent, HubTimepickerComponent, HubUcfirstPipe, HubValidationErrorDirective, NgClearButtonTemplateDirective, NgFooterTemplateDirective, NgHeaderTemplateDirective, NgLabelTemplateDirective, NgLoadingSpinnerTemplateDirective, NgLoadingTextTemplateDirective, NgMultiLabelTemplateDirective, NgNotFoundTemplateDirective, NgOptgroupTemplateDirective, NgOptionComponent, NgOptionTemplateDirective, NgSelectConfig, NgTagTemplateDirective, NgTypeToSearchTemplateDirective, applyMask, areEqual, camelToSnakeUpper, controlHasMinOrMaxValidator, defaultHubColorConfig, defaultHubDatepickerConfig, defaultHubDatepickerLabels, defaultHubFileInputLabels, defaultHubFormsConfig, defaultHubPasswordLabels, defaultInvalidFeedback, fileKey, fileKind, formatFileSize, get, getActiveElement, getMinOrMaxValueFromValidator, hubAcceptedFiles, hubAreEqual, hubFormControlAdapter, hubMaxFileSize, hubMaxFiles, hubMaxTotalSize, hubMinFileSize, hubMinFiles, isDefined, isMaskActive, isString, joinButLast, matchesAccept, provideHubFileUploader, provideHubForms, runInZone, scorePasswordStrength, toFileArray, uuid };
3895
- export type { FormTextType, HubColorConfig, HubColorPaletteName, HubColorSwatch, HubColorSwatchInput, HubCurrentFile, HubDateRange, HubDateValue, HubDatepickerConfig, HubDatepickerGranularity, HubDatepickerLabels, HubDatepickerMode, HubDatepickerValueFormat, HubFileConstraints, HubFileIconContext, HubFileInputLabels, HubFileItem, HubFileKind, HubFilePreview, HubFilePreviewContext, HubFileRejection, HubFileRejectionReason, HubFileStatus, HubFileUploadEvent, HubFileUploader, HubFileValue, HubFormControlAdapter, HubFormControlConfig, HubFormControlHandle, HubFormControlOption, HubFormsConfig, HubGroupErrorTrigger, HubInputFormat, HubLabelType, HubMaskResult, HubOtpMode, HubPasswordLabels, HubPasswordStrengthScore, HubSegmentedOption, HubSegmentedOptionContext, HubSegmentedSize, HubSelectFormat, HubSliderValue };
4022
+ export { FormTextTypes, HUB_COLOR_PALETTES, HUB_FILE_UPLOADER, HUB_FORMS_CONFIG, HubAppendDirective, HubAutoresizeDirective, HubDatepickerComponent, HubFieldControl, HubFieldsetComponent, HubFileDropDirective, HubFileDropOverlayComponent, HubFileDropzoneNoticeDirective, HubFileIconDirective, HubFileInputComponent, HubFilePreviewDirective, HubFormComponent, HubFormControl, HubFormTextDirective, HubGroupControl, HubInputComponent, HubInputFormats, HubInputPrefixDirective, HubInputSuffixDirective, HubInvertColorPipe, HubJoinButLastPipe, HubLabelTypes, HubLegendComponent, HubLegendDirective, HubMapPipe, HubOtpInputComponent, HubPrependDirective, HubSafeUrlPipe, HubSegmentedComponent, HubSegmentedOptionDirective, HubSelectClearButtonDirective, HubSelectComponent, HubSelectFooterDirective, HubSelectFormats, HubSelectHeaderDirective, HubSelectLabelDirective, HubSelectLoadingSpinnerDirective, HubSelectLoadingTextDirective, HubSelectMultiLabelDirective, HubSelectNotFoundDirective, HubSelectOptgroupDirective, HubSelectOptionDirective, HubSelectSuffixDirective, HubSelectTagDirective, HubSelectTypeToSearchDirective, HubSliderComponent, HubSnakeUpperPipe, HubTextareaComponent, HubTimepickerComponent, HubUcfirstPipe, HubValidationErrorDirective, NgClearButtonTemplateDirective, NgFooterTemplateDirective, NgHeaderTemplateDirective, NgLabelTemplateDirective, NgLoadingSpinnerTemplateDirective, NgLoadingTextTemplateDirective, NgMultiLabelTemplateDirective, NgNotFoundTemplateDirective, NgOptgroupTemplateDirective, NgOptionComponent, NgOptionTemplateDirective, NgSelectConfig, NgTagTemplateDirective, NgTypeToSearchTemplateDirective, applyMask, areEqual, camelToSnakeUpper, controlHasMinOrMaxValidator, defaultHubColorConfig, defaultHubDatepickerConfig, defaultHubDatepickerLabels, defaultHubFileInputLabels, defaultHubFormsConfig, defaultHubPasswordLabels, defaultInvalidFeedback, fileKey, fileKind, formatFileSize, get, getActiveElement, getMinOrMaxValueFromValidator, hubAcceptedFiles, hubAreEqual, hubFormControlAdapter, hubMaxFileSize, hubMaxFiles, hubMaxTotalSize, hubMinFileSize, hubMinFiles, isDefined, isMaskActive, isString, joinButLast, matchesAccept, provideHubFileUploader, provideHubForms, runInZone, scorePasswordStrength, toFileArray, uuid };
4023
+ export type { FormTextType, HubColorConfig, HubColorPaletteName, HubColorSwatch, HubColorSwatchInput, HubCurrentFile, HubDateRange, HubDateValue, HubDatepickerConfig, HubDatepickerGranularity, HubDatepickerLabels, HubDatepickerMode, HubDatepickerValueFormat, HubFileConstraints, HubFileDropScope, HubFileIconContext, HubFileInputAppearance, HubFileInputLabels, HubFileItem, HubFileKind, HubFilePreview, HubFilePreviewContext, HubFileRejection, HubFileRejectionReason, HubFileStatus, HubFileUploadEvent, HubFileUploader, HubFileValue, HubFormControlAdapter, HubFormControlConfig, HubFormControlHandle, HubFormControlOption, HubFormsConfig, HubGroupErrorTrigger, HubInputFormat, HubLabelType, HubMaskResult, HubOtpMode, HubPasswordLabels, HubPasswordStrengthScore, HubSegmentedOption, HubSegmentedOptionContext, HubSegmentedSize, HubSelectFormat, HubSliderValue };