ng-hub-ui-forms 22.4.0 → 22.6.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.
Binary file
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "ng-hub-ui-forms",
3
- "version": "22.4.0",
3
+ "version": "22.6.0",
4
4
  "license": "MIT",
5
- "description": "Accessible, signal-based form fields for Angular (input, textarea, slider, select, datepicker) with automatic error display for controls, FormGroups and FormArrays. Reactive Forms today, Signal Forms ready. Part of the ng-hub-ui family.",
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 <carlos.morcillo@me.com> (https://www.carlosmorcillo.com)",
7
7
  "repository": {
8
8
  "type": "git",
@@ -18,6 +18,10 @@
18
18
  "slider",
19
19
  "select",
20
20
  "datepicker",
21
+ "file-input",
22
+ "file-upload",
23
+ "drag-and-drop",
24
+ "dropzone",
21
25
  "validation",
22
26
  "reactive-forms",
23
27
  "signal-forms",
@@ -90,6 +90,90 @@
90
90
  // Input · color format
91
91
  --hub-input-color-size: 2.5rem;
92
92
 
93
+ // ── File input ────────────────────────────────────────────────────────────
94
+ // Dropzone surface
95
+ --hub-file-input-bg: var(--hub-sys-surface-page, #fff);
96
+ --hub-file-input-color: var(--hub-sys-text-primary, #212529);
97
+ --hub-file-input-border-width: var(--hub-ref-border-width, 1px);
98
+ --hub-file-input-border-style: dashed;
99
+ --hub-file-input-border-color: var(--hub-sys-border-color-default, #dee2e6);
100
+ --hub-file-input-border-radius: var(--hub-input-border-radius);
101
+ --hub-file-input-padding-x: var(--hub-ref-space-3, 1rem);
102
+ --hub-file-input-padding-y: var(--hub-ref-space-4, 1.5rem);
103
+ --hub-file-input-min-height: 7rem;
104
+ --hub-file-input-gap: var(--hub-ref-space-2, 0.5rem);
105
+ --hub-file-input-transition: var(--hub-form-transition);
106
+ --hub-file-input-hover-border-color: var(--hub-sys-color-primary, #0d6efd);
107
+ --hub-file-input-hover-bg: var(--hub-sys-surface-elevated, #f8f9fa);
108
+ --hub-file-input-dragover-bg: color-mix(in oklch, var(--hub-sys-color-primary, #0d6efd) 8%, transparent);
109
+ --hub-file-input-dragover-border-color: var(--hub-sys-color-primary, #0d6efd);
110
+ --hub-file-input-focus-border-color: var(--hub-input-focus-border-color);
111
+ --hub-file-input-focus-box-shadow: var(--hub-input-focus-box-shadow);
112
+
113
+ // Dropzone copy
114
+ --hub-file-input-prompt-color: var(--hub-sys-text-muted, #6c757d);
115
+ --hub-file-input-prompt-font-size: var(--hub-ref-font-size-base, 1rem);
116
+ --hub-file-input-browse-color: var(--hub-sys-color-primary, #0d6efd);
117
+ --hub-file-input-browse-font-weight: var(--hub-ref-font-weight-medium, 500);
118
+ --hub-file-input-hint-color: var(--hub-sys-text-muted, #6c757d);
119
+ --hub-file-input-hint-font-size: var(--hub-ref-font-size-sm, 0.875rem);
120
+
121
+ // Glyphs — every icon is a swappable mask, tinted by its `-color` token. Replace the
122
+ // `url(…)` to change the drawing without touching the template; project an
123
+ // `<ng-template hubFileIcon>` to replace the per-file glyph with a component.
124
+ --hub-file-input-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='black' d='M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5'/%3E%3Cpath fill='black' d='M7.646.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 1.707V10.5a.5.5 0 0 1-1 0V1.707L5.354 3.854a.5.5 0 1 1-.708-.708z'/%3E%3C/svg%3E");
125
+ --hub-file-input-icon-size: 2rem;
126
+ --hub-file-input-icon-color: var(--hub-sys-text-muted, #6c757d);
127
+ --hub-file-input-file-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='black' d='M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.414A1.5 1.5 0 0 0 13.56 3.35L10.65.44A1.5 1.5 0 0 0 9.586 0zm5.5 1.5v2a1 1 0 0 0 1 1h2z'/%3E%3C/svg%3E");
128
+ --hub-file-input-file-icon-size: 1.5rem;
129
+ --hub-file-input-file-icon-color: var(--hub-sys-text-muted, #6c757d);
130
+ --hub-file-input-remove-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='black' d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708'/%3E%3C/svg%3E");
131
+ --hub-file-input-cancel-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='black' d='M5 3.5h6a1.5 1.5 0 0 1 1.5 1.5v6a1.5 1.5 0 0 1-1.5 1.5H5A1.5 1.5 0 0 1 3.5 11V5A1.5 1.5 0 0 1 5 3.5'/%3E%3C/svg%3E");
132
+ --hub-file-input-retry-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='black' d='M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2z'/%3E%3Cpath fill='black' d='M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466'/%3E%3C/svg%3E");
133
+ --hub-file-input-done-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='black' stroke-width='2' d='M3 8.5 6.5 12 13 4.5'/%3E%3C/svg%3E");
134
+ --hub-file-input-done-color: var(--hub-sys-color-success, #198754);
135
+
136
+ // Per-file action buttons
137
+ --hub-file-input-action-size: 1rem;
138
+ --hub-file-input-action-color: var(--hub-sys-text-muted, #6c757d);
139
+ --hub-file-input-action-hover-color: var(--hub-sys-text-primary, #212529);
140
+ --hub-file-input-remove-hover-color: var(--hub-sys-color-danger, #dc3545);
141
+
142
+ // File list
143
+ --hub-file-input-list-gap: var(--hub-ref-space-2, 0.5rem);
144
+ --hub-file-input-list-margin-top: var(--hub-ref-space-2, 0.5rem);
145
+ --hub-file-input-item-bg: var(--hub-sys-surface-elevated, #f8f9fa);
146
+ --hub-file-input-item-color: var(--hub-sys-text-primary, #212529);
147
+ --hub-file-input-item-border-color: var(--hub-sys-border-color-default, #dee2e6);
148
+ --hub-file-input-item-border-radius: var(--hub-ref-radius-sm, 0.25rem);
149
+ --hub-file-input-item-padding-x: var(--hub-ref-space-2, 0.5rem);
150
+ --hub-file-input-item-padding-y: var(--hub-ref-space-2, 0.5rem);
151
+ --hub-file-input-item-gap: var(--hub-ref-space-2, 0.5rem);
152
+ --hub-file-input-name-font-size: var(--hub-ref-font-size-sm, 0.875rem);
153
+ --hub-file-input-size-color: var(--hub-sys-text-muted, #6c757d);
154
+ --hub-file-input-size-font-size: var(--hub-ref-font-size-sm, 0.875rem);
155
+ --hub-file-input-error-color: var(--hub-sys-color-danger, #dc3545);
156
+ --hub-file-input-error-font-size: var(--hub-ref-font-size-sm, 0.875rem);
157
+ --hub-file-input-clear-color: var(--hub-sys-text-muted, #6c757d);
158
+ --hub-file-input-clear-font-size: var(--hub-ref-font-size-sm, 0.875rem);
159
+
160
+ // Thumbnail
161
+ --hub-file-input-thumb-size: 2.5rem;
162
+ --hub-file-input-thumb-radius: var(--hub-ref-radius-sm, 0.25rem);
163
+ --hub-file-input-thumb-bg: var(--hub-sys-surface-page, #fff);
164
+
165
+ // Grid preview
166
+ --hub-file-input-grid-min-width: 8rem;
167
+ --hub-file-input-grid-gap: var(--hub-ref-space-2, 0.5rem);
168
+ --hub-file-input-grid-thumb-height: 6rem;
169
+
170
+ // Upload progress
171
+ --hub-file-input-progress-height: 0.25rem;
172
+ --hub-file-input-progress-radius: var(--hub-ref-radius-pill, 50rem);
173
+ --hub-file-input-progress-track-bg: var(--hub-sys-border-color-default, #dee2e6);
174
+ --hub-file-input-progress-bar-bg: var(--hub-sys-color-primary, #0d6efd);
175
+ --hub-file-input-progress-indeterminate-duration: 1.2s;
176
+
93
177
  // ── Textarea (inherits input) ──────────────────────────────────────────────
94
178
  --hub-textarea-padding-x: var(--hub-input-padding-x);
95
179
  --hub-textarea-padding-y: var(--hub-input-padding-y);
@@ -221,11 +305,11 @@
221
305
 
222
306
  // ── Segmented ───────────────────────────────────────────────────────────────
223
307
  --hub-segmented-bg: var(--hub-sys-surface-elevated, #f8f9fa);
224
- // Optional single-slot accent. Its default (transparent) contributes nothing, so the selected
225
- // pill keeps the neutral white surface below; set it — or apply a `[color]` / `data-variant` —
226
- // to tint the pill. A custom variant re-bases `--hub-segmented-selected-bg` (mirrors the
227
- // `--hub-table-accent` model), which both the sliding indicator and the multiple-mode pill read.
228
- --hub-segmented-accent: transparent;
308
+ // Single-slot accent (mirrors `--hub-table-accent` / `--hub-btn-accent`). When a `[color]` is
309
+ // applied it is set — from a ds token or a literal colour — and the selected pill derives its
310
+ // surface and contrast text from it (see the `[data-variant]` rule in the component styles).
311
+ // With no `[color]` the neutral defaults below keep the white-pill look.
312
+ --hub-segmented-accent: var(--hub-sys-color-primary, #0d6efd);
229
313
  --hub-segmented-selected-bg: var(--hub-sys-surface-page, #fff);
230
314
  --hub-segmented-selected-color: var(--hub-sys-text-primary, #212529);
231
315
  --hub-segmented-radius: var(--hub-ref-radius-md, 0.375rem);
@@ -13,4 +13,7 @@
13
13
  @use 'field';
14
14
 
15
15
  // One-call theming mixins (opt-in; forwarded so consumers can `@use` them from the package root).
16
+ // `hub-forms-theme` themes the shared field chrome; the others tune a single control.
17
+ @forward 'mixins/forms-theme';
16
18
  @forward 'mixins/segmented-theme';
19
+ @forward 'mixins/file-input-theme';
@@ -0,0 +1,199 @@
1
+ // ─────────────────────────────────────────────────────────────────────────
2
+ // hub-file-input-theme — one-call theming for `<hub-file-input>`
3
+ // ─────────────────────────────────────────────────────────────────────────
4
+ //
5
+ // Override any of the `--hub-file-input-*` design tokens in a single include.
6
+ // Every parameter is OPTIONAL and defaults to `null`: only the parameters you
7
+ // pass are emitted, so the rest keep the component's defaults. Token-based and
8
+ // self-contained (no Bootstrap dependencies).
9
+ //
10
+ // The glyph parameters take a CSS `<image>` — typically a `url("data:image/svg+xml,…")`
11
+ // — used as a MASK, so the drawing is tinted by its matching colour token and needs
12
+ // no fill of its own. To render a real icon component instead, project an
13
+ // `<ng-template hubFileIcon>` into the field.
14
+ //
15
+ // Apply it inside any selector that wraps the field — the host element, a
16
+ // container, a route, or `:root` for a global theme.
17
+ //
18
+ // @example — a brand-accented dropzone with a solid border and a custom upload glyph:
19
+ //
20
+ // @use 'ng-hub-ui-forms/styles' as *;
21
+ //
22
+ // .attachments {
23
+ // @include hub-file-input-theme(
24
+ // $border-style: solid,
25
+ // $border-radius: 0.75rem,
26
+ // $dragover-border-color: var(--hub-sys-color-brand),
27
+ // $browse-color: var(--hub-sys-color-brand),
28
+ // $icon: url('/assets/upload.svg'),
29
+ // $icon-color: var(--hub-sys-color-brand),
30
+ // $progress-bar-bg: var(--hub-sys-color-brand)
31
+ // );
32
+ // }
33
+ //
34
+ // scss-docs-start hub-file-input-theme
35
+ @mixin hub-file-input-theme(
36
+ // ── Dropzone surface ──
37
+ $bg: null, // dropzone background
38
+ $color: null, // dropzone text colour
39
+ $border-width: null, // dropzone border width
40
+ $border-style: null, // dropzone border style (defaults to `dashed`)
41
+ $border-color: null, // dropzone border colour at rest
42
+ $border-radius: null, // dropzone corner radius
43
+ $padding-x: null, // dropzone horizontal padding
44
+ $padding-y: null, // dropzone vertical padding
45
+ $min-height: null, // dropzone minimum height
46
+ $gap: null, // gutter between icon, prompt and hint
47
+ // ── States ──
48
+ $hover-bg: null, // background on hover
49
+ $hover-border-color: null, // border colour on hover
50
+ $dragover-bg: null, // background while a drag hovers the zone
51
+ $dragover-border-color: null, // border colour while a drag hovers the zone
52
+ // ── Copy ──
53
+ $prompt-color: null, // "drag files here" colour
54
+ $browse-color: null, // "browse files" colour
55
+ $hint-color: null, // constraints-hint colour
56
+ // ── Glyphs (CSS masks) ──
57
+ $icon: null, // upload glyph shown in the dropzone
58
+ $icon-size: null, // upload glyph size
59
+ $icon-color: null, // upload glyph tint
60
+ $file-icon: null, // per-file glyph, for files with no thumbnail
61
+ $file-icon-color: null, // per-file glyph tint
62
+ $remove-icon: null, // remove-button glyph
63
+ $cancel-icon: null, // cancel-upload glyph
64
+ $retry-icon: null, // retry-upload glyph
65
+ $done-icon: null, // finished-upload tick
66
+ $done-color: null, // finished-upload tick + item border colour
67
+ // ── File list ──
68
+ $item-bg: null, // background of a file row / tile
69
+ $item-border-color: null, // border of a file row / tile
70
+ $item-border-radius: null, // corner radius of a file row / tile
71
+ $thumb-size: null, // thumbnail size in list preview
72
+ $grid-min-width: null, // minimum tile width in grid preview
73
+ $grid-thumb-height: null, // thumbnail height in grid preview
74
+ // ── Upload progress ──
75
+ $progress-height: null, // progress-bar thickness
76
+ $progress-track-bg: null, // progress-bar track colour
77
+ $progress-bar-bg: null // progress-bar fill colour
78
+ ) {
79
+ // Dropzone surface
80
+ @if $bg != null {
81
+ --hub-file-input-bg: #{$bg};
82
+ }
83
+ @if $color != null {
84
+ --hub-file-input-color: #{$color};
85
+ }
86
+ @if $border-width != null {
87
+ --hub-file-input-border-width: #{$border-width};
88
+ }
89
+ @if $border-style != null {
90
+ --hub-file-input-border-style: #{$border-style};
91
+ }
92
+ @if $border-color != null {
93
+ --hub-file-input-border-color: #{$border-color};
94
+ }
95
+ @if $border-radius != null {
96
+ --hub-file-input-border-radius: #{$border-radius};
97
+ }
98
+ @if $padding-x != null {
99
+ --hub-file-input-padding-x: #{$padding-x};
100
+ }
101
+ @if $padding-y != null {
102
+ --hub-file-input-padding-y: #{$padding-y};
103
+ }
104
+ @if $min-height != null {
105
+ --hub-file-input-min-height: #{$min-height};
106
+ }
107
+ @if $gap != null {
108
+ --hub-file-input-gap: #{$gap};
109
+ }
110
+
111
+ // States
112
+ @if $hover-bg != null {
113
+ --hub-file-input-hover-bg: #{$hover-bg};
114
+ }
115
+ @if $hover-border-color != null {
116
+ --hub-file-input-hover-border-color: #{$hover-border-color};
117
+ }
118
+ @if $dragover-bg != null {
119
+ --hub-file-input-dragover-bg: #{$dragover-bg};
120
+ }
121
+ @if $dragover-border-color != null {
122
+ --hub-file-input-dragover-border-color: #{$dragover-border-color};
123
+ }
124
+
125
+ // Copy
126
+ @if $prompt-color != null {
127
+ --hub-file-input-prompt-color: #{$prompt-color};
128
+ }
129
+ @if $browse-color != null {
130
+ --hub-file-input-browse-color: #{$browse-color};
131
+ }
132
+ @if $hint-color != null {
133
+ --hub-file-input-hint-color: #{$hint-color};
134
+ }
135
+
136
+ // Glyphs
137
+ @if $icon != null {
138
+ --hub-file-input-icon: #{$icon};
139
+ }
140
+ @if $icon-size != null {
141
+ --hub-file-input-icon-size: #{$icon-size};
142
+ }
143
+ @if $icon-color != null {
144
+ --hub-file-input-icon-color: #{$icon-color};
145
+ }
146
+ @if $file-icon != null {
147
+ --hub-file-input-file-icon: #{$file-icon};
148
+ }
149
+ @if $file-icon-color != null {
150
+ --hub-file-input-file-icon-color: #{$file-icon-color};
151
+ }
152
+ @if $remove-icon != null {
153
+ --hub-file-input-remove-icon: #{$remove-icon};
154
+ }
155
+ @if $cancel-icon != null {
156
+ --hub-file-input-cancel-icon: #{$cancel-icon};
157
+ }
158
+ @if $retry-icon != null {
159
+ --hub-file-input-retry-icon: #{$retry-icon};
160
+ }
161
+ @if $done-icon != null {
162
+ --hub-file-input-done-icon: #{$done-icon};
163
+ }
164
+ @if $done-color != null {
165
+ --hub-file-input-done-color: #{$done-color};
166
+ }
167
+
168
+ // File list
169
+ @if $item-bg != null {
170
+ --hub-file-input-item-bg: #{$item-bg};
171
+ }
172
+ @if $item-border-color != null {
173
+ --hub-file-input-item-border-color: #{$item-border-color};
174
+ }
175
+ @if $item-border-radius != null {
176
+ --hub-file-input-item-border-radius: #{$item-border-radius};
177
+ }
178
+ @if $thumb-size != null {
179
+ --hub-file-input-thumb-size: #{$thumb-size};
180
+ }
181
+ @if $grid-min-width != null {
182
+ --hub-file-input-grid-min-width: #{$grid-min-width};
183
+ }
184
+ @if $grid-thumb-height != null {
185
+ --hub-file-input-grid-thumb-height: #{$grid-thumb-height};
186
+ }
187
+
188
+ // Upload progress
189
+ @if $progress-height != null {
190
+ --hub-file-input-progress-height: #{$progress-height};
191
+ }
192
+ @if $progress-track-bg != null {
193
+ --hub-file-input-progress-track-bg: #{$progress-track-bg};
194
+ }
195
+ @if $progress-bar-bg != null {
196
+ --hub-file-input-progress-bar-bg: #{$progress-bar-bg};
197
+ }
198
+ }
199
+ // scss-docs-end hub-file-input-theme
@@ -0,0 +1,67 @@
1
+ // ─────────────────────────────────────────────────────────────────────────
2
+ // hub-forms-theme — one-call theming for the shared `ng-hub-ui-forms` chrome
3
+ // ─────────────────────────────────────────────────────────────────────────
4
+ //
5
+ // Sets the cross-cutting `--hub-form-*` tokens that EVERY form control reads
6
+ // (focus ring, validation accents, disabled state, motion) in a single include.
7
+ // Every parameter is OPTIONAL and defaults to `null`: only the parameters you
8
+ // pass are emitted, so the rest keep the library defaults. Token-based and
9
+ // self-contained (no Bootstrap dependencies). For a specific control, layer the
10
+ // matching component mixin on top (e.g. `hub-segmented-theme`).
11
+ //
12
+ // Apply it inside any selector that wraps the fields — a form, a container, a
13
+ // route, or `:root` for a global theme.
14
+ //
15
+ // @example — brand focus ring + softer disabled state, globally:
16
+ //
17
+ // @use 'ng-hub-ui-forms/styles' as *;
18
+ //
19
+ // :root {
20
+ // @include hub-forms-theme(
21
+ // $focus-ring-color: var(--hub-sys-color-info-subtle),
22
+ // $disabled-opacity: 0.5
23
+ // );
24
+ // }
25
+ //
26
+ // scss-docs-start hub-forms-theme
27
+ @mixin hub-forms-theme(
28
+ // ── Focus ring (shared by every control) ──
29
+ $focus-ring-color: null, // focus-ring colour
30
+ $focus-ring-width: null, // focus-ring width
31
+ // ── Validation accents ──
32
+ $invalid-color: null, // invalid text + border colour
33
+ $valid-color: null, // valid text + border colour
34
+ // ── State ──
35
+ $disabled-opacity: null, // opacity applied to a disabled control
36
+ // ── Motion ──
37
+ $transition: null // border/box-shadow transition shared by inputs
38
+ ) {
39
+ // Focus ring
40
+ @if $focus-ring-color != null {
41
+ --hub-form-focus-ring-color: #{$focus-ring-color};
42
+ }
43
+ @if $focus-ring-width != null {
44
+ --hub-form-focus-ring-width: #{$focus-ring-width};
45
+ }
46
+
47
+ // Validation accents (text + border move together)
48
+ @if $invalid-color != null {
49
+ --hub-form-invalid-color: #{$invalid-color};
50
+ --hub-form-invalid-border-color: #{$invalid-color};
51
+ }
52
+ @if $valid-color != null {
53
+ --hub-form-valid-color: #{$valid-color};
54
+ --hub-form-valid-border-color: #{$valid-color};
55
+ }
56
+
57
+ // State
58
+ @if $disabled-opacity != null {
59
+ --hub-form-disabled-opacity: #{$disabled-opacity};
60
+ }
61
+
62
+ // Motion
63
+ @if $transition != null {
64
+ --hub-form-transition: #{$transition};
65
+ }
66
+ }
67
+ // scss-docs-end hub-forms-theme
@@ -12,7 +12,7 @@
12
12
  //
13
13
  // @example — a compact, brand-accented bar with a rounded frame:
14
14
  //
15
- // @use 'ng-hub-ui-forms/styles/mixins/segmented-theme' as *;
15
+ // @use 'ng-hub-ui-forms/styles' as *;
16
16
  //
17
17
  // .view-switch {
18
18
  // @include hub-segmented-theme(