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.
- package/README.md +71 -3
- package/fesm2022/ng-hub-ui-forms.mjs +1157 -45
- package/fesm2022/ng-hub-ui-forms.mjs.map +1 -1
- package/ng-hub-ui-forms-22.6.0.tgz +0 -0
- package/package.json +6 -2
- package/{src/lib/styles → styles}/_tokens.scss +89 -5
- package/{src/lib/styles → styles}/index.scss +3 -0
- package/styles/mixins/_file-input-theme.scss +199 -0
- package/styles/mixins/_forms-theme.scss +67 -0
- package/{src/lib/styles → styles}/mixins/_segmented-theme.scss +1 -1
- package/types/ng-hub-ui-forms.d.ts +668 -16
- package/ng-hub-ui-forms-22.4.0.tgz +0 -0
- /package/{src/lib/styles → styles}/_field.scss +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, model,
|
|
2
|
+
import { input, model, Directive, inject, TemplateRef, InjectionToken, makeEnvironmentProviders, ElementRef, ChangeDetectorRef, contentChild, contentChildren, signal, computed, effect, numberAttribute, booleanAttribute, output, DestroyRef, viewChild, isDevMode, ViewEncapsulation, ChangeDetectionStrategy, Component, viewChildren, PLATFORM_ID, afterRenderEffect, afterNextRender, Injectable, Renderer2, NgZone, afterEveryRender, HostAttributeToken, linkedSignal, forwardRef, Injector, HostListener, LOCALE_ID, Pipe } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormControl, Validators, NgControl, ControlContainer, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
5
|
import { Observable, Subject, takeUntil, tap, animationFrameScheduler, asapScheduler, fromEvent } from 'rxjs';
|
|
@@ -258,14 +258,16 @@ class HubFormControl {
|
|
|
258
258
|
control['toggle'] = this.toggle;
|
|
259
259
|
}
|
|
260
260
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFormControl, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
261
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: HubFormControl, isStandalone: true, inputs: { formControlName: { classPropertyName: "formControlName", publicName: "formControlName", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { required: "requiredChange" }, host: { properties: { "class.hidden-control": "
|
|
261
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.1", type: HubFormControl, isStandalone: true, inputs: { formControlName: { classPropertyName: "formControlName", publicName: "formControlName", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { required: "requiredChange" }, host: { properties: { "class.hidden-control": "hidden" } }, ngImport: i0 });
|
|
262
262
|
}
|
|
263
263
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFormControl, decorators: [{
|
|
264
|
-
type: Directive
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
264
|
+
type: Directive,
|
|
265
|
+
args: [{
|
|
266
|
+
host: {
|
|
267
|
+
'[class.hidden-control]': 'hidden'
|
|
268
|
+
}
|
|
269
|
+
}]
|
|
270
|
+
}], propDecorators: { formControlName: [{ type: i0.Input, args: [{ isSignal: true, alias: "formControlName", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }, { type: i0.Output, args: ["requiredChange"] }] } });
|
|
269
271
|
|
|
270
272
|
/**
|
|
271
273
|
* Marks a template as form helper text projected into a form field.
|
|
@@ -338,6 +340,78 @@ const defaultHubDatepickerConfig = {
|
|
|
338
340
|
labels: defaultHubDatepickerLabels
|
|
339
341
|
};
|
|
340
342
|
|
|
343
|
+
/** Binary size units, ascending. */
|
|
344
|
+
const FILE_SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
345
|
+
/**
|
|
346
|
+
* Formats a byte count as human-readable text using binary multiples (1 KB = 1024 B).
|
|
347
|
+
*
|
|
348
|
+
* Values below 10 in their unit keep one decimal (`1.4 MB`); larger ones are rounded (`24 MB`).
|
|
349
|
+
* The decimal separator follows the given locale, or the runtime's default when omitted.
|
|
350
|
+
*
|
|
351
|
+
* @param bytes - The size in bytes.
|
|
352
|
+
* @param locale - BCP 47 locale used to format the number. Defaults to the runtime locale.
|
|
353
|
+
* @returns The formatted size, or an empty string when `bytes` is not a finite, non-negative number.
|
|
354
|
+
*/
|
|
355
|
+
function formatFileSize(bytes, locale) {
|
|
356
|
+
if (!Number.isFinite(bytes) || bytes < 0) {
|
|
357
|
+
return '';
|
|
358
|
+
}
|
|
359
|
+
if (bytes < 1024) {
|
|
360
|
+
return `${bytes} ${FILE_SIZE_UNITS[0]}`;
|
|
361
|
+
}
|
|
362
|
+
let value = bytes;
|
|
363
|
+
let unit = 0;
|
|
364
|
+
while (value >= 1024 && unit < FILE_SIZE_UNITS.length - 1) {
|
|
365
|
+
value /= 1024;
|
|
366
|
+
unit++;
|
|
367
|
+
}
|
|
368
|
+
const formatted = new Intl.NumberFormat(locale, { maximumFractionDigits: value < 10 ? 1 : 0 }).format(value);
|
|
369
|
+
return `${formatted} ${FILE_SIZE_UNITS[unit]}`;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** Built-in (English) file-input labels. */
|
|
373
|
+
const defaultHubFileInputLabels = {
|
|
374
|
+
browse: 'Browse files',
|
|
375
|
+
dropHere: 'Drag files here, or',
|
|
376
|
+
remove: 'Remove file',
|
|
377
|
+
clear: 'Remove all',
|
|
378
|
+
retry: 'Retry upload',
|
|
379
|
+
cancel: 'Cancel upload',
|
|
380
|
+
filesSelected: (count) => (count === 1 ? '1 file selected' : `${count} files selected`),
|
|
381
|
+
formatSize: (bytes) => formatFileSize(bytes),
|
|
382
|
+
rejection: ({ file, reason, limit }) => {
|
|
383
|
+
switch (reason) {
|
|
384
|
+
case 'accept':
|
|
385
|
+
return `${file.name}: file type not allowed.`;
|
|
386
|
+
case 'maxSize':
|
|
387
|
+
return `${file.name}: exceeds the maximum size of ${formatFileSize(Number(limit))}.`;
|
|
388
|
+
case 'minSize':
|
|
389
|
+
return `${file.name}: below the minimum size of ${formatFileSize(Number(limit))}.`;
|
|
390
|
+
case 'maxFiles':
|
|
391
|
+
return `${file.name}: at most ${limit} files can be selected.`;
|
|
392
|
+
case 'maxTotalSize':
|
|
393
|
+
return `${file.name}: the total size would exceed ${formatFileSize(Number(limit))}.`;
|
|
394
|
+
case 'duplicate':
|
|
395
|
+
return `${file.name}: already selected.`;
|
|
396
|
+
default:
|
|
397
|
+
return `${file.name}: rejected.`;
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
constraints: ({ accept, maxSize, maxFiles }) => {
|
|
401
|
+
const parts = [];
|
|
402
|
+
if (accept) {
|
|
403
|
+
parts.push(accept);
|
|
404
|
+
}
|
|
405
|
+
if (maxSize != null) {
|
|
406
|
+
parts.push(`up to ${formatFileSize(maxSize)} each`);
|
|
407
|
+
}
|
|
408
|
+
if (maxFiles != null) {
|
|
409
|
+
parts.push(maxFiles === 1 ? '1 file max' : `${maxFiles} files max`);
|
|
410
|
+
}
|
|
411
|
+
return parts.join(' · ');
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
|
|
341
415
|
/**
|
|
342
416
|
* Default, human-readable messages for the most common Angular validation errors.
|
|
343
417
|
*
|
|
@@ -351,6 +425,18 @@ const defaultHubDatepickerConfig = {
|
|
|
351
425
|
*/
|
|
352
426
|
function defaultInvalidFeedback(key, value) {
|
|
353
427
|
switch (key) {
|
|
428
|
+
case 'fileAccept':
|
|
429
|
+
return `Only ${value?.accept ?? 'certain'} files are allowed.`;
|
|
430
|
+
case 'fileMaxSize':
|
|
431
|
+
return `Each file must be at most ${formatFileSize(value?.max)}.`;
|
|
432
|
+
case 'fileMinSize':
|
|
433
|
+
return `Each file must be at least ${formatFileSize(value?.min)}.`;
|
|
434
|
+
case 'fileMaxTotalSize':
|
|
435
|
+
return `The files must add up to at most ${formatFileSize(value?.max)}.`;
|
|
436
|
+
case 'fileMaxFiles':
|
|
437
|
+
return `Select at most ${value?.max ?? ''} files.`;
|
|
438
|
+
case 'fileMinFiles':
|
|
439
|
+
return `Select at least ${value?.min ?? ''} files.`;
|
|
354
440
|
case 'required':
|
|
355
441
|
return 'This field is required.';
|
|
356
442
|
case 'email':
|
|
@@ -380,7 +466,8 @@ function defaultInvalidFeedback(key, value) {
|
|
|
380
466
|
const defaultHubFormsConfig = {
|
|
381
467
|
invalidFeedbackTemplateFn: defaultInvalidFeedback,
|
|
382
468
|
showValid: false,
|
|
383
|
-
datepicker: defaultHubDatepickerConfig
|
|
469
|
+
datepicker: defaultHubDatepickerConfig,
|
|
470
|
+
fileInput: defaultHubFileInputLabels
|
|
384
471
|
};
|
|
385
472
|
/**
|
|
386
473
|
* Injection token holding the resolved {@link HubFormsConfig}.
|
|
@@ -415,10 +502,14 @@ function provideHubForms(config) {
|
|
|
415
502
|
...config?.datepicker,
|
|
416
503
|
labels: { ...defaultHubFormsConfig.datepicker.labels, ...config?.datepicker?.labels }
|
|
417
504
|
};
|
|
505
|
+
const fileInput = {
|
|
506
|
+
...defaultHubFormsConfig.fileInput,
|
|
507
|
+
...config?.fileInput
|
|
508
|
+
};
|
|
418
509
|
return makeEnvironmentProviders([
|
|
419
510
|
{
|
|
420
511
|
provide: HUB_FORMS_CONFIG,
|
|
421
|
-
useValue: { ...defaultHubFormsConfig, ...config, datepicker }
|
|
512
|
+
useValue: { ...defaultHubFormsConfig, ...config, datepicker, fileInput }
|
|
422
513
|
}
|
|
423
514
|
]);
|
|
424
515
|
}
|
|
@@ -596,11 +687,13 @@ class HubFieldControl extends HubFormControl {
|
|
|
596
687
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.2.0", version: "22.0.1", type: HubFieldControl, isStandalone: true, inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalidFeedbackTemplateFn: { classPropertyName: "invalidFeedbackTemplateFn", publicName: "invalidFeedbackTemplateFn", isSignal: true, isRequired: false, transformFunction: null }, showValid: { classPropertyName: "showValid", publicName: "showValid", isSignal: true, isRequired: false, transformFunction: null }, validFeedback: { classPropertyName: "validFeedback", publicName: "validFeedback", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { disabled: "disabledChange" }, host: { listeners: { "focusout": "handleBlur($event)" } }, queries: [{ propertyName: "formTextTmp", first: true, predicate: HubFormTextDirective, descendants: true, read: TemplateRef, isSignal: true }, { propertyName: "errorTpts", predicate: HubValidationErrorDirective, isSignal: true }], usesInheritance: true, ngImport: i0 });
|
|
597
688
|
}
|
|
598
689
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFieldControl, decorators: [{
|
|
599
|
-
type: Directive
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
690
|
+
type: Directive,
|
|
691
|
+
args: [{
|
|
692
|
+
host: {
|
|
693
|
+
'(focusout)': 'handleBlur($event)'
|
|
694
|
+
}
|
|
695
|
+
}]
|
|
696
|
+
}], ctorParameters: () => [], propDecorators: { formTextTmp: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HubFormTextDirective), { ...{ read: TemplateRef }, isSignal: true }] }], errorTpts: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HubValidationErrorDirective), { isSignal: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], invalidFeedbackTemplateFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidFeedbackTemplateFn", required: false }] }], showValid: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValid", required: false }] }], validFeedback: [{ type: i0.Input, args: [{ isSignal: true, alias: "validFeedback", required: false }] }] } });
|
|
604
697
|
|
|
605
698
|
/**
|
|
606
699
|
* Shared base for form *containers* (`hub-form`, `hub-fieldset`, `hub-legend`).
|
|
@@ -719,6 +812,42 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
719
812
|
type: Directive
|
|
720
813
|
}], ctorParameters: () => [], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], groupName: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupName", required: false }] }], errorTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorTrigger", required: false }] }], invalidFeedbackTemplateFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalidFeedbackTemplateFn", required: false }] }], errorTpts: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => HubValidationErrorDirective), { isSignal: true }] }] } });
|
|
721
814
|
|
|
815
|
+
/**
|
|
816
|
+
* Injection token holding the {@link HubFileUploader} that `<hub-file-input>` delegates uploads to.
|
|
817
|
+
*
|
|
818
|
+
* It has **no default**: when nothing is registered, the component resolves it as `null` and behaves
|
|
819
|
+
* as a pure picker — no automatic upload, no progress bar, no cancel or retry. Register one with
|
|
820
|
+
* {@link provideHubFileUploader}.
|
|
821
|
+
*/
|
|
822
|
+
const HUB_FILE_UPLOADER = new InjectionToken('HUB_FILE_UPLOADER');
|
|
823
|
+
/**
|
|
824
|
+
* Registers the uploader that `<hub-file-input>` uses to send files.
|
|
825
|
+
*
|
|
826
|
+
* The library defines the contract, not the transport: an uploader that knows your endpoint, field
|
|
827
|
+
* name, headers and retry policy is application code. Implement {@link HubFileUploader} there and
|
|
828
|
+
* hand it over here.
|
|
829
|
+
*
|
|
830
|
+
* Passing a class provides it in the same injector and aliases the token to that single instance, so
|
|
831
|
+
* the uploader can inject `HttpClient` and hold its own state.
|
|
832
|
+
*
|
|
833
|
+
* @param uploader - The uploader class (dependency-injected) or a ready-made instance.
|
|
834
|
+
* @returns Environment providers to add to `bootstrapApplication` (or a route's `providers`).
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* ```ts
|
|
838
|
+
* bootstrapApplication(App, {
|
|
839
|
+
* providers: [provideHttpClient(), provideHubFileUploader(ApiFileUploader)]
|
|
840
|
+
* });
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
function provideHubFileUploader(uploader) {
|
|
844
|
+
if (typeof uploader === 'function') {
|
|
845
|
+
const uploaderType = uploader;
|
|
846
|
+
return makeEnvironmentProviders([uploaderType, { provide: HUB_FILE_UPLOADER, useExisting: uploaderType }]);
|
|
847
|
+
}
|
|
848
|
+
return makeEnvironmentProviders([{ provide: HUB_FILE_UPLOADER, useValue: uploader }]);
|
|
849
|
+
}
|
|
850
|
+
|
|
722
851
|
/**
|
|
723
852
|
* Enum for form helper text display types.
|
|
724
853
|
*/
|
|
@@ -892,9 +1021,13 @@ function applyMask(value, mask) {
|
|
|
892
1021
|
* `invalidFeedbackTemplateFn` input, or projected `hubValidationError` templates.
|
|
893
1022
|
*
|
|
894
1023
|
* Formats: `text`, `number`, `password`, `email`, `tel`, `url`, `color`, `checkbox`, `switch`,
|
|
895
|
-
* `counter`. Label types: `stacked`, `floating`, `horizontal`. Text-like formats support
|
|
896
|
-
* append addons (input groups). Numeric formats auto-attach `min`/`max` validators when
|
|
897
|
-
* reactive control.
|
|
1024
|
+
* `counter` and `file`. Label types: `stacked`, `floating`, `horizontal`. Text-like formats support
|
|
1025
|
+
* prepend / append addons (input groups). Numeric formats auto-attach `min`/`max` validators when
|
|
1026
|
+
* bound to a reactive control.
|
|
1027
|
+
*
|
|
1028
|
+
* The `file` format is **deprecated since 22.6.0** in favour of `<hub-file-input>`: it is a bare
|
|
1029
|
+
* picker that cannot enforce `accept` on a drop, and has no size limits, no preview and no per-file
|
|
1030
|
+
* removal. It keeps working until the next major.
|
|
898
1031
|
*
|
|
899
1032
|
* @example
|
|
900
1033
|
* ```html
|
|
@@ -1001,12 +1134,25 @@ class HubInputComponent extends HubFieldControl {
|
|
|
1001
1134
|
#lastSearchTerm = null;
|
|
1002
1135
|
/** Clears any pending debounce timer when the component is destroyed. */
|
|
1003
1136
|
_searchCleanup = inject(DestroyRef).onDestroy(() => clearTimeout(this.#searchTimer));
|
|
1004
|
-
/**
|
|
1137
|
+
/**
|
|
1138
|
+
* Accepted file types (file format), e.g. `image/*,.pdf`.
|
|
1139
|
+
*
|
|
1140
|
+
* @deprecated Since 22.6.0. Use `<hub-file-input>`, which enforces `accept` on drops and pastes
|
|
1141
|
+
* too. Removed in the next major.
|
|
1142
|
+
*/
|
|
1005
1143
|
accept = input('*', /* @ts-ignore */
|
|
1006
1144
|
...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
1007
|
-
/**
|
|
1145
|
+
/**
|
|
1146
|
+
* Whether multiple files can be selected (file format).
|
|
1147
|
+
*
|
|
1148
|
+
* @deprecated Since 22.6.0. Use `<hub-file-input>`. Removed in the next major.
|
|
1149
|
+
*/
|
|
1008
1150
|
multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1009
|
-
/**
|
|
1151
|
+
/**
|
|
1152
|
+
* Text of the file-picker button (file format).
|
|
1153
|
+
*
|
|
1154
|
+
* @deprecated Since 22.6.0. Use `<hub-file-input>` and its `buttonLabel`. Removed in the next major.
|
|
1155
|
+
*/
|
|
1010
1156
|
buttonLabel = input('Choose file', /* @ts-ignore */
|
|
1011
1157
|
...(ngDevMode ? [{ debugName: "buttonLabel" }] : /* istanbul ignore next */ []));
|
|
1012
1158
|
/** Reference to the hidden native file input. */
|
|
@@ -1063,6 +1209,7 @@ class HubInputComponent extends HubFieldControl {
|
|
|
1063
1209
|
ngAfterContentInit() {
|
|
1064
1210
|
super.ngAfterContentInit();
|
|
1065
1211
|
this.#attachNumericValidators();
|
|
1212
|
+
this.#warnDeprecatedFileFormat();
|
|
1066
1213
|
}
|
|
1067
1214
|
writeValue(value) {
|
|
1068
1215
|
if (this.isCheckable()) {
|
|
@@ -1197,6 +1344,17 @@ class HubInputComponent extends HubFieldControl {
|
|
|
1197
1344
|
this.onChange?.(null);
|
|
1198
1345
|
this.valueChange.emit(null);
|
|
1199
1346
|
}
|
|
1347
|
+
/**
|
|
1348
|
+
* Warns once, in development only, that the `file` format is deprecated.
|
|
1349
|
+
*
|
|
1350
|
+
* It still works, but it cannot enforce `accept` on a drop, has no size limits, no preview and
|
|
1351
|
+
* no per-file removal. `<hub-file-input>` supersedes it.
|
|
1352
|
+
*/
|
|
1353
|
+
#warnDeprecatedFileFormat() {
|
|
1354
|
+
if (isDevMode() && this.type() === this._inputFormats.File) {
|
|
1355
|
+
console.warn('[ng-hub-ui-forms] <hub-input type="file"> is deprecated since 22.6.0 and will be removed in the next major. Use <hub-file-input> instead.');
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1200
1358
|
/**
|
|
1201
1359
|
* Auto-attaches `Validators.min` / `Validators.max` to the bound reactive control when `min` /
|
|
1202
1360
|
* `max` are set and not already present, so the inline bounds become real validation.
|
|
@@ -1593,18 +1751,14 @@ class HubAutoresizeDirective {
|
|
|
1593
1751
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubAutoresizeDirective, decorators: [{
|
|
1594
1752
|
type: Directive,
|
|
1595
1753
|
args: [{
|
|
1596
|
-
selector: 'textarea[hubAutoresize]'
|
|
1754
|
+
selector: 'textarea[hubAutoresize]',
|
|
1755
|
+
host: {
|
|
1756
|
+
'(:focusin)': 'onChange()',
|
|
1757
|
+
'(:focusout)': 'onChange()',
|
|
1758
|
+
'(:input)': 'onChange()'
|
|
1759
|
+
}
|
|
1597
1760
|
}]
|
|
1598
|
-
}], propDecorators: { hubAutoresize: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubAutoresize", required: false }] }]
|
|
1599
|
-
type: HostListener,
|
|
1600
|
-
args: [':focusin']
|
|
1601
|
-
}, {
|
|
1602
|
-
type: HostListener,
|
|
1603
|
-
args: [':focusout']
|
|
1604
|
-
}, {
|
|
1605
|
-
type: HostListener,
|
|
1606
|
-
args: [':input']
|
|
1607
|
-
}] } });
|
|
1761
|
+
}], propDecorators: { hubAutoresize: [{ type: i0.Input, args: [{ isSignal: true, alias: "hubAutoresize", required: false }] }] } });
|
|
1608
1762
|
|
|
1609
1763
|
/**
|
|
1610
1764
|
* Accessible multi-line text field with optional auto-resize, character counter and automatic
|
|
@@ -1816,6 +1970,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
1816
1970
|
}, template: "<div\n\tclass=\"hub-field hub-slider\"\n\t[class.hub-field--horizontal]=\"label() && labelType() === _labelTypes.Horizontal\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n\t[style.--hub-slider-percent]=\"percent()\"\n\t[style.--hub-slider-from]=\"lowerPercent()\"\n\t[style.--hub-slider-to]=\"upperPercent()\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div\n\t\tclass=\"hub-field__body hub-slider__range\"\n\t\t[class.hub-slider__range--dual]=\"range()\"\n\t\t[class.hub-slider__range--flush]=\"!showValue()\"\n\t>\n\t\t@if (range()) {\n\t\t\t<div class=\"hub-slider__rail\" aria-hidden=\"true\"></div>\n\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tclass=\"hub-slider__track hub-slider__track--lower\"\n\t\t\t\t[id]=\"id\"\n\t\t\t\t[min]=\"min()\"\n\t\t\t\t[max]=\"max()\"\n\t\t\t\t[step]=\"step()\"\n\t\t\t\t[value]=\"lower()\"\n\t\t\t\t(input)=\"setLower($any($event.target).value)\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\taria-label=\"Range start\"\n\t\t\t\t[attr.aria-valuetext]=\"lower()\"\n\t\t\t/>\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tclass=\"hub-slider__track hub-slider__track--upper\"\n\t\t\t\t[min]=\"min()\"\n\t\t\t\t[max]=\"max()\"\n\t\t\t\t[step]=\"step()\"\n\t\t\t\t[value]=\"upper()\"\n\t\t\t\t(input)=\"setUpper($any($event.target).value)\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\taria-label=\"Range end\"\n\t\t\t\t[attr.aria-valuetext]=\"upper()\"\n\t\t\t/>\n\n\t\t\t@if (showValue()) {\n\t\t\t\t<span class=\"hub-slider__bubble hub-slider__bubble--lower\" aria-hidden=\"true\">{{ lower() }}</span>\n\t\t\t\t<span class=\"hub-slider__bubble hub-slider__bubble--upper\" aria-hidden=\"true\">{{ upper() }}</span>\n\t\t\t}\n\t\t} @else {\n\t\t\t<input\n\t\t\t\ttype=\"range\"\n\t\t\t\tclass=\"hub-slider__track\"\n\t\t\t\t[id]=\"id\"\n\t\t\t\t[min]=\"min()\"\n\t\t\t\t[max]=\"max()\"\n\t\t\t\t[step]=\"step()\"\n\t\t\t\t[value]=\"single()\"\n\t\t\t\t(input)=\"setValue($any($event.target).value)\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\t[required]=\"!!required()\"\n\t\t\t\t[attr.aria-valuetext]=\"single()\"\n\t\t\t/>\n\n\t\t\t@if (showValue()) {\n\t\t\t\t<span class=\"hub-slider__bubble\" aria-hidden=\"true\">{{ single() }}</span>\n\t\t\t}\n\t\t}\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n</div>\n", styles: ["hub-slider{display:block}hub-slider.hidden-control{display:none!important}.hub-slider__range{position:relative;padding-top:var(--hub-slider-value-space)}.hub-slider__range--flush{--hub-slider-value-space: 0}.hub-slider__track{width:var(--hub-slider-track-width);height:var(--hub-slider-track-height);margin:0;appearance:none;-webkit-appearance:none;background:var(--hub-slider-track-fill) no-repeat,var(--hub-slider-track-bg);background-size:calc(var(--hub-slider-percent) * 1%) 100%,100% 100%;border-radius:var(--hub-slider-track-border-radius);cursor:pointer}.hub-slider__track:disabled{opacity:var(--hub-form-disabled-opacity);cursor:not-allowed}.hub-slider__track::-webkit-slider-thumb{appearance:none;-webkit-appearance:none;width:var(--hub-slider-thumb-width);height:var(--hub-slider-thumb-height);border-radius:var(--hub-slider-thumb-border-radius);background:var(--hub-slider-thumb-bg);border:var(--hub-slider-thumb-border);box-shadow:var(--hub-slider-thumb-shadow);cursor:pointer}.hub-slider__track::-moz-range-thumb{width:var(--hub-slider-thumb-width);height:var(--hub-slider-thumb-height);border-radius:var(--hub-slider-thumb-border-radius);background:var(--hub-slider-thumb-bg);border:var(--hub-slider-thumb-border);box-shadow:var(--hub-slider-thumb-shadow);cursor:pointer}.hub-slider__track::-moz-range-track{height:var(--hub-slider-track-height);background:transparent;border-radius:var(--hub-slider-track-border-radius)}.hub-slider__track:focus-visible{outline:0}.hub-slider__track:focus-visible::-webkit-slider-thumb{box-shadow:var(--hub-slider-thumb-shadow),var(--hub-input-focus-box-shadow)}.hub-slider__track:focus-visible::-moz-range-thumb{box-shadow:var(--hub-slider-thumb-shadow),var(--hub-input-focus-box-shadow)}.hub-slider__bubble{position:absolute;top:0;left:calc(var(--hub-slider-percent) * 1%);transform:translate(-50%);padding:.1rem .4rem;color:var(--hub-slider-tooltip-color);background:var(--hub-slider-tooltip-bg);font-size:var(--hub-ref-font-size-sm, .875rem);line-height:1.4;border-radius:var(--hub-slider-tooltip-border-radius);white-space:nowrap;pointer-events:none}.hub-slider__bubble--lower{left:calc(var(--hub-slider-from) * 1%)}.hub-slider__bubble--upper{left:calc(var(--hub-slider-to) * 1%)}.hub-slider__range--dual{min-height:calc(var(--hub-slider-value-space) + var(--hub-slider-thumb-height))}.hub-slider__range--dual .hub-slider__rail,.hub-slider__range--dual .hub-slider__track--lower,.hub-slider__range--dual .hub-slider__track--upper{position:absolute;left:0;right:0;top:calc(var(--hub-slider-value-space) + var(--hub-slider-thumb-height) / 2)}.hub-slider__range--dual .hub-slider__rail{height:var(--hub-slider-track-height);transform:translateY(-50%);border-radius:var(--hub-slider-track-border-radius);background:var(--hub-slider-track-fill) no-repeat,var(--hub-slider-track-bg);background-size:calc((var(--hub-slider-to) - var(--hub-slider-from)) * 1%) 100%,100% 100%;background-position:calc(var(--hub-slider-from) / max(100 - var(--hub-slider-to) + var(--hub-slider-from),1) * 100%) center,0 0;pointer-events:none}.hub-slider__range--dual .hub-slider__track--lower,.hub-slider__range--dual .hub-slider__track--upper{width:100%;height:var(--hub-slider-thumb-height);margin:0;transform:translateY(-50%);background:transparent;pointer-events:none}.hub-slider__range--dual .hub-slider__track--lower::-webkit-slider-thumb,.hub-slider__range--dual .hub-slider__track--upper::-webkit-slider-thumb{pointer-events:auto;position:relative;z-index:2}.hub-slider__range--dual .hub-slider__track--lower::-moz-range-thumb,.hub-slider__range--dual .hub-slider__track--upper::-moz-range-thumb{pointer-events:auto}.hub-slider__range--dual .hub-slider__track--lower::-moz-range-track,.hub-slider__range--dual .hub-slider__track--upper::-moz-range-track{background:transparent}\n"] }]
|
|
1817
1971
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelType: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelType", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], formText: [{ type: i0.Input, args: [{ isSignal: true, alias: "formText", required: false }] }], formTextType: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTextType", required: false }] }], classlist: [{ type: i0.Input, args: [{ isSignal: true, alias: "classlist", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }] } });
|
|
1818
1972
|
|
|
1973
|
+
/**
|
|
1974
|
+
* Resolves a user-supplied accent value to a paintable CSS colour for a `--hub-*-accent` slot.
|
|
1975
|
+
*
|
|
1976
|
+
* A bareword (a built-in semantic name such as `primary` / `success`, or any registered accent /
|
|
1977
|
+
* named CSS colour) becomes a `--hub-sys-color-*` design-system token with the word itself as the
|
|
1978
|
+
* raw fallback, so named CSS colours keep working when no token is registered. A literal colour
|
|
1979
|
+
* (`#ff0000`, `rgb(...)`, `oklch(...)`, `var(...)`) is passed through unchanged. Empty / whitespace
|
|
1980
|
+
* input resolves to `null` so the caller can keep its neutral default.
|
|
1981
|
+
*
|
|
1982
|
+
* @param value - The raw accent value (e.g. from a `color` input).
|
|
1983
|
+
* @returns The resolved CSS colour, or `null` when no accent is provided.
|
|
1984
|
+
*/
|
|
1985
|
+
function resolveHubAccent(value) {
|
|
1986
|
+
const color = value?.trim();
|
|
1987
|
+
if (!color)
|
|
1988
|
+
return null;
|
|
1989
|
+
return /^[a-zA-Z][\w-]*$/.test(color) ? `var(--hub-sys-color-${color}, ${color})` : color;
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1819
1992
|
/**
|
|
1820
1993
|
* Segmented button bar (`hub-segmented`): a compact group of 2..n options rendered as an inline
|
|
1821
1994
|
* (or vertical) segmented control. A full `ng-hub-ui-forms` field — it extends
|
|
@@ -1848,15 +2021,22 @@ class HubSegmentedComponent extends HubFieldControl {
|
|
|
1848
2021
|
size = input('md', /* @ts-ignore */
|
|
1849
2022
|
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
1850
2023
|
/**
|
|
1851
|
-
*
|
|
1852
|
-
*
|
|
1853
|
-
*
|
|
1854
|
-
*
|
|
1855
|
-
*
|
|
1856
|
-
*
|
|
2024
|
+
* Accent for the selected pill. Accepts ANY colour value:
|
|
2025
|
+
* - a built-in semantic name (`primary`, `success`, …) or any registered accent → resolves to
|
|
2026
|
+
* the matching `--hub-sys-color-*` design-system token;
|
|
2027
|
+
* - a literal colour (`#ff0000`, `rgb(...)`, `oklch(...)`, `rebeccapurple`) → used as-is.
|
|
2028
|
+
* Both the sliding indicator and the multiple-mode pill follow it, and the selected label gets a
|
|
2029
|
+
* derived contrast colour automatically. Empty string (default) keeps the neutral white-pill look.
|
|
1857
2030
|
*/
|
|
1858
2031
|
color = input('', /* @ts-ignore */
|
|
1859
2032
|
...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
|
|
2033
|
+
/**
|
|
2034
|
+
* Resolves {@link color} to a paintable accent for the `--hub-segmented-accent` slot: a bareword
|
|
2035
|
+
* becomes a `--hub-sys-color-*` token with the word as raw fallback (so named CSS colours work
|
|
2036
|
+
* too), while a literal `#hex` / `rgb()` / `oklch()` / `var()` is passed through unchanged.
|
|
2037
|
+
*/
|
|
2038
|
+
accentVar = computed(() => resolveHubAccent(this.color()), /* @ts-ignore */
|
|
2039
|
+
...(ngDevMode ? [{ debugName: "accentVar" }] : /* istanbul ignore next */ []));
|
|
1860
2040
|
/** Label text. */
|
|
1861
2041
|
label = input('', /* @ts-ignore */
|
|
1862
2042
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
@@ -2051,13 +2231,13 @@ class HubSegmentedComponent extends HubFieldControl {
|
|
|
2051
2231
|
return Object.is(a, b);
|
|
2052
2232
|
}
|
|
2053
2233
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubSegmentedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2054
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: HubSegmentedComponent, isStandalone: true, selector: "hub-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelType: { classPropertyName: "labelType", publicName: "labelType", isSignal: true, isRequired: false, transformFunction: null }, formText: { classPropertyName: "formText", publicName: "formText", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, vertical: { classPropertyName: "vertical", publicName: "vertical", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.hub-segmented-host": "true" } }, viewQueries: [{ propertyName: "_buttons", predicate: ["optionButton"], descendants: true, isSignal: true }, { propertyName: "_bar", first: true, predicate: ["segmentedBar"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n\tclass=\"hub-field hub-segmented-field\"\n\t[class.hub-field--horizontal]=\"label() && labelType() === _labelTypes.Horizontal\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body\">\n\t\t<div\n\t\t\t#segmentedBar\n\t\t\t[id]=\"id\"\n\t\t\tclass=\"hub-segmented\"\n\t\t\t[class.hub-segmented--sm]=\"size() === 'sm'\"\n\t\t\t[class.hub-segmented--lg]=\"size() === 'lg'\"\n\t\t\t[class.hub-segmented--vertical]=\"vertical()\"\n\t\t\t[class.hub-segmented--multiple]=\"multiple()\"\n\t\t\t[class.hub-segmented--disabled]=\"disabled()\"\n\t\t\t[attr.data-variant]=\"color() || null\"\n\t\t\t[attr.role]=\"multiple() ? 'group' : 'radiogroup'\"\n\t\t\t[attr.aria-disabled]=\"disabled() ? 'true' : null\"\n\t\t\t(keydown)=\"onKeydown($event)\"\n\t\t>\n\t\t\t@if (!multiple()) {\n\t\t\t\t<span class=\"hub-segmented__indicator\" aria-hidden=\"true\"></span>\n\t\t\t}\n\n\t\t\t@for (option of options(); track option.value; let index = $index) {\n\t\t\t\t<button\n\t\t\t\t\t#optionButton\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"hub-segmented__option\"\n\t\t\t\t\t[class.hub-segmented__option--selected]=\"isSelected(option)\"\n\t\t\t\t\t[attr.role]=\"multiple() ? null : 'radio'\"\n\t\t\t\t\t[attr.aria-checked]=\"multiple() ? null : isSelected(option)\"\n\t\t\t\t\t[attr.aria-pressed]=\"multiple() ? isSelected(option) : null\"\n\t\t\t\t\t[disabled]=\"disabled() || !!option.disabled\"\n\t\t\t\t\t[tabindex]=\"rovingIndex() === index ? 0 : -1\"\n\t\t\t\t\t(click)=\"select(option)\"\n\t\t\t\t>\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n</div>\n", styles: ["hub-segmented{display:block}hub-segmented.hidden-control{display:none!important}.hub-segmented{position:relative;display:inline-flex;gap:var(--hub-segmented-gap);padding:var(--hub-segmented-gap);background:var(--hub-segmented-bg);border-radius:var(--hub-segmented-radius)}.hub-segmented__indicator{position:absolute;z-index:0;left:var(--hub-segmented-indicator-x, 0);top:var(--hub-segmented-indicator-y, 0);width:var(--hub-segmented-indicator-width, 0);height:var(--hub-segmented-indicator-height, 0);background:var(--hub-segmented-selected-bg);border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075));pointer-events:none;transition:left var(--hub-segmented-indicator-transition),top var(--hub-segmented-indicator-transition),width var(--hub-segmented-indicator-transition),height var(--hub-segmented-indicator-transition)}@media(prefers-reduced-motion:reduce){.hub-segmented .hub-segmented__indicator{transition:none}}.hub-segmented--disabled{opacity:var(--hub-form-disabled-opacity);cursor:not-allowed}.hub-segmented--vertical{flex-direction:column}.hub-segmented--vertical .hub-segmented__option{width:100%}.hub-segmented--multiple .hub-segmented__option--selected{background:var(--hub-segmented-selected-bg);box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075))}.hub-segmented__option{appearance:none;border:0;margin:0;position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;padding:var(--hub-segmented-padding-y) var(--hub-segmented-padding-x);color:var(--hub-sys-text-muted, #6c757d);background:transparent;font:inherit;line-height:1.2;white-space:nowrap;border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));cursor:pointer;transition:var(--hub-form-transition),color .15s ease-in-out,background-color .15s ease-in-out}.hub-segmented__option:focus-visible{outline:0;box-shadow:0 0 0 var(--hub-form-focus-ring-width) var(--hub-form-focus-ring-color)}.hub-segmented__option--selected{color:var(--hub-segmented-selected-color)}.hub-segmented__option:disabled{cursor:not-allowed;opacity:var(--hub-form-disabled-opacity)}.hub-segmented--sm .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * .5) calc(var(--hub-segmented-padding-x) * .65);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-segmented--lg .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * 1.35) calc(var(--hub-segmented-padding-x) * 1.35);font-size:var(--hub-ref-font-size-lg, 1.125rem)}:where(.hub-segmented[data-variant
|
|
2234
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: HubSegmentedComponent, isStandalone: true, selector: "hub-segmented", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, labelType: { classPropertyName: "labelType", publicName: "labelType", isSignal: true, isRequired: false, transformFunction: null }, formText: { classPropertyName: "formText", publicName: "formText", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, vertical: { classPropertyName: "vertical", publicName: "vertical", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { properties: { "class.hub-segmented-host": "true" } }, viewQueries: [{ propertyName: "_buttons", predicate: ["optionButton"], descendants: true, isSignal: true }, { propertyName: "_bar", first: true, predicate: ["segmentedBar"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n\tclass=\"hub-field hub-segmented-field\"\n\t[class.hub-field--horizontal]=\"label() && labelType() === _labelTypes.Horizontal\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body\">\n\t\t<div\n\t\t\t#segmentedBar\n\t\t\t[id]=\"id\"\n\t\t\tclass=\"hub-segmented\"\n\t\t\t[class.hub-segmented--sm]=\"size() === 'sm'\"\n\t\t\t[class.hub-segmented--lg]=\"size() === 'lg'\"\n\t\t\t[class.hub-segmented--vertical]=\"vertical()\"\n\t\t\t[class.hub-segmented--multiple]=\"multiple()\"\n\t\t\t[class.hub-segmented--disabled]=\"disabled()\"\n\t\t\t[attr.data-variant]=\"color() || null\"\n\t\t\t[style.--hub-segmented-accent]=\"accentVar()\"\n\t\t\t[attr.role]=\"multiple() ? 'group' : 'radiogroup'\"\n\t\t\t[attr.aria-disabled]=\"disabled() ? 'true' : null\"\n\t\t\t(keydown)=\"onKeydown($event)\"\n\t\t>\n\t\t\t@if (!multiple()) {\n\t\t\t\t<span class=\"hub-segmented__indicator\" aria-hidden=\"true\"></span>\n\t\t\t}\n\n\t\t\t@for (option of options(); track option.value; let index = $index) {\n\t\t\t\t<button\n\t\t\t\t\t#optionButton\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"hub-segmented__option\"\n\t\t\t\t\t[class.hub-segmented__option--selected]=\"isSelected(option)\"\n\t\t\t\t\t[attr.role]=\"multiple() ? null : 'radio'\"\n\t\t\t\t\t[attr.aria-checked]=\"multiple() ? null : isSelected(option)\"\n\t\t\t\t\t[attr.aria-pressed]=\"multiple() ? isSelected(option) : null\"\n\t\t\t\t\t[disabled]=\"disabled() || !!option.disabled\"\n\t\t\t\t\t[tabindex]=\"rovingIndex() === index ? 0 : -1\"\n\t\t\t\t\t(click)=\"select(option)\"\n\t\t\t\t>\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n</div>\n", styles: ["hub-segmented{display:block}hub-segmented.hidden-control{display:none!important}.hub-segmented{position:relative;display:inline-flex;gap:var(--hub-segmented-gap);padding:var(--hub-segmented-gap);background:var(--hub-segmented-bg);border-radius:var(--hub-segmented-radius)}.hub-segmented__indicator{position:absolute;z-index:0;left:var(--hub-segmented-indicator-x, 0);top:var(--hub-segmented-indicator-y, 0);width:var(--hub-segmented-indicator-width, 0);height:var(--hub-segmented-indicator-height, 0);background:var(--hub-segmented-selected-bg);border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075));pointer-events:none;transition:left var(--hub-segmented-indicator-transition),top var(--hub-segmented-indicator-transition),width var(--hub-segmented-indicator-transition),height var(--hub-segmented-indicator-transition)}@media(prefers-reduced-motion:reduce){.hub-segmented .hub-segmented__indicator{transition:none}}.hub-segmented--disabled{opacity:var(--hub-form-disabled-opacity);cursor:not-allowed}.hub-segmented--vertical{flex-direction:column}.hub-segmented--vertical .hub-segmented__option{width:100%}.hub-segmented--multiple .hub-segmented__option--selected{background:var(--hub-segmented-selected-bg);box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075))}.hub-segmented__option{appearance:none;border:0;margin:0;position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;padding:var(--hub-segmented-padding-y) var(--hub-segmented-padding-x);color:var(--hub-sys-text-muted, #6c757d);background:transparent;font:inherit;line-height:1.2;white-space:nowrap;border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));cursor:pointer;transition:var(--hub-form-transition),color .15s ease-in-out,background-color .15s ease-in-out}.hub-segmented__option:focus-visible{outline:0;box-shadow:0 0 0 var(--hub-form-focus-ring-width) var(--hub-form-focus-ring-color)}.hub-segmented__option--selected{color:var(--hub-segmented-selected-color)}.hub-segmented__option:disabled{cursor:not-allowed;opacity:var(--hub-form-disabled-opacity)}.hub-segmented--sm .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * .5) calc(var(--hub-segmented-padding-x) * .65);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-segmented--lg .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * 1.35) calc(var(--hub-segmented-padding-x) * 1.35);font-size:var(--hub-ref-font-size-lg, 1.125rem)}:where(.hub-segmented[data-variant]){--hub-segmented-selected-bg: var(--hub-segmented-accent);--hub-segmented-selected-color: oklch(from var(--hub-segmented-accent) clamp(0, (.62 - l) * 1000, 1) 0 h)}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
2055
2235
|
}
|
|
2056
2236
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubSegmentedComponent, decorators: [{
|
|
2057
2237
|
type: Component,
|
|
2058
2238
|
args: [{ selector: 'hub-segmented', standalone: true, imports: [NgTemplateOutlet, KeyValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
2059
2239
|
'[class.hub-segmented-host]': 'true'
|
|
2060
|
-
}, template: "<div\n\tclass=\"hub-field hub-segmented-field\"\n\t[class.hub-field--horizontal]=\"label() && labelType() === _labelTypes.Horizontal\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body\">\n\t\t<div\n\t\t\t#segmentedBar\n\t\t\t[id]=\"id\"\n\t\t\tclass=\"hub-segmented\"\n\t\t\t[class.hub-segmented--sm]=\"size() === 'sm'\"\n\t\t\t[class.hub-segmented--lg]=\"size() === 'lg'\"\n\t\t\t[class.hub-segmented--vertical]=\"vertical()\"\n\t\t\t[class.hub-segmented--multiple]=\"multiple()\"\n\t\t\t[class.hub-segmented--disabled]=\"disabled()\"\n\t\t\t[attr.data-variant]=\"color() || null\"\n\t\t\t[attr.role]=\"multiple() ? 'group' : 'radiogroup'\"\n\t\t\t[attr.aria-disabled]=\"disabled() ? 'true' : null\"\n\t\t\t(keydown)=\"onKeydown($event)\"\n\t\t>\n\t\t\t@if (!multiple()) {\n\t\t\t\t<span class=\"hub-segmented__indicator\" aria-hidden=\"true\"></span>\n\t\t\t}\n\n\t\t\t@for (option of options(); track option.value; let index = $index) {\n\t\t\t\t<button\n\t\t\t\t\t#optionButton\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"hub-segmented__option\"\n\t\t\t\t\t[class.hub-segmented__option--selected]=\"isSelected(option)\"\n\t\t\t\t\t[attr.role]=\"multiple() ? null : 'radio'\"\n\t\t\t\t\t[attr.aria-checked]=\"multiple() ? null : isSelected(option)\"\n\t\t\t\t\t[attr.aria-pressed]=\"multiple() ? isSelected(option) : null\"\n\t\t\t\t\t[disabled]=\"disabled() || !!option.disabled\"\n\t\t\t\t\t[tabindex]=\"rovingIndex() === index ? 0 : -1\"\n\t\t\t\t\t(click)=\"select(option)\"\n\t\t\t\t>\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n</div>\n", styles: ["hub-segmented{display:block}hub-segmented.hidden-control{display:none!important}.hub-segmented{position:relative;display:inline-flex;gap:var(--hub-segmented-gap);padding:var(--hub-segmented-gap);background:var(--hub-segmented-bg);border-radius:var(--hub-segmented-radius)}.hub-segmented__indicator{position:absolute;z-index:0;left:var(--hub-segmented-indicator-x, 0);top:var(--hub-segmented-indicator-y, 0);width:var(--hub-segmented-indicator-width, 0);height:var(--hub-segmented-indicator-height, 0);background:var(--hub-segmented-selected-bg);border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075));pointer-events:none;transition:left var(--hub-segmented-indicator-transition),top var(--hub-segmented-indicator-transition),width var(--hub-segmented-indicator-transition),height var(--hub-segmented-indicator-transition)}@media(prefers-reduced-motion:reduce){.hub-segmented .hub-segmented__indicator{transition:none}}.hub-segmented--disabled{opacity:var(--hub-form-disabled-opacity);cursor:not-allowed}.hub-segmented--vertical{flex-direction:column}.hub-segmented--vertical .hub-segmented__option{width:100%}.hub-segmented--multiple .hub-segmented__option--selected{background:var(--hub-segmented-selected-bg);box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075))}.hub-segmented__option{appearance:none;border:0;margin:0;position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;padding:var(--hub-segmented-padding-y) var(--hub-segmented-padding-x);color:var(--hub-sys-text-muted, #6c757d);background:transparent;font:inherit;line-height:1.2;white-space:nowrap;border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));cursor:pointer;transition:var(--hub-form-transition),color .15s ease-in-out,background-color .15s ease-in-out}.hub-segmented__option:focus-visible{outline:0;box-shadow:0 0 0 var(--hub-form-focus-ring-width) var(--hub-form-focus-ring-color)}.hub-segmented__option--selected{color:var(--hub-segmented-selected-color)}.hub-segmented__option:disabled{cursor:not-allowed;opacity:var(--hub-form-disabled-opacity)}.hub-segmented--sm .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * .5) calc(var(--hub-segmented-padding-x) * .65);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-segmented--lg .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * 1.35) calc(var(--hub-segmented-padding-x) * 1.35);font-size:var(--hub-ref-font-size-lg, 1.125rem)}:where(.hub-segmented[data-variant
|
|
2240
|
+
}, template: "<div\n\tclass=\"hub-field hub-segmented-field\"\n\t[class.hub-field--horizontal]=\"label() && labelType() === _labelTypes.Horizontal\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body\">\n\t\t<div\n\t\t\t#segmentedBar\n\t\t\t[id]=\"id\"\n\t\t\tclass=\"hub-segmented\"\n\t\t\t[class.hub-segmented--sm]=\"size() === 'sm'\"\n\t\t\t[class.hub-segmented--lg]=\"size() === 'lg'\"\n\t\t\t[class.hub-segmented--vertical]=\"vertical()\"\n\t\t\t[class.hub-segmented--multiple]=\"multiple()\"\n\t\t\t[class.hub-segmented--disabled]=\"disabled()\"\n\t\t\t[attr.data-variant]=\"color() || null\"\n\t\t\t[style.--hub-segmented-accent]=\"accentVar()\"\n\t\t\t[attr.role]=\"multiple() ? 'group' : 'radiogroup'\"\n\t\t\t[attr.aria-disabled]=\"disabled() ? 'true' : null\"\n\t\t\t(keydown)=\"onKeydown($event)\"\n\t\t>\n\t\t\t@if (!multiple()) {\n\t\t\t\t<span class=\"hub-segmented__indicator\" aria-hidden=\"true\"></span>\n\t\t\t}\n\n\t\t\t@for (option of options(); track option.value; let index = $index) {\n\t\t\t\t<button\n\t\t\t\t\t#optionButton\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclass=\"hub-segmented__option\"\n\t\t\t\t\t[class.hub-segmented__option--selected]=\"isSelected(option)\"\n\t\t\t\t\t[attr.role]=\"multiple() ? null : 'radio'\"\n\t\t\t\t\t[attr.aria-checked]=\"multiple() ? null : isSelected(option)\"\n\t\t\t\t\t[attr.aria-pressed]=\"multiple() ? isSelected(option) : null\"\n\t\t\t\t\t[disabled]=\"disabled() || !!option.disabled\"\n\t\t\t\t\t[tabindex]=\"rovingIndex() === index ? 0 : -1\"\n\t\t\t\t\t(click)=\"select(option)\"\n\t\t\t\t>\n\t\t\t\t\t{{ option.label }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n</div>\n", styles: ["hub-segmented{display:block}hub-segmented.hidden-control{display:none!important}.hub-segmented{position:relative;display:inline-flex;gap:var(--hub-segmented-gap);padding:var(--hub-segmented-gap);background:var(--hub-segmented-bg);border-radius:var(--hub-segmented-radius)}.hub-segmented__indicator{position:absolute;z-index:0;left:var(--hub-segmented-indicator-x, 0);top:var(--hub-segmented-indicator-y, 0);width:var(--hub-segmented-indicator-width, 0);height:var(--hub-segmented-indicator-height, 0);background:var(--hub-segmented-selected-bg);border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075));pointer-events:none;transition:left var(--hub-segmented-indicator-transition),top var(--hub-segmented-indicator-transition),width var(--hub-segmented-indicator-transition),height var(--hub-segmented-indicator-transition)}@media(prefers-reduced-motion:reduce){.hub-segmented .hub-segmented__indicator{transition:none}}.hub-segmented--disabled{opacity:var(--hub-form-disabled-opacity);cursor:not-allowed}.hub-segmented--vertical{flex-direction:column}.hub-segmented--vertical .hub-segmented__option{width:100%}.hub-segmented--multiple .hub-segmented__option--selected{background:var(--hub-segmented-selected-bg);box-shadow:var(--hub-sys-shadow-sm, 0 .125rem .25rem rgba(0, 0, 0, .075))}.hub-segmented__option{appearance:none;border:0;margin:0;position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;padding:var(--hub-segmented-padding-y) var(--hub-segmented-padding-x);color:var(--hub-sys-text-muted, #6c757d);background:transparent;font:inherit;line-height:1.2;white-space:nowrap;border-radius:calc(var(--hub-segmented-radius) - var(--hub-segmented-gap));cursor:pointer;transition:var(--hub-form-transition),color .15s ease-in-out,background-color .15s ease-in-out}.hub-segmented__option:focus-visible{outline:0;box-shadow:0 0 0 var(--hub-form-focus-ring-width) var(--hub-form-focus-ring-color)}.hub-segmented__option--selected{color:var(--hub-segmented-selected-color)}.hub-segmented__option:disabled{cursor:not-allowed;opacity:var(--hub-form-disabled-opacity)}.hub-segmented--sm .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * .5) calc(var(--hub-segmented-padding-x) * .65);font-size:var(--hub-ref-font-size-sm, .875rem)}.hub-segmented--lg .hub-segmented__option{padding:calc(var(--hub-segmented-padding-y) * 1.35) calc(var(--hub-segmented-padding-x) * 1.35);font-size:var(--hub-ref-font-size-lg, 1.125rem)}:where(.hub-segmented[data-variant]){--hub-segmented-selected-bg: var(--hub-segmented-accent);--hub-segmented-selected-color: oklch(from var(--hub-segmented-accent) clamp(0, (.62 - l) * 1000, 1) 0 h)}\n"] }]
|
|
2061
2241
|
}], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelType: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelType", required: false }] }], formText: [{ type: i0.Input, args: [{ isSignal: true, alias: "formText", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], vertical: [{ type: i0.Input, args: [{ isSignal: true, alias: "vertical", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], _buttons: [{ type: i0.ViewChildren, args: ['optionButton', { isSignal: true }] }], _bar: [{ type: i0.ViewChild, args: ['segmentedBar', { isSignal: true }] }] } });
|
|
2062
2242
|
|
|
2063
2243
|
/**
|
|
@@ -2158,12 +2338,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
2158
2338
|
type: Component,
|
|
2159
2339
|
args: [{ selector: 'form[hubForm]', imports: [NgTemplateOutlet, KeyValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
2160
2340
|
'[class.hub-form-host]': 'true',
|
|
2161
|
-
'[class.hub-form--invalid]': 'isInvalid'
|
|
2341
|
+
'[class.hub-form--invalid]': 'isInvalid',
|
|
2342
|
+
'(submit)': 'handleSubmit($event)'
|
|
2162
2343
|
}, template: "<ng-content></ng-content>\n\n@if (isInvalid) {\n\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t<ul>\n\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t<li>\n\t\t\t\t\t<ng-container\n\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t></ng-container>\n\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t</ng-template>\n\t\t\t\t</li>\n\t\t\t}\n\t\t</ul>\n\t</div>\n}\n" }]
|
|
2163
|
-
}]
|
|
2164
|
-
type: HostListener,
|
|
2165
|
-
args: ['submit', ['$event']]
|
|
2166
|
-
}] } });
|
|
2344
|
+
}] });
|
|
2167
2345
|
|
|
2168
2346
|
/**
|
|
2169
2347
|
* Presentational legend for a `<hub-fieldset>` (or any grouped section), with optional
|
|
@@ -6093,6 +6271,780 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImpor
|
|
|
6093
6271
|
}, template: "<div\n\tclass=\"hub-field hub-datepicker\"\n\t[class.hub-field--horizontal]=\"labelType() === _labelTypes.Horizontal || (!label() && required())\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body hub-datepicker__trigger\" cdkOverlayOrigin #trigger=\"cdkOverlayOrigin\">\n\t\t<input\n\t\t\tclass=\"hub-field__control hub-datepicker__input\"\n\t\t\t[class.hub-field__control--invalid]=\"isInvalid\"\n\t\t\t[class.hub-field__control--valid]=\"showsValid\"\n\t\t\t[class.hub-datepicker__input--clearable]=\"clearable() && displayValue()\"\n\t\t\t[id]=\"id\"\n\t\t\ttype=\"text\"\n\t\t\treadonly\n\t\t\t[value]=\"displayValue()\"\n\t\t\t[placeholder]=\"placeholder()\"\n\t\t\t[disabled]=\"disabled()\"\n\t\t\t[required]=\"!!required()\"\n\t\t\t[attr.aria-invalid]=\"isInvalid\"\n\t\t\t[attr.aria-expanded]=\"_open()\"\n\t\t\taria-haspopup=\"dialog\"\n\t\t\t(click)=\"toggleCalendar()\"\n\t\t\t(blur)=\"handleBlur($event)\"\n\t\t/>\n\t\t@if (clearable() && displayValue() && !disabled() && !readonly()) {\n\t\t\t<button type=\"button\" class=\"hub-datepicker__clear\" [attr.aria-label]=\"_labels().clear\" (click)=\"clear()\">×</button>\n\t\t}\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclass=\"hub-datepicker__icon\"\n\t\t\ttabindex=\"-1\"\n\t\t\t[attr.aria-label]=\"_labels().openCalendar\"\n\t\t\t[disabled]=\"disabled() || readonly()\"\n\t\t\t(click)=\"toggleCalendar()\"\n\t\t></button>\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n</div>\n\n<ng-template\n\tcdkConnectedOverlay\n\t[cdkConnectedOverlayOrigin]=\"trigger\"\n\t[cdkConnectedOverlayOpen]=\"_open()\"\n\t[cdkConnectedOverlayHasBackdrop]=\"true\"\n\tcdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n\t[cdkConnectedOverlayOffsetY]=\"4\"\n\t(backdropClick)=\"close()\"\n\t(detach)=\"close()\"\n>\n\t<div class=\"hub-datepicker__panel\" role=\"dialog\" [attr.aria-label]=\"monthYearLabel()\">\n\t\t<header class=\"hub-datepicker__header\">\n\t\t\t<div class=\"hub-datepicker__nav-group\">\n\t\t\t\t<button type=\"button\" class=\"hub-datepicker__nav\" [attr.aria-label]=\"_labels().previousYear\" (click)=\"shiftMonth(-12)\">\u00AB</button>\n\t\t\t\t<button type=\"button\" class=\"hub-datepicker__nav\" [attr.aria-label]=\"_labels().previousMonth\" (click)=\"shiftMonth(-1)\">\u2039</button>\n\t\t\t</div>\n\t\t\t<span class=\"hub-datepicker__title\" aria-live=\"polite\">{{ monthYearLabel() }}</span>\n\t\t\t<div class=\"hub-datepicker__nav-group\">\n\t\t\t\t<button type=\"button\" class=\"hub-datepicker__nav\" [attr.aria-label]=\"_labels().nextMonth\" (click)=\"shiftMonth(1)\">\u203A</button>\n\t\t\t\t<button type=\"button\" class=\"hub-datepicker__nav\" [attr.aria-label]=\"_labels().nextYear\" (click)=\"shiftMonth(12)\">\u00BB</button>\n\t\t\t</div>\n\t\t</header>\n\n\t\t<div class=\"hub-datepicker__weekdays\" aria-hidden=\"true\">\n\t\t\t@for (wd of weekdays(); track $index) {\n\t\t\t\t<span class=\"hub-datepicker__weekday\">{{ wd }}</span>\n\t\t\t}\n\t\t</div>\n\n\t\t<div class=\"hub-datepicker__grid\" role=\"grid\" (keydown)=\"onGridKeydown($event)\">\n\t\t\t@for (cell of grid(); track cell.date.getTime()) {\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\trole=\"gridcell\"\n\t\t\t\t\tclass=\"hub-datepicker__cell\"\n\t\t\t\t\t[class.hub-datepicker__cell--outside]=\"!cell.currentMonth\"\n\t\t\t\t\t[class.hub-datepicker__cell--today]=\"cell.today\"\n\t\t\t\t\t[class.hub-datepicker__cell--selected]=\"cell.selected || cell.rangeStart || cell.rangeEnd\"\n\t\t\t\t\t[class.hub-datepicker__cell--range-start]=\"cell.rangeStart\"\n\t\t\t\t\t[class.hub-datepicker__cell--range-end]=\"cell.rangeEnd\"\n\t\t\t\t\t[class.hub-datepicker__cell--in-range]=\"cell.inRange\"\n\t\t\t\t\t[attr.data-time]=\"cell.date.getTime()\"\n\t\t\t\t\t[attr.tabindex]=\"cell.focused ? 0 : -1\"\n\t\t\t\t\t[attr.aria-selected]=\"cell.selected || cell.rangeStart || cell.rangeEnd\"\n\t\t\t\t\t[disabled]=\"cell.disabled\"\n\t\t\t\t\t(click)=\"selectDay(cell)\"\n\t\t\t\t>\n\t\t\t\t\t{{ cell.day }}\n\t\t\t\t</button>\n\t\t\t}\n\t\t</div>\n\n\t\t@if (showToday() || clearable()) {\n\t\t\t<footer class=\"hub-datepicker__footer\">\n\t\t\t\t@if (showToday()) {\n\t\t\t\t\t<button type=\"button\" class=\"hub-datepicker__action\" (click)=\"goToToday()\">{{ _labels().today }}</button>\n\t\t\t\t}\n\t\t\t\t@if (clearable()) {\n\t\t\t\t\t<button type=\"button\" class=\"hub-datepicker__action\" (click)=\"clear()\">{{ _labels().clear }}</button>\n\t\t\t\t}\n\t\t\t</footer>\n\t\t}\n\t</div>\n</ng-template>\n", styles: ["hub-datepicker{display:block}hub-datepicker.hidden-control{display:none!important}.hub-datepicker__trigger{position:relative}.hub-datepicker__input{padding-right:var(--hub-datepicker-icon-width);cursor:pointer}.hub-datepicker__input--clearable{padding-right:calc(var(--hub-datepicker-icon-width) + 1.5rem)}.hub-datepicker__clear{position:absolute;top:0;right:var(--hub-datepicker-icon-width);height:100%;width:1.5rem;padding:0;color:var(--hub-daterangepicker-nav-arrow-color);font-size:1.1rem;line-height:1;background:transparent;border:0;cursor:pointer}.hub-datepicker__clear:hover{color:var(--hub-form-invalid-color)}.hub-datepicker__icon{position:absolute;top:0;right:0;width:var(--hub-datepicker-icon-width);height:100%;padding:0;background:transparent;border:0;cursor:pointer}.hub-datepicker__icon:after{content:\"\";display:block;width:var(--hub-datepicker-icon-size);height:var(--hub-datepicker-icon-size);margin:0 auto;background:var(--hub-datepicker-icon) center/contain no-repeat}.hub-datepicker__icon:disabled{cursor:not-allowed;opacity:var(--hub-form-disabled-opacity)}.hub-datepicker__panel{width:max-content;padding:var(--hub-daterangepicker-padding-y, var(--hub-ref-space-3, 1rem)) var(--hub-daterangepicker-padding-x, var(--hub-ref-space-3, 1rem));color:var(--hub-daterangepicker-color);background:var(--hub-daterangepicker-bg);border:var(--hub-input-border-width) solid var(--hub-daterangepicker-border-color);border-radius:var(--hub-daterangepicker-border-radius);box-shadow:var(--hub-daterangepicker-box-shadow);font-family:var(--hub-input-font-family)}.hub-datepicker__panel .hub-datepicker__header{display:flex;align-items:center;justify-content:space-between;margin-bottom:var(--hub-ref-space-2, .5rem)}.hub-datepicker__panel .hub-datepicker__nav-group{display:inline-flex;gap:.125rem}.hub-datepicker__panel .hub-datepicker__nav{display:inline-flex;align-items:center;justify-content:center;width:var(--hub-daterangepicker-cell-size);height:var(--hub-daterangepicker-cell-size);font-size:1.25rem;line-height:1;color:var(--hub-daterangepicker-nav-arrow-color);background:transparent;border:0;border-radius:var(--hub-daterangepicker-cell-border-radius);cursor:pointer}.hub-datepicker__panel .hub-datepicker__nav:hover{color:var(--hub-daterangepicker-nav-arrow-hover-color);background:var(--hub-daterangepicker-cell-hover-bg)}.hub-datepicker__panel .hub-datepicker__title{font-weight:var(--hub-ref-font-weight-medium, 500);text-transform:capitalize}.hub-datepicker__panel .hub-datepicker__weekdays,.hub-datepicker__panel .hub-datepicker__grid{display:grid;grid-template-columns:repeat(7,var(--hub-daterangepicker-cell-size));gap:.125rem}.hub-datepicker__panel .hub-datepicker__weekday{display:inline-flex;align-items:center;justify-content:center;height:1.5rem;font-size:var(--hub-ref-font-size-sm, .875rem);color:var(--hub-daterangepicker-off-color);text-transform:capitalize}.hub-datepicker__panel .hub-datepicker__cell{display:inline-flex;align-items:center;justify-content:center;width:var(--hub-daterangepicker-cell-size);height:var(--hub-daterangepicker-cell-size);color:var(--hub-daterangepicker-cell-color);background:transparent;border:0;border-radius:var(--hub-daterangepicker-cell-border-radius);font-size:var(--hub-input-font-size);cursor:pointer}.hub-datepicker__panel .hub-datepicker__cell:hover{background:var(--hub-daterangepicker-cell-hover-bg)}.hub-datepicker__panel .hub-datepicker__cell--outside{color:var(--hub-daterangepicker-off-color)}.hub-datepicker__panel .hub-datepicker__cell--today{font-weight:var(--hub-ref-font-weight-bold, 700);box-shadow:inset 0 0 0 1px var(--hub-daterangepicker-border-color)}.hub-datepicker__panel .hub-datepicker__cell--in-range{background:var(--hub-daterangepicker-in-range-bg);border-radius:0}.hub-datepicker__panel .hub-datepicker__cell--range-start{border-top-right-radius:0;border-bottom-right-radius:0}.hub-datepicker__panel .hub-datepicker__cell--range-end{border-top-left-radius:0;border-bottom-left-radius:0}.hub-datepicker__panel .hub-datepicker__cell--selected,.hub-datepicker__panel .hub-datepicker__cell--selected:hover{color:var(--hub-daterangepicker-active-color);background:var(--hub-daterangepicker-active-bg)}.hub-datepicker__panel .hub-datepicker__cell:disabled{color:var(--hub-daterangepicker-off-color);background:transparent;cursor:not-allowed;opacity:.6}.hub-datepicker__panel .hub-datepicker__cell:focus-visible{outline:0;box-shadow:var(--hub-input-focus-box-shadow)}.hub-datepicker__panel .hub-datepicker__footer{display:flex;justify-content:flex-end;gap:var(--hub-ref-space-2, .5rem);margin-top:var(--hub-ref-space-2, .5rem);padding-top:var(--hub-ref-space-2, .5rem);border-top:var(--hub-input-border-width) solid var(--hub-daterangepicker-border-color)}.hub-datepicker__panel .hub-datepicker__action{padding:.2rem .5rem;color:var(--hub-sys-color-primary, #0d6efd);font-size:var(--hub-ref-font-size-sm, .875rem);background:transparent;border:0;border-radius:var(--hub-daterangepicker-cell-border-radius);cursor:pointer}.hub-datepicker__panel .hub-datepicker__action:hover{background:var(--hub-daterangepicker-cell-hover-bg)}\n"] }]
|
|
6094
6272
|
}], propDecorators: { mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], labelType: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelType", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], firstDayOfWeek: [{ type: i0.Input, args: [{ isSignal: true, alias: "firstDayOfWeek", required: false }] }], weekdayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekdayFormat", required: false }] }], displayFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "displayFormat", required: false }] }], rangeSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeSeparator", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], showToday: [{ type: i0.Input, args: [{ isSignal: true, alias: "showToday", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], labels: [{ type: i0.Input, args: [{ isSignal: true, alias: "labels", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], formText: [{ type: i0.Input, args: [{ isSignal: true, alias: "formText", required: false }] }], formTextType: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTextType", required: false }] }], classlist: [{ type: i0.Input, args: [{ isSignal: true, alias: "classlist", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], viewChange: [{ type: i0.Output, args: ["viewChange"] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
|
|
6095
6273
|
|
|
6274
|
+
/**
|
|
6275
|
+
* Overrides the glyph `<hub-file-input>` renders next to a non-image file.
|
|
6276
|
+
*
|
|
6277
|
+
* The library never depends on `ng-hub-ui-icons`: it draws a neutral document glyph from the
|
|
6278
|
+
* `--hub-file-input-file-icon` CSS token and lets you project anything richer — a `<hub-icon>` from
|
|
6279
|
+
* any pack, an inline `<svg>`, an emoji — through this template. The item is the implicit context,
|
|
6280
|
+
* so the glyph can vary with `file.type`.
|
|
6281
|
+
*
|
|
6282
|
+
* @example
|
|
6283
|
+
* ```html
|
|
6284
|
+
* <hub-file-input formControlName="attachments" [multiple]="true">
|
|
6285
|
+
* <ng-template hubFileIcon let-item>
|
|
6286
|
+
* <hub-icon [name]="item.file.type === 'application/pdf' ? 'fa:solid:file-pdf' : 'fa:solid:file'" />
|
|
6287
|
+
* </ng-template>
|
|
6288
|
+
* </hub-file-input>
|
|
6289
|
+
* ```
|
|
6290
|
+
*/
|
|
6291
|
+
class HubFileIconDirective {
|
|
6292
|
+
/** Template reference for the icon. */
|
|
6293
|
+
template = inject(TemplateRef);
|
|
6294
|
+
/**
|
|
6295
|
+
* Narrows the implicit context for the template type-checker.
|
|
6296
|
+
*
|
|
6297
|
+
* @param _directive - The directive instance.
|
|
6298
|
+
* @param _context - The candidate context.
|
|
6299
|
+
* @returns Always `true`; exists only to type `let-item`.
|
|
6300
|
+
*/
|
|
6301
|
+
static ngTemplateContextGuard(_directive, _context) {
|
|
6302
|
+
return true;
|
|
6303
|
+
}
|
|
6304
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFileIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6305
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: HubFileIconDirective, isStandalone: true, selector: "[hubFileIcon]", ngImport: i0 });
|
|
6306
|
+
}
|
|
6307
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFileIconDirective, decorators: [{
|
|
6308
|
+
type: Directive,
|
|
6309
|
+
args: [{
|
|
6310
|
+
selector: '[hubFileIcon]'
|
|
6311
|
+
}]
|
|
6312
|
+
}] });
|
|
6313
|
+
|
|
6314
|
+
/**
|
|
6315
|
+
* Replaces the entire rendering of a selected file in `<hub-file-input>`.
|
|
6316
|
+
*
|
|
6317
|
+
* Where `hubFileIcon` swaps a glyph, this takes over the whole row (or grid tile): thumbnail, name,
|
|
6318
|
+
* size, progress bar and buttons. The template receives the item plus the actions it needs, so a
|
|
6319
|
+
* custom preview keeps working removal, retry and cancel without reaching into the component.
|
|
6320
|
+
*
|
|
6321
|
+
* @example
|
|
6322
|
+
* ```html
|
|
6323
|
+
* <hub-file-input formControlName="attachments" [multiple]="true">
|
|
6324
|
+
* <ng-template hubFilePreview let-item let-remove="remove">
|
|
6325
|
+
* <article class="attachment">
|
|
6326
|
+
* <img *ngIf="item.previewUrl" [src]="item.previewUrl" alt="" />
|
|
6327
|
+
* <span>{{ item.file.name }}</span>
|
|
6328
|
+
* <button type="button" (click)="remove()">Remove</button>
|
|
6329
|
+
* </article>
|
|
6330
|
+
* </ng-template>
|
|
6331
|
+
* </hub-file-input>
|
|
6332
|
+
* ```
|
|
6333
|
+
*/
|
|
6334
|
+
class HubFilePreviewDirective {
|
|
6335
|
+
/** Template reference for the item. */
|
|
6336
|
+
template = inject(TemplateRef);
|
|
6337
|
+
/**
|
|
6338
|
+
* Narrows the context for the template type-checker.
|
|
6339
|
+
*
|
|
6340
|
+
* @param _directive - The directive instance.
|
|
6341
|
+
* @param _context - The candidate context.
|
|
6342
|
+
* @returns Always `true`; exists only to type `let-item` and the action bindings.
|
|
6343
|
+
*/
|
|
6344
|
+
static ngTemplateContextGuard(_directive, _context) {
|
|
6345
|
+
return true;
|
|
6346
|
+
}
|
|
6347
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFilePreviewDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
6348
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.1", type: HubFilePreviewDirective, isStandalone: true, selector: "[hubFilePreview]", ngImport: i0 });
|
|
6349
|
+
}
|
|
6350
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFilePreviewDirective, decorators: [{
|
|
6351
|
+
type: Directive,
|
|
6352
|
+
args: [{
|
|
6353
|
+
selector: '[hubFilePreview]'
|
|
6354
|
+
}]
|
|
6355
|
+
}] });
|
|
6356
|
+
|
|
6357
|
+
/**
|
|
6358
|
+
* Tests a file against an `accept` specification.
|
|
6359
|
+
*
|
|
6360
|
+
* The native `accept` attribute only filters the operating-system file dialog: files arriving by
|
|
6361
|
+
* drag-and-drop or paste bypass it entirely. This function applies the same rules by hand, so a
|
|
6362
|
+
* dropped file is held to the constraint the consumer declared.
|
|
6363
|
+
*
|
|
6364
|
+
* Supported tokens, comma-separated, matched case-insensitively:
|
|
6365
|
+
* - an extension — `.pdf`
|
|
6366
|
+
* - a wildcard MIME type — `image/*`
|
|
6367
|
+
* - an exact MIME type — `application/pdf`
|
|
6368
|
+
* - the catch-alls `*` and `*/*`
|
|
6369
|
+
*
|
|
6370
|
+
* Note that a MIME token can never match a file the browser could not type (`file.type === ''`,
|
|
6371
|
+
* common for unusual extensions); use an extension token for those.
|
|
6372
|
+
*
|
|
6373
|
+
* @param file - The file to test.
|
|
6374
|
+
* @param accept - The `accept` specification. Empty or `*` accepts everything.
|
|
6375
|
+
* @returns `true` when the file satisfies at least one token.
|
|
6376
|
+
*/
|
|
6377
|
+
function matchesAccept(file, accept) {
|
|
6378
|
+
const specification = (accept ?? '').trim();
|
|
6379
|
+
if (!specification || specification === '*' || specification === '*/*') {
|
|
6380
|
+
return true;
|
|
6381
|
+
}
|
|
6382
|
+
const name = file.name.toLowerCase();
|
|
6383
|
+
const type = (file.type ?? '').toLowerCase();
|
|
6384
|
+
return specification
|
|
6385
|
+
.split(',')
|
|
6386
|
+
.map((token) => token.trim().toLowerCase())
|
|
6387
|
+
.filter((token) => token.length > 0)
|
|
6388
|
+
.some((token) => {
|
|
6389
|
+
if (token === '*' || token === '*/*') {
|
|
6390
|
+
return true;
|
|
6391
|
+
}
|
|
6392
|
+
// An extension, e.g. `.pdf`
|
|
6393
|
+
if (token.startsWith('.')) {
|
|
6394
|
+
return name.endsWith(token);
|
|
6395
|
+
}
|
|
6396
|
+
// A wildcard MIME type, e.g. `image/*` — the file's type must start with `image/`
|
|
6397
|
+
if (token.endsWith('/*')) {
|
|
6398
|
+
return type.length > 0 && type.startsWith(token.slice(0, -1));
|
|
6399
|
+
}
|
|
6400
|
+
// An exact MIME type
|
|
6401
|
+
return type === token;
|
|
6402
|
+
});
|
|
6403
|
+
}
|
|
6404
|
+
|
|
6405
|
+
/**
|
|
6406
|
+
* Builds the identity key used to detect duplicate selections.
|
|
6407
|
+
*
|
|
6408
|
+
* Two `File` objects are never referentially equal across separate drops, so identity is derived
|
|
6409
|
+
* from the tuple the browser exposes: name, byte size and last-modified timestamp. Collisions are
|
|
6410
|
+
* possible in theory (same name, size and mtime) but describe the same file in every practical case.
|
|
6411
|
+
*
|
|
6412
|
+
* @param file - The file to key.
|
|
6413
|
+
* @returns A stable string key for the file.
|
|
6414
|
+
*/
|
|
6415
|
+
function fileKey(file) {
|
|
6416
|
+
return `${file.name}:${file.size}:${file.lastModified}`;
|
|
6417
|
+
}
|
|
6418
|
+
|
|
6419
|
+
/**
|
|
6420
|
+
* Normalizes any file-shaped control value to a plain array.
|
|
6421
|
+
*
|
|
6422
|
+
* Accepts the shapes a control can realistically hold: a single `File` (single mode), an array of
|
|
6423
|
+
* files (multiple mode), a native `FileList` (a control fed straight from a DOM event or from the
|
|
6424
|
+
* deprecated `<hub-input type="file">`), or `null`/`undefined`.
|
|
6425
|
+
*
|
|
6426
|
+
* @param value - The raw control value.
|
|
6427
|
+
* @returns The files it contains, in order; an empty array when it holds none.
|
|
6428
|
+
*/
|
|
6429
|
+
function toFileArray(value) {
|
|
6430
|
+
if (value == null) {
|
|
6431
|
+
return [];
|
|
6432
|
+
}
|
|
6433
|
+
if (value instanceof File) {
|
|
6434
|
+
return [value];
|
|
6435
|
+
}
|
|
6436
|
+
if (Array.isArray(value)) {
|
|
6437
|
+
return value.filter((entry) => entry instanceof File);
|
|
6438
|
+
}
|
|
6439
|
+
if (typeof FileList !== 'undefined' && value instanceof FileList) {
|
|
6440
|
+
return Array.from(value);
|
|
6441
|
+
}
|
|
6442
|
+
return [];
|
|
6443
|
+
}
|
|
6444
|
+
|
|
6445
|
+
/**
|
|
6446
|
+
* Accessible file field with drag-and-drop, clipboard paste, per-file constraints, previews and
|
|
6447
|
+
* optional upload progress.
|
|
6448
|
+
*
|
|
6449
|
+
* A full `ng-hub-ui-forms` field: it extends {@link HubFieldControl}, so it binds with
|
|
6450
|
+
* `formControlName` / `ngModel` and shares the label, helper text and validation chrome with every
|
|
6451
|
+
* other control.
|
|
6452
|
+
*
|
|
6453
|
+
* The **form value stays native** — a `File` in single mode, a `File[]` in multiple mode, `null`
|
|
6454
|
+
* when empty — so it can be handed straight to a `FormData`. The rich per-file state (preview URL,
|
|
6455
|
+
* upload status, progress, error) lives in the {@link files} signal instead of contaminating the
|
|
6456
|
+
* control.
|
|
6457
|
+
*
|
|
6458
|
+
* Constraints declared as inputs (`accept`, `maxSize`, `minSize`, `maxTotalSize`, `maxFiles`) act as
|
|
6459
|
+
* a **filter**: an offending file never reaches the value and surfaces through {@link rejected}.
|
|
6460
|
+
* They are enforced by hand rather than delegated to the native `accept` attribute, which only
|
|
6461
|
+
* filters the operating-system dialog and is bypassed entirely by a drop or a paste. To make the
|
|
6462
|
+
* *control itself* invalid — the right choice when a value can also be patched in programmatically —
|
|
6463
|
+
* add the matching validators (`hubAcceptedFiles`, `hubMaxFileSize`, …).
|
|
6464
|
+
*
|
|
6465
|
+
* Uploading is opt-in and transport-agnostic: register a {@link HubFileUploader} with
|
|
6466
|
+
* `provideHubFileUploader()` and the component drives progress, cancel and retry. With no uploader
|
|
6467
|
+
* registered it is a pure picker.
|
|
6468
|
+
*
|
|
6469
|
+
* @example
|
|
6470
|
+
* ```html
|
|
6471
|
+
* <hub-file-input
|
|
6472
|
+
* formControlName="attachments"
|
|
6473
|
+
* label="Attachments"
|
|
6474
|
+
* [multiple]="true"
|
|
6475
|
+
* accept="image/*,.pdf"
|
|
6476
|
+
* [maxSize]="5 * 1024 * 1024"
|
|
6477
|
+
* [maxFiles]="3"
|
|
6478
|
+
* preview="grid"
|
|
6479
|
+
* (rejected)="notify($event)"
|
|
6480
|
+
* />
|
|
6481
|
+
* ```
|
|
6482
|
+
*/
|
|
6483
|
+
class HubFileInputComponent extends HubFieldControl {
|
|
6484
|
+
#config = inject(HUB_FORMS_CONFIG);
|
|
6485
|
+
#uploader = inject(HUB_FILE_UPLOADER, { optional: true });
|
|
6486
|
+
#isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
6487
|
+
#subscriptions = new Map();
|
|
6488
|
+
_formTextTypes = FormTextTypes;
|
|
6489
|
+
_items = signal([], /* @ts-ignore */
|
|
6490
|
+
...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
|
|
6491
|
+
/** Depth counter for `dragenter`/`dragleave`: children of the dropzone fire their own events. */
|
|
6492
|
+
#dragDepth = 0;
|
|
6493
|
+
/** Releases every object URL and aborts every in-flight upload when the field goes away. */
|
|
6494
|
+
_cleanup = inject(DestroyRef).onDestroy(() => {
|
|
6495
|
+
this.#subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
6496
|
+
this.#subscriptions.clear();
|
|
6497
|
+
this._items().forEach((item) => this.#revokePreview(item));
|
|
6498
|
+
});
|
|
6499
|
+
/** Label text. */
|
|
6500
|
+
label = input('', /* @ts-ignore */
|
|
6501
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6502
|
+
/** Whether more than one file can be held. Switches the control value to `File[]`. */
|
|
6503
|
+
multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6504
|
+
/** Accepted file types, e.g. `image/*,.pdf`. Enforced on drop and paste, not just in the dialog. */
|
|
6505
|
+
accept = input('*', /* @ts-ignore */
|
|
6506
|
+
...(ngDevMode ? [{ debugName: "accept" }] : /* istanbul ignore next */ []));
|
|
6507
|
+
/** Maximum size per file, in bytes. */
|
|
6508
|
+
maxSize = input(null, /* @ts-ignore */
|
|
6509
|
+
...(ngDevMode ? [{ debugName: "maxSize" }] : /* istanbul ignore next */ []));
|
|
6510
|
+
/** Minimum size per file, in bytes. Rejects the 0-byte files a failed export produces. */
|
|
6511
|
+
minSize = input(null, /* @ts-ignore */
|
|
6512
|
+
...(ngDevMode ? [{ debugName: "minSize" }] : /* istanbul ignore next */ []));
|
|
6513
|
+
/** Maximum combined size of every held file, in bytes. */
|
|
6514
|
+
maxTotalSize = input(null, /* @ts-ignore */
|
|
6515
|
+
...(ngDevMode ? [{ debugName: "maxTotalSize" }] : /* istanbul ignore next */ []));
|
|
6516
|
+
/** Maximum number of files. Only meaningful with `multiple`. */
|
|
6517
|
+
maxFiles = input(null, /* @ts-ignore */
|
|
6518
|
+
...(ngDevMode ? [{ debugName: "maxFiles" }] : /* istanbul ignore next */ []));
|
|
6519
|
+
/** Whether files can be dropped onto the field. */
|
|
6520
|
+
dragDrop = input(true, { ...(ngDevMode ? { debugName: "dragDrop" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6521
|
+
/** Whether files can be pasted into the focused field (e.g. a screenshot). */
|
|
6522
|
+
paste = input(true, { ...(ngDevMode ? { debugName: "paste" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6523
|
+
/** How the held files are rendered: not at all, as a list, or as a thumbnail grid. */
|
|
6524
|
+
preview = input('list', /* @ts-ignore */
|
|
6525
|
+
...(ngDevMode ? [{ debugName: "preview" }] : /* istanbul ignore next */ []));
|
|
6526
|
+
/** Whether the same file can be selected twice (keyed on name, size and last-modified date). */
|
|
6527
|
+
allowDuplicates = input(false, { ...(ngDevMode ? { debugName: "allowDuplicates" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6528
|
+
/** Opens the device camera instead of the file browser, on the platforms that support it. */
|
|
6529
|
+
capture = input(null, /* @ts-ignore */
|
|
6530
|
+
...(ngDevMode ? [{ debugName: "capture" }] : /* istanbul ignore next */ []));
|
|
6531
|
+
/** Whether a newly accepted file starts uploading immediately. Ignored with no uploader registered. */
|
|
6532
|
+
autoUpload = input(true, { ...(ngDevMode ? { debugName: "autoUpload" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
6533
|
+
/** Overrides the "browse" button text coming from the global labels. */
|
|
6534
|
+
buttonLabel = input(null, /* @ts-ignore */
|
|
6535
|
+
...(ngDevMode ? [{ debugName: "buttonLabel" }] : /* istanbul ignore next */ []));
|
|
6536
|
+
/** Overrides the auto-generated hint that summarizes the active constraints. Pass `''` to hide it. */
|
|
6537
|
+
hint = input(null, /* @ts-ignore */
|
|
6538
|
+
...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
|
|
6539
|
+
/** Helper text shown below the control. */
|
|
6540
|
+
formText = input('', /* @ts-ignore */
|
|
6541
|
+
...(ngDevMode ? [{ debugName: "formText" }] : /* istanbul ignore next */ []));
|
|
6542
|
+
/** Helper text placement. Only `bottom` is supported. */
|
|
6543
|
+
formTextType = input(FormTextTypes.Bottom, /* @ts-ignore */
|
|
6544
|
+
...(ngDevMode ? [{ debugName: "formTextType" }] : /* istanbul ignore next */ []));
|
|
6545
|
+
/** Extra CSS classes applied to the host element. */
|
|
6546
|
+
classlist = input('', /* @ts-ignore */
|
|
6547
|
+
...(ngDevMode ? [{ debugName: "classlist" }] : /* istanbul ignore next */ []));
|
|
6548
|
+
/** Emits the native value whenever the selection changes. */
|
|
6549
|
+
valueChange = output();
|
|
6550
|
+
/** Emits the files refused by the declared constraints, with the reason for each. */
|
|
6551
|
+
rejected = output();
|
|
6552
|
+
/** Emits the file removed from the selection. */
|
|
6553
|
+
fileRemoved = output();
|
|
6554
|
+
/** Emits the full item list whenever an upload changes status or progress. */
|
|
6555
|
+
uploadStateChange = output();
|
|
6556
|
+
/** The hidden native file input, kept focusable so the dropzone label activates it. */
|
|
6557
|
+
nativeInput = viewChild('nativeInput', /* @ts-ignore */
|
|
6558
|
+
...(ngDevMode ? [{ debugName: "nativeInput" }] : /* istanbul ignore next */ []));
|
|
6559
|
+
/** Projected per-file icon template. */
|
|
6560
|
+
iconTpt = contentChild(HubFileIconDirective, /* @ts-ignore */
|
|
6561
|
+
...(ngDevMode ? [{ debugName: "iconTpt" }] : /* istanbul ignore next */ []));
|
|
6562
|
+
/** Projected per-file preview template, replacing the built-in item rendering. */
|
|
6563
|
+
previewTpt = contentChild(HubFilePreviewDirective, /* @ts-ignore */
|
|
6564
|
+
...(ngDevMode ? [{ debugName: "previewTpt" }] : /* istanbul ignore next */ []));
|
|
6565
|
+
/** Whether a drag is currently hovering the dropzone. */
|
|
6566
|
+
isDragging = signal(false, /* @ts-ignore */
|
|
6567
|
+
...(ngDevMode ? [{ debugName: "isDragging" }] : /* istanbul ignore next */ []));
|
|
6568
|
+
/** Latest message for the screen-reader live region. */
|
|
6569
|
+
announcement = signal('', /* @ts-ignore */
|
|
6570
|
+
...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
|
|
6571
|
+
/** The rich, per-file state of the current selection. */
|
|
6572
|
+
files = this._items.asReadonly();
|
|
6573
|
+
/** Whether at least one file is currently uploading. Gate a submit button on this. */
|
|
6574
|
+
uploading = computed(() => this._items().some((item) => item.status === 'uploading'), /* @ts-ignore */
|
|
6575
|
+
...(ngDevMode ? [{ debugName: "uploading" }] : /* istanbul ignore next */ []));
|
|
6576
|
+
/** Whether an uploader is registered, which is what enables progress, cancel and retry. */
|
|
6577
|
+
hasUploader = !!this.#uploader;
|
|
6578
|
+
/** The resolved, localizable labels. */
|
|
6579
|
+
labels = computed(() => this.#config.fileInput, /* @ts-ignore */
|
|
6580
|
+
...(ngDevMode ? [{ debugName: "labels" }] : /* istanbul ignore next */ []));
|
|
6581
|
+
/** Text of the browse action. */
|
|
6582
|
+
browseLabel = computed(() => this.buttonLabel() ?? this.labels().browse, /* @ts-ignore */
|
|
6583
|
+
...(ngDevMode ? [{ debugName: "browseLabel" }] : /* istanbul ignore next */ []));
|
|
6584
|
+
/** The hint summarizing the active constraints. */
|
|
6585
|
+
constraintsHint = computed(() => {
|
|
6586
|
+
const override = this.hint();
|
|
6587
|
+
if (override != null) {
|
|
6588
|
+
return override;
|
|
6589
|
+
}
|
|
6590
|
+
const accept = this.accept();
|
|
6591
|
+
return this.labels().constraints({
|
|
6592
|
+
accept: !accept || accept === '*' || accept === '*/*' ? null : accept,
|
|
6593
|
+
maxSize: this.maxSize(),
|
|
6594
|
+
maxFiles: this.multiple() ? this.maxFiles() : null
|
|
6595
|
+
});
|
|
6596
|
+
}, /* @ts-ignore */
|
|
6597
|
+
...(ngDevMode ? [{ debugName: "constraintsHint" }] : /* istanbul ignore next */ []));
|
|
6598
|
+
/** Whether the dropzone currently accepts a drop. */
|
|
6599
|
+
dropEnabled = computed(() => this.dragDrop() && !this.disabled(), /* @ts-ignore */
|
|
6600
|
+
...(ngDevMode ? [{ debugName: "dropEnabled" }] : /* istanbul ignore next */ []));
|
|
6601
|
+
/**
|
|
6602
|
+
* Derives the native validation errors from the held files rather than from the DOM.
|
|
6603
|
+
*
|
|
6604
|
+
* The hidden native input is emptied after every `change` so the same file can be picked twice,
|
|
6605
|
+
* and it never sees a dropped or pasted file at all. Its `validity` therefore reports
|
|
6606
|
+
* `valueMissing` even when files are held — reading it would mark a satisfied field as required.
|
|
6607
|
+
*
|
|
6608
|
+
* @param _target - Ignored; the file state is the source of truth.
|
|
6609
|
+
*/
|
|
6610
|
+
updateNativeErrors(_target) {
|
|
6611
|
+
if (this._control) {
|
|
6612
|
+
return;
|
|
6613
|
+
}
|
|
6614
|
+
this._nativeErrors.set(this.required() && this._items().length === 0 ? { required: true } : null);
|
|
6615
|
+
}
|
|
6616
|
+
/**
|
|
6617
|
+
* Resolves a human-readable message for a failed upload.
|
|
6618
|
+
*
|
|
6619
|
+
* @param item - The item in the `error` state.
|
|
6620
|
+
* @returns The error message.
|
|
6621
|
+
*/
|
|
6622
|
+
errorText(item) {
|
|
6623
|
+
const error = item.error;
|
|
6624
|
+
return error?.message ?? String(error ?? '');
|
|
6625
|
+
}
|
|
6626
|
+
writeValue(value) {
|
|
6627
|
+
this._items().forEach((item) => this.#revokePreview(item));
|
|
6628
|
+
this.#subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
6629
|
+
this.#subscriptions.clear();
|
|
6630
|
+
this._items.set(toFileArray(value).map((file) => this.#createItem(file)));
|
|
6631
|
+
}
|
|
6632
|
+
/** Opens the native file dialog. */
|
|
6633
|
+
open() {
|
|
6634
|
+
if (!this.disabled()) {
|
|
6635
|
+
this.nativeInput()?.nativeElement.click();
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
/**
|
|
6639
|
+
* Removes a file from the selection, aborting its upload and releasing its preview.
|
|
6640
|
+
*
|
|
6641
|
+
* @param id - The id of the item to remove.
|
|
6642
|
+
*/
|
|
6643
|
+
remove(id) {
|
|
6644
|
+
const item = this._items().find((candidate) => candidate.id === id);
|
|
6645
|
+
if (!item || this.disabled()) {
|
|
6646
|
+
return;
|
|
6647
|
+
}
|
|
6648
|
+
this.#abort(id);
|
|
6649
|
+
this.#revokePreview(item);
|
|
6650
|
+
this._items.update((items) => items.filter((candidate) => candidate.id !== id));
|
|
6651
|
+
this.#emitValue();
|
|
6652
|
+
this.fileRemoved.emit(item.file);
|
|
6653
|
+
this.#announce(this.labels().filesSelected(this._items().length));
|
|
6654
|
+
}
|
|
6655
|
+
/** Removes every file, aborting any upload in flight. */
|
|
6656
|
+
clear() {
|
|
6657
|
+
if (this.disabled()) {
|
|
6658
|
+
return;
|
|
6659
|
+
}
|
|
6660
|
+
this.#subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
6661
|
+
this.#subscriptions.clear();
|
|
6662
|
+
this._items().forEach((item) => this.#revokePreview(item));
|
|
6663
|
+
this._items.set([]);
|
|
6664
|
+
this.#emitValue();
|
|
6665
|
+
this.#announce(this.labels().filesSelected(0));
|
|
6666
|
+
}
|
|
6667
|
+
/** Uploads every file that has not been uploaded yet. No-op without a registered uploader. */
|
|
6668
|
+
upload() {
|
|
6669
|
+
this._items()
|
|
6670
|
+
.filter((item) => item.status === 'ready' || item.status === 'error')
|
|
6671
|
+
.forEach((item) => this.#startUpload(item.id));
|
|
6672
|
+
}
|
|
6673
|
+
/**
|
|
6674
|
+
* Aborts an in-flight upload, returning the file to the `ready` state.
|
|
6675
|
+
*
|
|
6676
|
+
* @param id - The id of the uploading item.
|
|
6677
|
+
*/
|
|
6678
|
+
cancel(id) {
|
|
6679
|
+
if (!this.#subscriptions.has(id)) {
|
|
6680
|
+
return;
|
|
6681
|
+
}
|
|
6682
|
+
this.#abort(id);
|
|
6683
|
+
this.#patch(id, { status: 'ready', progress: null, error: null });
|
|
6684
|
+
}
|
|
6685
|
+
/**
|
|
6686
|
+
* Re-runs the upload of a file, typically after a failure.
|
|
6687
|
+
*
|
|
6688
|
+
* @param id - The id of the item to upload again.
|
|
6689
|
+
*/
|
|
6690
|
+
retry(id) {
|
|
6691
|
+
this.#startUpload(id);
|
|
6692
|
+
}
|
|
6693
|
+
/**
|
|
6694
|
+
* Reads the files chosen through the native dialog.
|
|
6695
|
+
*
|
|
6696
|
+
* @param event - The `change` event of the hidden native input.
|
|
6697
|
+
*/
|
|
6698
|
+
handleNativeChange(event) {
|
|
6699
|
+
const native = event.target;
|
|
6700
|
+
this.#addFiles(Array.from(native.files ?? []));
|
|
6701
|
+
// Without this, re-picking the very same file fires no second `change` event.
|
|
6702
|
+
native.value = '';
|
|
6703
|
+
}
|
|
6704
|
+
/**
|
|
6705
|
+
* Accepts files dropped onto the dropzone.
|
|
6706
|
+
*
|
|
6707
|
+
* @param event - The drop event.
|
|
6708
|
+
*/
|
|
6709
|
+
handleDrop(event) {
|
|
6710
|
+
if (!this.dropEnabled()) {
|
|
6711
|
+
return;
|
|
6712
|
+
}
|
|
6713
|
+
event.preventDefault();
|
|
6714
|
+
this.#dragDepth = 0;
|
|
6715
|
+
this.isDragging.set(false);
|
|
6716
|
+
this.#addFiles(this.#extractFiles(event.dataTransfer));
|
|
6717
|
+
this.handleBlur();
|
|
6718
|
+
}
|
|
6719
|
+
/**
|
|
6720
|
+
* Highlights the dropzone once a drag carrying files enters it.
|
|
6721
|
+
*
|
|
6722
|
+
* @param event - The dragenter event.
|
|
6723
|
+
*/
|
|
6724
|
+
handleDragEnter(event) {
|
|
6725
|
+
if (!this.dropEnabled() || !this.#carriesFiles(event)) {
|
|
6726
|
+
return;
|
|
6727
|
+
}
|
|
6728
|
+
event.preventDefault();
|
|
6729
|
+
this.#dragDepth++;
|
|
6730
|
+
this.isDragging.set(true);
|
|
6731
|
+
}
|
|
6732
|
+
/**
|
|
6733
|
+
* Keeps the drop target alive. A `dragover` that does not call `preventDefault` refuses the drop.
|
|
6734
|
+
*
|
|
6735
|
+
* @param event - The dragover event.
|
|
6736
|
+
*/
|
|
6737
|
+
handleDragOver(event) {
|
|
6738
|
+
if (!this.dropEnabled() || !this.#carriesFiles(event)) {
|
|
6739
|
+
return;
|
|
6740
|
+
}
|
|
6741
|
+
event.preventDefault();
|
|
6742
|
+
if (event.dataTransfer) {
|
|
6743
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
6744
|
+
}
|
|
6745
|
+
}
|
|
6746
|
+
/**
|
|
6747
|
+
* Un-highlights the dropzone, but only once the drag has left it *and* all of its children.
|
|
6748
|
+
*
|
|
6749
|
+
* @param event - The dragleave event.
|
|
6750
|
+
*/
|
|
6751
|
+
handleDragLeave(event) {
|
|
6752
|
+
if (!this.dropEnabled()) {
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
event.preventDefault();
|
|
6756
|
+
this.#dragDepth = Math.max(0, this.#dragDepth - 1);
|
|
6757
|
+
if (this.#dragDepth === 0) {
|
|
6758
|
+
this.isDragging.set(false);
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
/**
|
|
6762
|
+
* Accepts files pasted into the focused field, e.g. a screenshot from the clipboard.
|
|
6763
|
+
*
|
|
6764
|
+
* @param event - The paste event.
|
|
6765
|
+
*/
|
|
6766
|
+
handlePaste(event) {
|
|
6767
|
+
if (!this.paste() || this.disabled()) {
|
|
6768
|
+
return;
|
|
6769
|
+
}
|
|
6770
|
+
const files = this.#extractFiles(event.clipboardData);
|
|
6771
|
+
if (files.length > 0) {
|
|
6772
|
+
event.preventDefault();
|
|
6773
|
+
this.#addFiles(files);
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
/**
|
|
6777
|
+
* Builds the context handed to a projected `hubFilePreview` template.
|
|
6778
|
+
*
|
|
6779
|
+
* @param item - The item being rendered.
|
|
6780
|
+
* @returns The template context, complete with the actions the item needs.
|
|
6781
|
+
*/
|
|
6782
|
+
previewContext(item) {
|
|
6783
|
+
return {
|
|
6784
|
+
$implicit: item,
|
|
6785
|
+
remove: () => this.remove(item.id),
|
|
6786
|
+
retry: () => this.retry(item.id),
|
|
6787
|
+
cancel: () => this.cancel(item.id)
|
|
6788
|
+
};
|
|
6789
|
+
}
|
|
6790
|
+
/**
|
|
6791
|
+
* Validates and appends the incoming files, emitting the new value and any rejections.
|
|
6792
|
+
*
|
|
6793
|
+
* In single mode the incoming file replaces the current one, so the constraint checks run against
|
|
6794
|
+
* an empty baseline rather than against the file about to be discarded.
|
|
6795
|
+
*
|
|
6796
|
+
* @param incoming - The files to add.
|
|
6797
|
+
*/
|
|
6798
|
+
#addFiles(incoming) {
|
|
6799
|
+
if (this.disabled() || incoming.length === 0) {
|
|
6800
|
+
return;
|
|
6801
|
+
}
|
|
6802
|
+
const multiple = this.multiple();
|
|
6803
|
+
const current = this._items();
|
|
6804
|
+
const baseline = multiple ? current : [];
|
|
6805
|
+
const accepted = [];
|
|
6806
|
+
const rejections = [];
|
|
6807
|
+
const keys = new Set(baseline.map((item) => fileKey(item.file)));
|
|
6808
|
+
let count = baseline.length;
|
|
6809
|
+
let total = baseline.reduce((sum, item) => sum + item.file.size, 0);
|
|
6810
|
+
for (const file of incoming) {
|
|
6811
|
+
const rejection = this.#reject(file, count, total, keys);
|
|
6812
|
+
if (rejection) {
|
|
6813
|
+
rejections.push(rejection);
|
|
6814
|
+
continue;
|
|
6815
|
+
}
|
|
6816
|
+
keys.add(fileKey(file));
|
|
6817
|
+
count++;
|
|
6818
|
+
total += file.size;
|
|
6819
|
+
accepted.push(this.#createItem(file));
|
|
6820
|
+
}
|
|
6821
|
+
if (accepted.length > 0) {
|
|
6822
|
+
if (!multiple) {
|
|
6823
|
+
current.forEach((item) => this.#abort(item.id));
|
|
6824
|
+
current.forEach((item) => this.#revokePreview(item));
|
|
6825
|
+
}
|
|
6826
|
+
this._items.set([...baseline, ...accepted]);
|
|
6827
|
+
this.#emitValue();
|
|
6828
|
+
if (this.#uploader && this.autoUpload()) {
|
|
6829
|
+
accepted.forEach((item) => this.#startUpload(item.id));
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6832
|
+
if (rejections.length > 0) {
|
|
6833
|
+
this.rejected.emit(rejections);
|
|
6834
|
+
}
|
|
6835
|
+
this.#announce(this.#summarize(accepted.length, rejections));
|
|
6836
|
+
}
|
|
6837
|
+
/**
|
|
6838
|
+
* Applies the declared constraints to a single candidate file.
|
|
6839
|
+
*
|
|
6840
|
+
* @param file - The candidate.
|
|
6841
|
+
* @param count - How many files are already accepted.
|
|
6842
|
+
* @param total - The combined size of the already-accepted files.
|
|
6843
|
+
* @param keys - Identity keys of the already-accepted files, for duplicate detection.
|
|
6844
|
+
* @returns The rejection, or `null` when the file passes every constraint.
|
|
6845
|
+
*/
|
|
6846
|
+
#reject(file, count, total, keys) {
|
|
6847
|
+
const build = (reason, limit) => ({
|
|
6848
|
+
file,
|
|
6849
|
+
reason,
|
|
6850
|
+
limit
|
|
6851
|
+
});
|
|
6852
|
+
if (!this.allowDuplicates() && keys.has(fileKey(file))) {
|
|
6853
|
+
return build('duplicate', null);
|
|
6854
|
+
}
|
|
6855
|
+
if (!matchesAccept(file, this.accept())) {
|
|
6856
|
+
return build('accept', this.accept());
|
|
6857
|
+
}
|
|
6858
|
+
const minSize = this.minSize();
|
|
6859
|
+
if (minSize != null && file.size < minSize) {
|
|
6860
|
+
return build('minSize', minSize);
|
|
6861
|
+
}
|
|
6862
|
+
const maxSize = this.maxSize();
|
|
6863
|
+
if (maxSize != null && file.size > maxSize) {
|
|
6864
|
+
return build('maxSize', maxSize);
|
|
6865
|
+
}
|
|
6866
|
+
const maxFiles = this.multiple() ? this.maxFiles() : 1;
|
|
6867
|
+
if (maxFiles != null && count + 1 > maxFiles) {
|
|
6868
|
+
return build('maxFiles', maxFiles);
|
|
6869
|
+
}
|
|
6870
|
+
const maxTotalSize = this.maxTotalSize();
|
|
6871
|
+
if (maxTotalSize != null && total + file.size > maxTotalSize) {
|
|
6872
|
+
return build('maxTotalSize', maxTotalSize);
|
|
6873
|
+
}
|
|
6874
|
+
return null;
|
|
6875
|
+
}
|
|
6876
|
+
/**
|
|
6877
|
+
* Wraps a file in an item, minting its preview URL when it is a previewable image.
|
|
6878
|
+
*
|
|
6879
|
+
* @param file - The accepted file.
|
|
6880
|
+
* @returns The new item, in the `ready` state.
|
|
6881
|
+
*/
|
|
6882
|
+
#createItem(file) {
|
|
6883
|
+
const previewable = this.#isBrowser && this.preview() !== 'none' && file.type.startsWith('image/');
|
|
6884
|
+
return {
|
|
6885
|
+
id: uuid(),
|
|
6886
|
+
file,
|
|
6887
|
+
previewUrl: previewable ? URL.createObjectURL(file) : null,
|
|
6888
|
+
status: 'ready',
|
|
6889
|
+
progress: null,
|
|
6890
|
+
error: null
|
|
6891
|
+
};
|
|
6892
|
+
}
|
|
6893
|
+
/**
|
|
6894
|
+
* Releases the object URL of an item. Skipping this leaks the whole file for the page's lifetime.
|
|
6895
|
+
*
|
|
6896
|
+
* @param item - The item whose preview should be released.
|
|
6897
|
+
*/
|
|
6898
|
+
#revokePreview(item) {
|
|
6899
|
+
if (item.previewUrl && this.#isBrowser) {
|
|
6900
|
+
URL.revokeObjectURL(item.previewUrl);
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
/** Writes the native value to the form control and emits it. */
|
|
6904
|
+
#emitValue() {
|
|
6905
|
+
const files = this._items().map((item) => item.file);
|
|
6906
|
+
const value = this.multiple() ? files : (files[0] ?? null);
|
|
6907
|
+
this.onChange?.(value);
|
|
6908
|
+
this.valueChange.emit(value);
|
|
6909
|
+
}
|
|
6910
|
+
/**
|
|
6911
|
+
* Subscribes to the registered uploader for one item, mapping its events onto the item's status.
|
|
6912
|
+
*
|
|
6913
|
+
* @param id - The id of the item to upload.
|
|
6914
|
+
*/
|
|
6915
|
+
#startUpload(id) {
|
|
6916
|
+
const uploader = this.#uploader;
|
|
6917
|
+
const item = this._items().find((candidate) => candidate.id === id);
|
|
6918
|
+
if (!uploader || !item) {
|
|
6919
|
+
return;
|
|
6920
|
+
}
|
|
6921
|
+
this.#abort(id);
|
|
6922
|
+
this.#patch(id, { status: 'uploading', progress: null, error: null });
|
|
6923
|
+
const subscription = uploader.upload(item.file, { id }).subscribe({
|
|
6924
|
+
next: (event) => {
|
|
6925
|
+
if (event.status === 'progress') {
|
|
6926
|
+
// A transport that cannot report a total leaves `progress` null, which the template
|
|
6927
|
+
// renders as an indeterminate bar rather than a bar frozen at 0%.
|
|
6928
|
+
const progress = event.total ? Math.round((event.loaded / event.total) * 100) : null;
|
|
6929
|
+
this.#patch(id, { status: 'uploading', progress });
|
|
6930
|
+
return;
|
|
6931
|
+
}
|
|
6932
|
+
if (event.status === 'done') {
|
|
6933
|
+
this.#patch(id, { status: 'done', progress: 100, error: null });
|
|
6934
|
+
return;
|
|
6935
|
+
}
|
|
6936
|
+
this.#patch(id, { status: 'error', error: event.error });
|
|
6937
|
+
},
|
|
6938
|
+
error: (error) => this.#patch(id, { status: 'error', error }),
|
|
6939
|
+
complete: () => {
|
|
6940
|
+
// An uploader that completes without emitting `done` still finished successfully.
|
|
6941
|
+
const current = this._items().find((candidate) => candidate.id === id);
|
|
6942
|
+
if (current?.status === 'uploading') {
|
|
6943
|
+
this.#patch(id, { status: 'done', progress: 100, error: null });
|
|
6944
|
+
}
|
|
6945
|
+
}
|
|
6946
|
+
});
|
|
6947
|
+
this.#subscriptions.set(id, subscription);
|
|
6948
|
+
}
|
|
6949
|
+
/**
|
|
6950
|
+
* Unsubscribes from an item's upload, which aborts the underlying request.
|
|
6951
|
+
*
|
|
6952
|
+
* @param id - The id of the item.
|
|
6953
|
+
*/
|
|
6954
|
+
#abort(id) {
|
|
6955
|
+
this.#subscriptions.get(id)?.unsubscribe();
|
|
6956
|
+
this.#subscriptions.delete(id);
|
|
6957
|
+
}
|
|
6958
|
+
/**
|
|
6959
|
+
* Immutably updates one item and notifies listeners of the new upload state.
|
|
6960
|
+
*
|
|
6961
|
+
* @param id - The id of the item to update.
|
|
6962
|
+
* @param patch - The fields to overwrite.
|
|
6963
|
+
*/
|
|
6964
|
+
#patch(id, patch) {
|
|
6965
|
+
let changed = false;
|
|
6966
|
+
this._items.update((items) => items.map((item) => {
|
|
6967
|
+
if (item.id !== id) {
|
|
6968
|
+
return item;
|
|
6969
|
+
}
|
|
6970
|
+
changed = true;
|
|
6971
|
+
return { ...item, ...patch };
|
|
6972
|
+
}));
|
|
6973
|
+
if (changed) {
|
|
6974
|
+
this.uploadStateChange.emit(this._items());
|
|
6975
|
+
}
|
|
6976
|
+
}
|
|
6977
|
+
/**
|
|
6978
|
+
* Extracts the files carried by a drop or a paste.
|
|
6979
|
+
*
|
|
6980
|
+
* Reads `items` when present so non-file payloads (dragged text, HTML) are skipped, and falls
|
|
6981
|
+
* back to `files` otherwise.
|
|
6982
|
+
*
|
|
6983
|
+
* @param transfer - The `DataTransfer` of the event.
|
|
6984
|
+
* @returns The files it carries.
|
|
6985
|
+
*/
|
|
6986
|
+
#extractFiles(transfer) {
|
|
6987
|
+
if (!transfer) {
|
|
6988
|
+
return [];
|
|
6989
|
+
}
|
|
6990
|
+
if (transfer.items?.length) {
|
|
6991
|
+
return Array.from(transfer.items)
|
|
6992
|
+
.filter((item) => item.kind === 'file')
|
|
6993
|
+
.map((item) => item.getAsFile())
|
|
6994
|
+
.filter((file) => file !== null);
|
|
6995
|
+
}
|
|
6996
|
+
return Array.from(transfer.files ?? []);
|
|
6997
|
+
}
|
|
6998
|
+
/**
|
|
6999
|
+
* Whether a drag event carries files, as opposed to text or a page element.
|
|
7000
|
+
*
|
|
7001
|
+
* @param event - The drag event.
|
|
7002
|
+
* @returns `true` when the payload includes files.
|
|
7003
|
+
*/
|
|
7004
|
+
#carriesFiles(event) {
|
|
7005
|
+
return Array.from(event.dataTransfer?.types ?? []).includes('Files');
|
|
7006
|
+
}
|
|
7007
|
+
/**
|
|
7008
|
+
* Builds the live-region message describing the outcome of an add.
|
|
7009
|
+
*
|
|
7010
|
+
* @param acceptedCount - How many files were accepted.
|
|
7011
|
+
* @param rejections - The files that were refused.
|
|
7012
|
+
* @returns The message to announce.
|
|
7013
|
+
*/
|
|
7014
|
+
#summarize(acceptedCount, rejections) {
|
|
7015
|
+
const labels = this.labels();
|
|
7016
|
+
const parts = [];
|
|
7017
|
+
if (acceptedCount > 0) {
|
|
7018
|
+
parts.push(labels.filesSelected(this._items().length));
|
|
7019
|
+
}
|
|
7020
|
+
rejections.forEach((rejection) => parts.push(labels.rejection(rejection)));
|
|
7021
|
+
return parts.join(' ');
|
|
7022
|
+
}
|
|
7023
|
+
/**
|
|
7024
|
+
* Publishes a message to the live region, forcing a re-announcement of identical text.
|
|
7025
|
+
*
|
|
7026
|
+
* @param message - The message to announce.
|
|
7027
|
+
*/
|
|
7028
|
+
#announce(message) {
|
|
7029
|
+
if (!message) {
|
|
7030
|
+
return;
|
|
7031
|
+
}
|
|
7032
|
+
// A screen reader ignores a live region whose text did not change; the zero-width space makes
|
|
7033
|
+
// two consecutive identical messages differ.
|
|
7034
|
+
this.announcement.set(this.announcement() === message ? `${message}` : message);
|
|
7035
|
+
}
|
|
7036
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFileInputComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
7037
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.1", type: HubFileInputComponent, isStandalone: true, selector: "hub-file-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, accept: { classPropertyName: "accept", publicName: "accept", isSignal: true, isRequired: false, transformFunction: null }, maxSize: { classPropertyName: "maxSize", publicName: "maxSize", isSignal: true, isRequired: false, transformFunction: null }, minSize: { classPropertyName: "minSize", publicName: "minSize", isSignal: true, isRequired: false, transformFunction: null }, maxTotalSize: { classPropertyName: "maxTotalSize", publicName: "maxTotalSize", isSignal: true, isRequired: false, transformFunction: null }, maxFiles: { classPropertyName: "maxFiles", publicName: "maxFiles", isSignal: true, isRequired: false, transformFunction: null }, dragDrop: { classPropertyName: "dragDrop", publicName: "dragDrop", isSignal: true, isRequired: false, transformFunction: null }, paste: { classPropertyName: "paste", publicName: "paste", isSignal: true, isRequired: false, transformFunction: null }, preview: { classPropertyName: "preview", publicName: "preview", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, capture: { classPropertyName: "capture", publicName: "capture", isSignal: true, isRequired: false, transformFunction: null }, autoUpload: { classPropertyName: "autoUpload", publicName: "autoUpload", isSignal: true, isRequired: false, transformFunction: null }, buttonLabel: { classPropertyName: "buttonLabel", publicName: "buttonLabel", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, formText: { classPropertyName: "formText", publicName: "formText", isSignal: true, isRequired: false, transformFunction: null }, formTextType: { classPropertyName: "formTextType", publicName: "formTextType", isSignal: true, isRequired: false, transformFunction: null }, classlist: { classPropertyName: "classlist", publicName: "classlist", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", rejected: "rejected", fileRemoved: "fileRemoved", uploadStateChange: "uploadStateChange" }, host: { listeners: { "paste": "handlePaste($event)" }, properties: { "class": "classlist()", "class.hub-file-input-host": "true" } }, queries: [{ propertyName: "iconTpt", first: true, predicate: HubFileIconDirective, descendants: true, isSignal: true }, { propertyName: "previewTpt", first: true, predicate: HubFilePreviewDirective, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "nativeInput", first: true, predicate: ["nativeInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div\n\tclass=\"hub-field hub-file-input\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body\">\n\t\t<!--\n\t\t\tThe dropzone is a <label> bound to the hidden native input: clicking or activating it with\n\t\t\tthe keyboard opens the file dialog through the platform, so no role, tabindex or key\n\t\t\thandler is needed. The \"browse\" affordance is a <span>, not a <button>, because a button\n\t\t\tinside a label would open the dialog twice.\n\t\t-->\n\t\t<label\n\t\t\tclass=\"hub-file-input__dropzone\"\n\t\t\t[for]=\"id\"\n\t\t\t[class.hub-file-input__dropzone--dragover]=\"isDragging()\"\n\t\t\t[class.hub-file-input__dropzone--no-drop]=\"!dragDrop()\"\n\t\t\t[class.hub-file-input__dropzone--invalid]=\"isInvalid\"\n\t\t\t(dragenter)=\"handleDragEnter($event)\"\n\t\t\t(dragover)=\"handleDragOver($event)\"\n\t\t\t(dragleave)=\"handleDragLeave($event)\"\n\t\t\t(drop)=\"handleDrop($event)\"\n\t\t>\n\t\t\t<input\n\t\t\t\t#nativeInput\n\t\t\t\ttype=\"file\"\n\t\t\t\tclass=\"hub-file-input__native\"\n\t\t\t\t[id]=\"id\"\n\t\t\t\t[accept]=\"accept()\"\n\t\t\t\t[multiple]=\"multiple()\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\t[attr.capture]=\"capture()\"\n\t\t\t\t[attr.aria-required]=\"required() ? 'true' : null\"\n\t\t\t\t[attr.aria-invalid]=\"isInvalid\"\n\t\t\t\t[attr.aria-describedby]=\"constraintsHint() ? id + '-hint' : null\"\n\t\t\t\t(change)=\"handleNativeChange($event)\"\n\t\t\t\t(blur)=\"handleBlur($event)\"\n\t\t\t/>\n\n\t\t\t<span class=\"hub-file-input__icon\" aria-hidden=\"true\"></span>\n\n\t\t\t<span class=\"hub-file-input__prompt\">\n\t\t\t\t@if (dragDrop()) {\n\t\t\t\t\t<span class=\"hub-file-input__drop-text\">{{ labels().dropHere }}</span>\n\t\t\t\t}\n\t\t\t\t<span class=\"hub-file-input__browse\">{{ browseLabel() }}</span>\n\t\t\t</span>\n\n\t\t\t@if (constraintsHint()) {\n\t\t\t\t<span class=\"hub-file-input__hint\" [id]=\"id + '-hint'\">{{ constraintsHint() }}</span>\n\t\t\t}\n\t\t</label>\n\n\t\t@if (preview() !== 'none' && _items().length > 0) {\n\t\t\t<ul class=\"hub-file-input__list\" [class.hub-file-input__list--grid]=\"preview() === 'grid'\">\n\t\t\t\t@for (item of _items(); track item.id) {\n\t\t\t\t\t<li class=\"hub-file-input__item\" [attr.data-status]=\"item.status\">\n\t\t\t\t\t\t@if (previewTpt(); as tpt) {\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"tpt.template\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"previewContext(item)\"\n\t\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t<span class=\"hub-file-input__thumb\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t@if (item.previewUrl) {\n\t\t\t\t\t\t\t\t\t<img class=\"hub-file-input__image\" [src]=\"item.previewUrl\" alt=\"\" />\n\t\t\t\t\t\t\t\t} @else if (iconTpt(); as icon) {\n\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"icon.template\"\n\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: item }\"\n\t\t\t\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t<span class=\"hub-file-input__file-icon\"></span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<span class=\"hub-file-input__meta\">\n\t\t\t\t\t\t\t\t<span class=\"hub-file-input__name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n\t\t\t\t\t\t\t\t<span class=\"hub-file-input__size\">{{ labels().formatSize(item.file.size) }}</span>\n\n\t\t\t\t\t\t\t\t@if (item.status === 'uploading') {\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__progress\"\n\t\t\t\t\t\t\t\t\t\t[class.hub-file-input__progress--indeterminate]=\"item.progress === null\"\n\t\t\t\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\t\t\t\taria-valuemax=\"100\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-valuenow]=\"item.progress\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__progress-bar\"\n\t\t\t\t\t\t\t\t\t\t\t[style.--hub-file-input-progress-value]=\"item.progress ?? 0\"\n\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t@if (item.status === 'error') {\n\t\t\t\t\t\t\t\t\t<span class=\"hub-file-input__error\">{{ errorText(item) }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<span class=\"hub-file-input__actions\">\n\t\t\t\t\t\t\t\t@if (item.status === 'uploading') {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__action hub-file-input__action--cancel\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"labels().cancel\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"cancel(item.id)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t@if (item.status === 'error') {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__action hub-file-input__action--retry\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"labels().retry\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"retry(item.id)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t@if (!disabled() && item.status !== 'uploading') {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__action hub-file-input__action--remove\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"labels().remove\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"remove(item.id)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t}\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\n\t\t\t@if (_items().length > 1 && !disabled()) {\n\t\t\t\t<button type=\"button\" class=\"hub-file-input__clear\" (click)=\"clear()\">{{ labels().clear }}</button>\n\t\t\t}\n\t\t}\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n\n\t<span class=\"hub-file-input__live\" aria-live=\"polite\">{{ announcement() }}</span>\n</div>\n", styles: [".hub-file-input__dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--hub-file-input-gap);min-height:var(--hub-file-input-min-height);padding:var(--hub-file-input-padding-y) var(--hub-file-input-padding-x);color:var(--hub-file-input-color);background-color:var(--hub-file-input-bg);border:var(--hub-file-input-border-width) var(--hub-file-input-border-style) var(--hub-file-input-border-color);border-radius:var(--hub-file-input-border-radius);text-align:center;cursor:pointer;transition:var(--hub-file-input-transition)}.hub-file-input__dropzone:hover{background-color:var(--hub-file-input-hover-bg);border-color:var(--hub-file-input-hover-border-color)}.hub-file-input__dropzone:has(.hub-file-input__native:focus-visible){border-color:var(--hub-file-input-focus-border-color);box-shadow:var(--hub-file-input-focus-box-shadow);outline:none}.hub-file-input__dropzone--dragover,.hub-file-input__dropzone--dragover:hover{background-color:var(--hub-file-input-dragover-bg);border-color:var(--hub-file-input-dragover-border-color)}.hub-file-input__dropzone--invalid{border-color:var(--hub-form-invalid-border-color)}.hub-file-input__dropzone:has(.hub-file-input__native:disabled){cursor:not-allowed}.hub-file-input__dropzone:has(.hub-file-input__native:disabled):hover{background-color:var(--hub-file-input-bg);border-color:var(--hub-file-input-border-color)}.hub-file-input__native{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.hub-file-input__icon{display:block;width:var(--hub-file-input-icon-size);height:var(--hub-file-input-icon-size);background-color:var(--hub-file-input-icon-color);-webkit-mask:var(--hub-file-input-icon) center/contain no-repeat;mask:var(--hub-file-input-icon) center/contain no-repeat}.hub-file-input__prompt{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:center;gap:.25em;color:var(--hub-file-input-prompt-color);font-size:var(--hub-file-input-prompt-font-size)}.hub-file-input__browse{color:var(--hub-file-input-browse-color);font-weight:var(--hub-file-input-browse-font-weight);text-decoration:underline}.hub-file-input__hint{color:var(--hub-file-input-hint-color);font-size:var(--hub-file-input-hint-font-size)}.hub-file-input__list{display:flex;flex-direction:column;gap:var(--hub-file-input-list-gap);margin:var(--hub-file-input-list-margin-top) 0 0;padding:0;list-style:none}.hub-file-input__list--grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--hub-file-input-grid-min-width),1fr));gap:var(--hub-file-input-grid-gap)}.hub-file-input__item{display:flex;align-items:center;gap:var(--hub-file-input-item-gap);padding:var(--hub-file-input-item-padding-y) var(--hub-file-input-item-padding-x);color:var(--hub-file-input-item-color);background-color:var(--hub-file-input-item-bg);border:var(--hub-file-input-border-width) solid var(--hub-file-input-item-border-color);border-radius:var(--hub-file-input-item-border-radius);min-width:0}.hub-file-input__item[data-status=error]{border-color:var(--hub-file-input-error-color)}.hub-file-input__item[data-status=done]{border-color:var(--hub-file-input-done-color)}.hub-file-input__thumb{display:flex;flex:none;align-items:center;justify-content:center;width:var(--hub-file-input-thumb-size);height:var(--hub-file-input-thumb-size);overflow:hidden;background-color:var(--hub-file-input-thumb-bg);border-radius:var(--hub-file-input-thumb-radius)}.hub-file-input__image{width:100%;height:100%;object-fit:cover}.hub-file-input__file-icon{display:block;width:var(--hub-file-input-file-icon-size);height:var(--hub-file-input-file-icon-size);background-color:var(--hub-file-input-file-icon-color);-webkit-mask:var(--hub-file-input-file-icon) center/contain no-repeat;mask:var(--hub-file-input-file-icon) center/contain no-repeat}.hub-file-input__meta{display:flex;flex:1 1 auto;flex-direction:column;gap:.125rem;min-width:0}.hub-file-input__name{overflow:hidden;font-size:var(--hub-file-input-name-font-size);text-overflow:ellipsis;white-space:nowrap}.hub-file-input__size{color:var(--hub-file-input-size-color);font-size:var(--hub-file-input-size-font-size)}.hub-file-input__error{color:var(--hub-file-input-error-color);font-size:var(--hub-file-input-error-font-size)}.hub-file-input__progress{display:block;width:100%;height:var(--hub-file-input-progress-height);overflow:hidden;background-color:var(--hub-file-input-progress-track-bg);border-radius:var(--hub-file-input-progress-radius)}.hub-file-input__progress-bar{display:block;width:calc(var(--hub-file-input-progress-value, 0) * 1%);height:100%;background-color:var(--hub-file-input-progress-bar-bg);border-radius:inherit;transition:width .15s linear}.hub-file-input__progress--indeterminate .hub-file-input__progress-bar{width:33%;animation:hub-file-input-indeterminate var(--hub-file-input-progress-indeterminate-duration) ease-in-out infinite}.hub-file-input__actions{display:flex;flex:none;align-items:center;gap:.25rem}.hub-file-input__action{display:block;width:var(--hub-file-input-action-size);height:var(--hub-file-input-action-size);padding:0;background-color:var(--hub-file-input-action-color);border:0;cursor:pointer;transition:var(--hub-file-input-transition)}.hub-file-input__action:hover{background-color:var(--hub-file-input-action-hover-color)}.hub-file-input__action--remove{-webkit-mask:var(--hub-file-input-remove-icon) center/contain no-repeat;mask:var(--hub-file-input-remove-icon) center/contain no-repeat}.hub-file-input__action--remove:hover{background-color:var(--hub-file-input-remove-hover-color)}.hub-file-input__action--cancel{-webkit-mask:var(--hub-file-input-cancel-icon) center/contain no-repeat;mask:var(--hub-file-input-cancel-icon) center/contain no-repeat}.hub-file-input__action--retry{-webkit-mask:var(--hub-file-input-retry-icon) center/contain no-repeat;mask:var(--hub-file-input-retry-icon) center/contain no-repeat}.hub-file-input__clear{align-self:flex-start;margin-top:var(--hub-file-input-list-margin-top);padding:0;color:var(--hub-file-input-clear-color);font-size:var(--hub-file-input-clear-font-size);background:none;border:0;text-decoration:underline;cursor:pointer}.hub-file-input__item[data-status=done] .hub-file-input__size:after{display:inline-block;width:.75em;height:.75em;margin-inline-start:.375em;background-color:var(--hub-file-input-done-color);-webkit-mask:var(--hub-file-input-done-icon) center/contain no-repeat;mask:var(--hub-file-input-done-icon) center/contain no-repeat;content:\"\"}.hub-file-input__live{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.hub-file-input__list--grid .hub-file-input__item{flex-direction:column;align-items:stretch}.hub-file-input__list--grid .hub-file-input__thumb{width:100%;height:var(--hub-file-input-grid-thumb-height)}.hub-file-input__list--grid .hub-file-input__actions{justify-content:flex-end}@keyframes hub-file-input-indeterminate{0%{transform:translate(-100%)}to{transform:translate(300%)}}@media(prefers-reduced-motion:reduce){.hub-file-input__progress--indeterminate .hub-file-input__progress-bar{animation:none;width:100%;opacity:.5}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
7038
|
+
}
|
|
7039
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.1", ngImport: i0, type: HubFileInputComponent, decorators: [{
|
|
7040
|
+
type: Component,
|
|
7041
|
+
args: [{ selector: 'hub-file-input', imports: [NgTemplateOutlet, KeyValuePipe], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
7042
|
+
'[class]': 'classlist()',
|
|
7043
|
+
'[class.hub-file-input-host]': 'true',
|
|
7044
|
+
'(paste)': 'handlePaste($event)'
|
|
7045
|
+
}, template: "<div\n\tclass=\"hub-field hub-file-input\"\n\t[class.hub-field--disabled]=\"disabled()\"\n\t[class.hub-field--invalid]=\"isInvalid\"\n\t[class.hub-field--valid]=\"showsValid\"\n>\n\t@if (label() || required()) {\n\t\t<label [for]=\"id\" class=\"hub-field__label\">\n\t\t\t{{ label() }}\n\t\t\t@if (required()) {\n\t\t\t\t<span class=\"hub-field__required\" aria-hidden=\"true\">*</span>\n\t\t\t}\n\t\t</label>\n\t}\n\n\t<div class=\"hub-field__body\">\n\t\t<!--\n\t\t\tThe dropzone is a <label> bound to the hidden native input: clicking or activating it with\n\t\t\tthe keyboard opens the file dialog through the platform, so no role, tabindex or key\n\t\t\thandler is needed. The \"browse\" affordance is a <span>, not a <button>, because a button\n\t\t\tinside a label would open the dialog twice.\n\t\t-->\n\t\t<label\n\t\t\tclass=\"hub-file-input__dropzone\"\n\t\t\t[for]=\"id\"\n\t\t\t[class.hub-file-input__dropzone--dragover]=\"isDragging()\"\n\t\t\t[class.hub-file-input__dropzone--no-drop]=\"!dragDrop()\"\n\t\t\t[class.hub-file-input__dropzone--invalid]=\"isInvalid\"\n\t\t\t(dragenter)=\"handleDragEnter($event)\"\n\t\t\t(dragover)=\"handleDragOver($event)\"\n\t\t\t(dragleave)=\"handleDragLeave($event)\"\n\t\t\t(drop)=\"handleDrop($event)\"\n\t\t>\n\t\t\t<input\n\t\t\t\t#nativeInput\n\t\t\t\ttype=\"file\"\n\t\t\t\tclass=\"hub-file-input__native\"\n\t\t\t\t[id]=\"id\"\n\t\t\t\t[accept]=\"accept()\"\n\t\t\t\t[multiple]=\"multiple()\"\n\t\t\t\t[disabled]=\"disabled()\"\n\t\t\t\t[attr.capture]=\"capture()\"\n\t\t\t\t[attr.aria-required]=\"required() ? 'true' : null\"\n\t\t\t\t[attr.aria-invalid]=\"isInvalid\"\n\t\t\t\t[attr.aria-describedby]=\"constraintsHint() ? id + '-hint' : null\"\n\t\t\t\t(change)=\"handleNativeChange($event)\"\n\t\t\t\t(blur)=\"handleBlur($event)\"\n\t\t\t/>\n\n\t\t\t<span class=\"hub-file-input__icon\" aria-hidden=\"true\"></span>\n\n\t\t\t<span class=\"hub-file-input__prompt\">\n\t\t\t\t@if (dragDrop()) {\n\t\t\t\t\t<span class=\"hub-file-input__drop-text\">{{ labels().dropHere }}</span>\n\t\t\t\t}\n\t\t\t\t<span class=\"hub-file-input__browse\">{{ browseLabel() }}</span>\n\t\t\t</span>\n\n\t\t\t@if (constraintsHint()) {\n\t\t\t\t<span class=\"hub-file-input__hint\" [id]=\"id + '-hint'\">{{ constraintsHint() }}</span>\n\t\t\t}\n\t\t</label>\n\n\t\t@if (preview() !== 'none' && _items().length > 0) {\n\t\t\t<ul class=\"hub-file-input__list\" [class.hub-file-input__list--grid]=\"preview() === 'grid'\">\n\t\t\t\t@for (item of _items(); track item.id) {\n\t\t\t\t\t<li class=\"hub-file-input__item\" [attr.data-status]=\"item.status\">\n\t\t\t\t\t\t@if (previewTpt(); as tpt) {\n\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"tpt.template\"\n\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"previewContext(item)\"\n\t\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t<span class=\"hub-file-input__thumb\" aria-hidden=\"true\">\n\t\t\t\t\t\t\t\t@if (item.previewUrl) {\n\t\t\t\t\t\t\t\t\t<img class=\"hub-file-input__image\" [src]=\"item.previewUrl\" alt=\"\" />\n\t\t\t\t\t\t\t\t} @else if (iconTpt(); as icon) {\n\t\t\t\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t\t\t\t[ngTemplateOutlet]=\"icon.template\"\n\t\t\t\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: item }\"\n\t\t\t\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t\t\t} @else {\n\t\t\t\t\t\t\t\t\t<span class=\"hub-file-input__file-icon\"></span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<span class=\"hub-file-input__meta\">\n\t\t\t\t\t\t\t\t<span class=\"hub-file-input__name\" [title]=\"item.file.name\">{{ item.file.name }}</span>\n\t\t\t\t\t\t\t\t<span class=\"hub-file-input__size\">{{ labels().formatSize(item.file.size) }}</span>\n\n\t\t\t\t\t\t\t\t@if (item.status === 'uploading') {\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__progress\"\n\t\t\t\t\t\t\t\t\t\t[class.hub-file-input__progress--indeterminate]=\"item.progress === null\"\n\t\t\t\t\t\t\t\t\t\trole=\"progressbar\"\n\t\t\t\t\t\t\t\t\t\taria-valuemin=\"0\"\n\t\t\t\t\t\t\t\t\t\taria-valuemax=\"100\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-valuenow]=\"item.progress\"\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__progress-bar\"\n\t\t\t\t\t\t\t\t\t\t\t[style.--hub-file-input-progress-value]=\"item.progress ?? 0\"\n\t\t\t\t\t\t\t\t\t\t></span>\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t@if (item.status === 'error') {\n\t\t\t\t\t\t\t\t\t<span class=\"hub-file-input__error\">{{ errorText(item) }}</span>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</span>\n\n\t\t\t\t\t\t\t<span class=\"hub-file-input__actions\">\n\t\t\t\t\t\t\t\t@if (item.status === 'uploading') {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__action hub-file-input__action--cancel\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"labels().cancel\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"cancel(item.id)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t@if (item.status === 'error') {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__action hub-file-input__action--retry\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"labels().retry\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"retry(item.id)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t@if (!disabled() && item.status !== 'uploading') {\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclass=\"hub-file-input__action hub-file-input__action--remove\"\n\t\t\t\t\t\t\t\t\t\t[attr.aria-label]=\"labels().remove\"\n\t\t\t\t\t\t\t\t\t\t(click)=\"remove(item.id)\"\n\t\t\t\t\t\t\t\t\t></button>\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t}\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\n\t\t\t@if (_items().length > 1 && !disabled()) {\n\t\t\t\t<button type=\"button\" class=\"hub-file-input__clear\" (click)=\"clear()\">{{ labels().clear }}</button>\n\t\t\t}\n\t\t}\n\t</div>\n\n\t@if (formText() || formTextTmp()) {\n\t\t<div class=\"hub-field__form-text\" [class.hub-field__form-text--disabled]=\"disabled()\">\n\t\t\t@if (formTextTmp()) {\n\t\t\t\t<ng-container [ngTemplateOutlet]=\"formTextTmp()!\"></ng-container>\n\t\t\t} @else {\n\t\t\t\t{{ formText() }}\n\t\t\t}\n\t\t</div>\n\t}\n\n\t@if (isInvalid) {\n\t\t<div class=\"hub-field__feedback\" role=\"alert\">\n\t\t\t<ul>\n\t\t\t\t@for (error of errors | keyvalue; track error.key) {\n\t\t\t\t\t<li>\n\t\t\t\t\t\t<ng-container\n\t\t\t\t\t\t\t[ngTemplateOutlet]=\"_errorTemplates[error.key] ?? defaultErrorTpt\"\n\t\t\t\t\t\t\t[ngTemplateOutletContext]=\"{ $implicit: error.value, value: error.value }\"\n\t\t\t\t\t\t></ng-container>\n\t\t\t\t\t\t<ng-template #defaultErrorTpt>\n\t\t\t\t\t\t\t<span class=\"hub-field__feedback-text\" [innerHTML]=\"getInvalidFeedbackTemplate(error.key, error.value)\"></span>\n\t\t\t\t\t\t</ng-template>\n\t\t\t\t\t</li>\n\t\t\t\t}\n\t\t\t</ul>\n\t\t</div>\n\t}\n\n\t@if (showsValid && validFeedback()) {\n\t\t<div class=\"hub-field__feedback hub-field__feedback--valid\" role=\"status\">\n\t\t\t<span class=\"hub-field__feedback-text\">{{ validFeedback() }}</span>\n\t\t</div>\n\t}\n\n\t<span class=\"hub-file-input__live\" aria-live=\"polite\">{{ announcement() }}</span>\n</div>\n", styles: [".hub-file-input__dropzone{position:relative;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--hub-file-input-gap);min-height:var(--hub-file-input-min-height);padding:var(--hub-file-input-padding-y) var(--hub-file-input-padding-x);color:var(--hub-file-input-color);background-color:var(--hub-file-input-bg);border:var(--hub-file-input-border-width) var(--hub-file-input-border-style) var(--hub-file-input-border-color);border-radius:var(--hub-file-input-border-radius);text-align:center;cursor:pointer;transition:var(--hub-file-input-transition)}.hub-file-input__dropzone:hover{background-color:var(--hub-file-input-hover-bg);border-color:var(--hub-file-input-hover-border-color)}.hub-file-input__dropzone:has(.hub-file-input__native:focus-visible){border-color:var(--hub-file-input-focus-border-color);box-shadow:var(--hub-file-input-focus-box-shadow);outline:none}.hub-file-input__dropzone--dragover,.hub-file-input__dropzone--dragover:hover{background-color:var(--hub-file-input-dragover-bg);border-color:var(--hub-file-input-dragover-border-color)}.hub-file-input__dropzone--invalid{border-color:var(--hub-form-invalid-border-color)}.hub-file-input__dropzone:has(.hub-file-input__native:disabled){cursor:not-allowed}.hub-file-input__dropzone:has(.hub-file-input__native:disabled):hover{background-color:var(--hub-file-input-bg);border-color:var(--hub-file-input-border-color)}.hub-file-input__native{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.hub-file-input__icon{display:block;width:var(--hub-file-input-icon-size);height:var(--hub-file-input-icon-size);background-color:var(--hub-file-input-icon-color);-webkit-mask:var(--hub-file-input-icon) center/contain no-repeat;mask:var(--hub-file-input-icon) center/contain no-repeat}.hub-file-input__prompt{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:center;gap:.25em;color:var(--hub-file-input-prompt-color);font-size:var(--hub-file-input-prompt-font-size)}.hub-file-input__browse{color:var(--hub-file-input-browse-color);font-weight:var(--hub-file-input-browse-font-weight);text-decoration:underline}.hub-file-input__hint{color:var(--hub-file-input-hint-color);font-size:var(--hub-file-input-hint-font-size)}.hub-file-input__list{display:flex;flex-direction:column;gap:var(--hub-file-input-list-gap);margin:var(--hub-file-input-list-margin-top) 0 0;padding:0;list-style:none}.hub-file-input__list--grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--hub-file-input-grid-min-width),1fr));gap:var(--hub-file-input-grid-gap)}.hub-file-input__item{display:flex;align-items:center;gap:var(--hub-file-input-item-gap);padding:var(--hub-file-input-item-padding-y) var(--hub-file-input-item-padding-x);color:var(--hub-file-input-item-color);background-color:var(--hub-file-input-item-bg);border:var(--hub-file-input-border-width) solid var(--hub-file-input-item-border-color);border-radius:var(--hub-file-input-item-border-radius);min-width:0}.hub-file-input__item[data-status=error]{border-color:var(--hub-file-input-error-color)}.hub-file-input__item[data-status=done]{border-color:var(--hub-file-input-done-color)}.hub-file-input__thumb{display:flex;flex:none;align-items:center;justify-content:center;width:var(--hub-file-input-thumb-size);height:var(--hub-file-input-thumb-size);overflow:hidden;background-color:var(--hub-file-input-thumb-bg);border-radius:var(--hub-file-input-thumb-radius)}.hub-file-input__image{width:100%;height:100%;object-fit:cover}.hub-file-input__file-icon{display:block;width:var(--hub-file-input-file-icon-size);height:var(--hub-file-input-file-icon-size);background-color:var(--hub-file-input-file-icon-color);-webkit-mask:var(--hub-file-input-file-icon) center/contain no-repeat;mask:var(--hub-file-input-file-icon) center/contain no-repeat}.hub-file-input__meta{display:flex;flex:1 1 auto;flex-direction:column;gap:.125rem;min-width:0}.hub-file-input__name{overflow:hidden;font-size:var(--hub-file-input-name-font-size);text-overflow:ellipsis;white-space:nowrap}.hub-file-input__size{color:var(--hub-file-input-size-color);font-size:var(--hub-file-input-size-font-size)}.hub-file-input__error{color:var(--hub-file-input-error-color);font-size:var(--hub-file-input-error-font-size)}.hub-file-input__progress{display:block;width:100%;height:var(--hub-file-input-progress-height);overflow:hidden;background-color:var(--hub-file-input-progress-track-bg);border-radius:var(--hub-file-input-progress-radius)}.hub-file-input__progress-bar{display:block;width:calc(var(--hub-file-input-progress-value, 0) * 1%);height:100%;background-color:var(--hub-file-input-progress-bar-bg);border-radius:inherit;transition:width .15s linear}.hub-file-input__progress--indeterminate .hub-file-input__progress-bar{width:33%;animation:hub-file-input-indeterminate var(--hub-file-input-progress-indeterminate-duration) ease-in-out infinite}.hub-file-input__actions{display:flex;flex:none;align-items:center;gap:.25rem}.hub-file-input__action{display:block;width:var(--hub-file-input-action-size);height:var(--hub-file-input-action-size);padding:0;background-color:var(--hub-file-input-action-color);border:0;cursor:pointer;transition:var(--hub-file-input-transition)}.hub-file-input__action:hover{background-color:var(--hub-file-input-action-hover-color)}.hub-file-input__action--remove{-webkit-mask:var(--hub-file-input-remove-icon) center/contain no-repeat;mask:var(--hub-file-input-remove-icon) center/contain no-repeat}.hub-file-input__action--remove:hover{background-color:var(--hub-file-input-remove-hover-color)}.hub-file-input__action--cancel{-webkit-mask:var(--hub-file-input-cancel-icon) center/contain no-repeat;mask:var(--hub-file-input-cancel-icon) center/contain no-repeat}.hub-file-input__action--retry{-webkit-mask:var(--hub-file-input-retry-icon) center/contain no-repeat;mask:var(--hub-file-input-retry-icon) center/contain no-repeat}.hub-file-input__clear{align-self:flex-start;margin-top:var(--hub-file-input-list-margin-top);padding:0;color:var(--hub-file-input-clear-color);font-size:var(--hub-file-input-clear-font-size);background:none;border:0;text-decoration:underline;cursor:pointer}.hub-file-input__item[data-status=done] .hub-file-input__size:after{display:inline-block;width:.75em;height:.75em;margin-inline-start:.375em;background-color:var(--hub-file-input-done-color);-webkit-mask:var(--hub-file-input-done-icon) center/contain no-repeat;mask:var(--hub-file-input-done-icon) center/contain no-repeat;content:\"\"}.hub-file-input__live{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}.hub-file-input__list--grid .hub-file-input__item{flex-direction:column;align-items:stretch}.hub-file-input__list--grid .hub-file-input__thumb{width:100%;height:var(--hub-file-input-grid-thumb-height)}.hub-file-input__list--grid .hub-file-input__actions{justify-content:flex-end}@keyframes hub-file-input-indeterminate{0%{transform:translate(-100%)}to{transform:translate(300%)}}@media(prefers-reduced-motion:reduce){.hub-file-input__progress--indeterminate .hub-file-input__progress-bar{animation:none;width:100%;opacity:.5}}\n"] }]
|
|
7046
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], accept: [{ type: i0.Input, args: [{ isSignal: true, alias: "accept", required: false }] }], maxSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSize", required: false }] }], minSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "minSize", required: false }] }], maxTotalSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTotalSize", required: false }] }], maxFiles: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxFiles", required: false }] }], dragDrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "dragDrop", required: false }] }], paste: [{ type: i0.Input, args: [{ isSignal: true, alias: "paste", required: false }] }], preview: [{ type: i0.Input, args: [{ isSignal: true, alias: "preview", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], capture: [{ type: i0.Input, args: [{ isSignal: true, alias: "capture", required: false }] }], autoUpload: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoUpload", required: false }] }], buttonLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "buttonLabel", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], formText: [{ type: i0.Input, args: [{ isSignal: true, alias: "formText", required: false }] }], formTextType: [{ type: i0.Input, args: [{ isSignal: true, alias: "formTextType", required: false }] }], classlist: [{ type: i0.Input, args: [{ isSignal: true, alias: "classlist", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], rejected: [{ type: i0.Output, args: ["rejected"] }], fileRemoved: [{ type: i0.Output, args: ["fileRemoved"] }], uploadStateChange: [{ type: i0.Output, args: ["uploadStateChange"] }], nativeInput: [{ type: i0.ViewChild, args: ['nativeInput', { isSignal: true }] }], iconTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HubFileIconDirective), { isSignal: true }] }], previewTpt: [{ type: i0.ContentChild, args: [i0.forwardRef(() => HubFilePreviewDirective), { isSignal: true }] }] } });
|
|
7047
|
+
|
|
6096
7048
|
/**
|
|
6097
7049
|
* Inverts a HEX color, or returns the best contrasting black/white when `bw` is enabled.
|
|
6098
7050
|
*/
|
|
@@ -6293,6 +7245,166 @@ function hubAreEqual(primaryControlName, secondaryControlName) {
|
|
|
6293
7245
|
};
|
|
6294
7246
|
}
|
|
6295
7247
|
|
|
7248
|
+
/**
|
|
7249
|
+
* Validates that every file held by the control matches an `accept` specification.
|
|
7250
|
+
*
|
|
7251
|
+
* Complements the `accept` input of `<hub-file-input>`, which *filters* offending files before they
|
|
7252
|
+
* reach the value. This validator instead makes the control **invalid**, which is what you want when
|
|
7253
|
+
* files can also arrive from outside the component (patched value, restored draft, another control).
|
|
7254
|
+
*
|
|
7255
|
+
* Sets the `fileAccept` error with `{ accept, files }`, where `files` lists the offending names.
|
|
7256
|
+
* An empty control is valid — pair it with `Validators.required` to demand a file.
|
|
7257
|
+
*
|
|
7258
|
+
* @param accept - The accept specification, e.g. `'image/*,.pdf'`.
|
|
7259
|
+
* @returns A validator function for a file-valued control.
|
|
7260
|
+
*
|
|
7261
|
+
* @example
|
|
7262
|
+
* ```ts
|
|
7263
|
+
* new FormControl<File[]>([], [hubAcceptedFiles('image/*,.pdf')]);
|
|
7264
|
+
* ```
|
|
7265
|
+
*/
|
|
7266
|
+
function hubAcceptedFiles(accept) {
|
|
7267
|
+
return (control) => {
|
|
7268
|
+
const offending = toFileArray(control.value).filter((file) => !matchesAccept(file, accept));
|
|
7269
|
+
if (offending.length === 0) {
|
|
7270
|
+
return null;
|
|
7271
|
+
}
|
|
7272
|
+
return { fileAccept: { accept, files: offending.map((file) => file.name) } };
|
|
7273
|
+
};
|
|
7274
|
+
}
|
|
7275
|
+
|
|
7276
|
+
/**
|
|
7277
|
+
* Validates that no file held by the control exceeds a maximum size.
|
|
7278
|
+
*
|
|
7279
|
+
* Sets the `fileMaxSize` error with `{ max, actual, files }`, where `actual` is the size of the
|
|
7280
|
+
* largest offending file and `files` lists the offending names. An empty control is valid.
|
|
7281
|
+
*
|
|
7282
|
+
* @param maxBytes - The maximum size per file, in bytes.
|
|
7283
|
+
* @returns A validator function for a file-valued control.
|
|
7284
|
+
*
|
|
7285
|
+
* @example
|
|
7286
|
+
* ```ts
|
|
7287
|
+
* new FormControl<File | null>(null, [hubMaxFileSize(5 * 1024 * 1024)]);
|
|
7288
|
+
* ```
|
|
7289
|
+
*/
|
|
7290
|
+
function hubMaxFileSize(maxBytes) {
|
|
7291
|
+
return (control) => {
|
|
7292
|
+
const offending = toFileArray(control.value).filter((file) => file.size > maxBytes);
|
|
7293
|
+
if (offending.length === 0) {
|
|
7294
|
+
return null;
|
|
7295
|
+
}
|
|
7296
|
+
return {
|
|
7297
|
+
fileMaxSize: {
|
|
7298
|
+
max: maxBytes,
|
|
7299
|
+
actual: Math.max(...offending.map((file) => file.size)),
|
|
7300
|
+
files: offending.map((file) => file.name)
|
|
7301
|
+
}
|
|
7302
|
+
};
|
|
7303
|
+
};
|
|
7304
|
+
}
|
|
7305
|
+
|
|
7306
|
+
/**
|
|
7307
|
+
* Validates that no file held by the control falls below a minimum size.
|
|
7308
|
+
*
|
|
7309
|
+
* Useful to reject empty or truncated uploads (a 0-byte file is a common symptom of a failed export).
|
|
7310
|
+
* Sets the `fileMinSize` error with `{ min, actual, files }`, where `actual` is the size of the
|
|
7311
|
+
* smallest offending file. An empty control is valid.
|
|
7312
|
+
*
|
|
7313
|
+
* @param minBytes - The minimum size per file, in bytes.
|
|
7314
|
+
* @returns A validator function for a file-valued control.
|
|
7315
|
+
*
|
|
7316
|
+
* @example
|
|
7317
|
+
* ```ts
|
|
7318
|
+
* new FormControl<File | null>(null, [hubMinFileSize(1)]);
|
|
7319
|
+
* ```
|
|
7320
|
+
*/
|
|
7321
|
+
function hubMinFileSize(minBytes) {
|
|
7322
|
+
return (control) => {
|
|
7323
|
+
const offending = toFileArray(control.value).filter((file) => file.size < minBytes);
|
|
7324
|
+
if (offending.length === 0) {
|
|
7325
|
+
return null;
|
|
7326
|
+
}
|
|
7327
|
+
return {
|
|
7328
|
+
fileMinSize: {
|
|
7329
|
+
min: minBytes,
|
|
7330
|
+
actual: Math.min(...offending.map((file) => file.size)),
|
|
7331
|
+
files: offending.map((file) => file.name)
|
|
7332
|
+
}
|
|
7333
|
+
};
|
|
7334
|
+
};
|
|
7335
|
+
}
|
|
7336
|
+
|
|
7337
|
+
/**
|
|
7338
|
+
* Validates that the combined size of every file held by the control stays within a budget.
|
|
7339
|
+
*
|
|
7340
|
+
* This is the constraint a request-body limit actually enforces, and it is not implied by a
|
|
7341
|
+
* per-file maximum: ten files of 2 MB pass `hubMaxFileSize(5 MB)` and still blow a 10 MB request.
|
|
7342
|
+
*
|
|
7343
|
+
* Sets the `fileMaxTotalSize` error with `{ max, actual }`. An empty control is valid.
|
|
7344
|
+
*
|
|
7345
|
+
* @param maxBytes - The maximum combined size, in bytes.
|
|
7346
|
+
* @returns A validator function for a file-valued control.
|
|
7347
|
+
*
|
|
7348
|
+
* @example
|
|
7349
|
+
* ```ts
|
|
7350
|
+
* new FormControl<File[]>([], [hubMaxTotalSize(20 * 1024 * 1024)]);
|
|
7351
|
+
* ```
|
|
7352
|
+
*/
|
|
7353
|
+
function hubMaxTotalSize(maxBytes) {
|
|
7354
|
+
return (control) => {
|
|
7355
|
+
const files = toFileArray(control.value);
|
|
7356
|
+
if (files.length === 0) {
|
|
7357
|
+
return null;
|
|
7358
|
+
}
|
|
7359
|
+
const total = files.reduce((sum, file) => sum + file.size, 0);
|
|
7360
|
+
return total > maxBytes ? { fileMaxTotalSize: { max: maxBytes, actual: total } } : null;
|
|
7361
|
+
};
|
|
7362
|
+
}
|
|
7363
|
+
|
|
7364
|
+
/**
|
|
7365
|
+
* Validates that the control holds at most `max` files.
|
|
7366
|
+
*
|
|
7367
|
+
* Sets the `fileMaxFiles` error with `{ max, actual }`. An empty control is valid.
|
|
7368
|
+
*
|
|
7369
|
+
* @param max - The maximum number of files.
|
|
7370
|
+
* @returns A validator function for a file-valued control.
|
|
7371
|
+
*
|
|
7372
|
+
* @example
|
|
7373
|
+
* ```ts
|
|
7374
|
+
* new FormControl<File[]>([], [hubMaxFiles(3)]);
|
|
7375
|
+
* ```
|
|
7376
|
+
*/
|
|
7377
|
+
function hubMaxFiles(max) {
|
|
7378
|
+
return (control) => {
|
|
7379
|
+
const count = toFileArray(control.value).length;
|
|
7380
|
+
return count > max ? { fileMaxFiles: { max, actual: count } } : null;
|
|
7381
|
+
};
|
|
7382
|
+
}
|
|
7383
|
+
|
|
7384
|
+
/**
|
|
7385
|
+
* Validates that the control holds at least `min` files.
|
|
7386
|
+
*
|
|
7387
|
+
* Unlike the other file validators, this one **does** reject an empty control: holding zero files
|
|
7388
|
+
* is precisely what a minimum forbids. It therefore subsumes `Validators.required`, and adding both
|
|
7389
|
+
* only produces two errors for the same cause.
|
|
7390
|
+
*
|
|
7391
|
+
* Sets the `fileMinFiles` error with `{ min, actual }`.
|
|
7392
|
+
*
|
|
7393
|
+
* @param min - The minimum number of files.
|
|
7394
|
+
* @returns A validator function for a file-valued control.
|
|
7395
|
+
*
|
|
7396
|
+
* @example
|
|
7397
|
+
* ```ts
|
|
7398
|
+
* new FormControl<File[]>([], [hubMinFiles(2)]);
|
|
7399
|
+
* ```
|
|
7400
|
+
*/
|
|
7401
|
+
function hubMinFiles(min) {
|
|
7402
|
+
return (control) => {
|
|
7403
|
+
const count = toFileArray(control.value).length;
|
|
7404
|
+
return count < min ? { fileMinFiles: { min, actual: count } } : null;
|
|
7405
|
+
};
|
|
7406
|
+
}
|
|
7407
|
+
|
|
6296
7408
|
/**
|
|
6297
7409
|
* Ready-made {@link HubFormControlAdapter} backed by `HubInputComponent` /
|
|
6298
7410
|
* `HubSelectComponent`.
|
|
@@ -6361,5 +7473,5 @@ const hubFormControlAdapter = {
|
|
|
6361
7473
|
* Generated bundle index. Do not edit.
|
|
6362
7474
|
*/
|
|
6363
7475
|
|
|
6364
|
-
export { FormTextTypes, HUB_FORMS_CONFIG, HubAutoresizeDirective, HubDatepickerComponent, HubFieldControl, HubFieldsetComponent, HubFormComponent, HubFormControl, HubFormTextDirective, HubGroupControl, HubInputComponent, HubInputFormats, HubInputPrefixDirective, HubInputSuffixDirective, HubInvertColorPipe, HubJoinButLastPipe, HubLabelTypes, HubLegendComponent, HubLegendDirective, HubMapPipe, HubOtpInputComponent, HubSafeUrlPipe, HubSegmentedComponent, HubSelectComponent, HubSelectFormats, HubSliderComponent, HubSnakeUpperPipe, HubTextareaComponent, HubUcfirstPipe, HubValidationErrorDirective, NgClearButtonTemplateDirective, NgFooterTemplateDirective, NgHeaderTemplateDirective, NgLabelTemplateDirective, NgLoadingSpinnerTemplateDirective, NgLoadingTextTemplateDirective, NgMultiLabelTemplateDirective, NgNotFoundTemplateDirective, NgOptgroupTemplateDirective, NgOptionComponent, NgOptionTemplateDirective, NgSelectConfig, NgTagTemplateDirective, NgTypeToSearchTemplateDirective, applyMask, areEqual, camelToSnakeUpper, controlHasMinOrMaxValidator, defaultHubDatepickerConfig, defaultHubDatepickerLabels, defaultHubFormsConfig, defaultInvalidFeedback, get, getActiveElement, getMinOrMaxValueFromValidator, hubAreEqual, hubFormControlAdapter, isDefined$1 as isDefined, isMaskActive, isString, joinButLast, provideHubForms, runInZone, uuid };
|
|
7476
|
+
export { FormTextTypes, HUB_FILE_UPLOADER, HUB_FORMS_CONFIG, HubAutoresizeDirective, HubDatepickerComponent, HubFieldControl, HubFieldsetComponent, HubFileIconDirective, HubFileInputComponent, HubFilePreviewDirective, HubFormComponent, HubFormControl, HubFormTextDirective, HubGroupControl, HubInputComponent, HubInputFormats, HubInputPrefixDirective, HubInputSuffixDirective, HubInvertColorPipe, HubJoinButLastPipe, HubLabelTypes, HubLegendComponent, HubLegendDirective, HubMapPipe, HubOtpInputComponent, HubSafeUrlPipe, HubSegmentedComponent, HubSelectComponent, HubSelectFormats, HubSliderComponent, HubSnakeUpperPipe, HubTextareaComponent, HubUcfirstPipe, HubValidationErrorDirective, NgClearButtonTemplateDirective, NgFooterTemplateDirective, NgHeaderTemplateDirective, NgLabelTemplateDirective, NgLoadingSpinnerTemplateDirective, NgLoadingTextTemplateDirective, NgMultiLabelTemplateDirective, NgNotFoundTemplateDirective, NgOptgroupTemplateDirective, NgOptionComponent, NgOptionTemplateDirective, NgSelectConfig, NgTagTemplateDirective, NgTypeToSearchTemplateDirective, applyMask, areEqual, camelToSnakeUpper, controlHasMinOrMaxValidator, defaultHubDatepickerConfig, defaultHubDatepickerLabels, defaultHubFileInputLabels, defaultHubFormsConfig, defaultInvalidFeedback, fileKey, formatFileSize, get, getActiveElement, getMinOrMaxValueFromValidator, hubAcceptedFiles, hubAreEqual, hubFormControlAdapter, hubMaxFileSize, hubMaxFiles, hubMaxTotalSize, hubMinFileSize, hubMinFiles, isDefined$1 as isDefined, isMaskActive, isString, joinButLast, matchesAccept, provideHubFileUploader, provideHubForms, runInZone, toFileArray, uuid };
|
|
6365
7477
|
//# sourceMappingURL=ng-hub-ui-forms.mjs.map
|