@truenas/ui-components 0.1.75 → 0.1.76

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.
@@ -15,6 +15,7 @@ import { DomSanitizer } from '@angular/platform-browser';
15
15
  import { HttpClient } from '@angular/common/http';
16
16
  import { firstValueFrom, BehaviorSubject, merge, Subject, take } from 'rxjs';
17
17
  import { RouterLink } from '@angular/router';
18
+ import { filesize } from 'filesize';
18
19
  import { CdkMenu, CdkMenuItem, CdkMenuTrigger } from '@angular/cdk/menu';
19
20
  import { trigger, state, transition, style, animate } from '@angular/animations';
20
21
  import { SPACE, ENTER, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW } from '@angular/cdk/keycodes';
@@ -2478,8 +2479,109 @@ var InputType;
2478
2479
  InputType["Number"] = "number";
2479
2480
  InputType["Password"] = "password";
2480
2481
  InputType["PlainText"] = "text";
2482
+ /**
2483
+ * Data-size field: the form model holds a raw byte count (a number), while the
2484
+ * field displays/accepts a human-readable string (e.g. `2 GiB`, `500M`, `2 TB`).
2485
+ * See `tn-input`'s `sizeStandard` / `sizeDefaultUnit` inputs to tune formatting
2486
+ * and bare-number parsing.
2487
+ */
2488
+ InputType["Size"] = "size";
2481
2489
  })(InputType || (InputType = {}));
2482
2490
 
2491
+ // Ordered binary/decimal prefixes; the array index doubles as the power applied
2492
+ // to the base (B = base^0, K = base^1, M = base^2, ...).
2493
+ const UNIT_PREFIXES = ['B', 'K', 'M', 'G', 'T', 'P', 'E'];
2494
+ /**
2495
+ * Formats a raw byte count into a human-readable string (e.g. `2 GiB`).
2496
+ *
2497
+ * Returns an empty string for `null`/`undefined`/empty/non-numeric input so the
2498
+ * field renders blank rather than `NaN`.
2499
+ *
2500
+ * @param bytes The byte count to format.
2501
+ * @param standard Unit standard (defaults to IEC base-2).
2502
+ * @param round Decimal places to round to (defaults to 2).
2503
+ */
2504
+ function formatSize(bytes, standard = 'iec', round = 2) {
2505
+ if (bytes === null || bytes === undefined || bytes === '') {
2506
+ return '';
2507
+ }
2508
+ const num = Number(bytes);
2509
+ if (Number.isNaN(num)) {
2510
+ return '';
2511
+ }
2512
+ // `standard: 'iec'` implies base 2 and KiB-style symbols; `'si'` implies base
2513
+ // 10 and kB-style symbols. filesize derives the base from the standard.
2514
+ return filesize(num, { standard, round });
2515
+ }
2516
+ /**
2517
+ * Parses a human-readable size string into a raw byte count.
2518
+ *
2519
+ * Lenient by design: accepts IEC (`KiB`), short (`KB`), and human (`K`) unit
2520
+ * spellings, optional whitespace, and a bare number (which is interpreted using
2521
+ * `defaultUnit`). The chosen `standard` decides the multiplier — under `iec`,
2522
+ * `MB`/`M`/`MiB` are all treated as 1024-based; under `si` they are 1000-based.
2523
+ *
2524
+ * Returns `null` for empty, malformed, or unrecognized-unit input so callers can
2525
+ * map invalid entries to a null form-model value (never `0`).
2526
+ *
2527
+ * @param raw The human-readable string to parse.
2528
+ * @param defaultUnit Unit assumed when the input carries no unit (defaults to `MiB`).
2529
+ * @param standard Unit standard (defaults to IEC base-2).
2530
+ */
2531
+ function parseSize(raw, defaultUnit = 'MiB', standard = 'iec') {
2532
+ if (raw === null || raw === undefined) {
2533
+ return null;
2534
+ }
2535
+ const str = String(raw).trim();
2536
+ if (str === '') {
2537
+ return null;
2538
+ }
2539
+ // Leading non-negative number (sizes are never negative), optional whitespace,
2540
+ // then an optional unit. Anything trailing the unit makes the whole match fail.
2541
+ const match = str.match(/^(\d+(?:\.\d+)?)\s*([a-zA-Z]*)$/);
2542
+ if (!match) {
2543
+ return null;
2544
+ }
2545
+ const num = parseFloat(match[1]);
2546
+ if (Number.isNaN(num)) {
2547
+ return null;
2548
+ }
2549
+ const exponent = unitExponent(match[2] || defaultUnit);
2550
+ if (exponent === null) {
2551
+ return null;
2552
+ }
2553
+ const base = standard === 'si' ? 1000 : 1024;
2554
+ // Round to whole bytes: a byte count is an integer, and rounding absorbs the
2555
+ // floating-point drift of e.g. 1.1 * 1024.
2556
+ return Math.round(num * base ** exponent);
2557
+ }
2558
+ /**
2559
+ * Resolves a unit spelling to its prefix power (B → 0, K → 1, M → 2, ...).
2560
+ *
2561
+ * Accepts the prefix alone (`K`), the short form (`KB`), or the IEC form (`KiB`),
2562
+ * case-insensitively. Returns `null` for anything unrecognized.
2563
+ */
2564
+ function unitExponent(unit) {
2565
+ const normalized = unit.trim().toUpperCase();
2566
+ if (normalized === '') {
2567
+ return null;
2568
+ }
2569
+ if (normalized === 'B' || normalized === 'BYTE' || normalized === 'BYTES') {
2570
+ return 0;
2571
+ }
2572
+ const index = UNIT_PREFIXES.indexOf(normalized.charAt(0));
2573
+ // index 0 is 'B', already handled above; a leading 'B' here (e.g. "BB") is invalid.
2574
+ if (index <= 0) {
2575
+ return null;
2576
+ }
2577
+ // Allow the bare prefix ("K"), the short unit ("KB"), or the IEC unit ("KIB").
2578
+ const suffix = normalized.slice(1);
2579
+ if (suffix === '' || suffix === 'B' || suffix === 'IB') {
2580
+ return index;
2581
+ }
2582
+ return null;
2583
+ }
2584
+
2483
2585
  // Module-level counter for deterministic, unique instance ids (matches the
2484
2586
  // tn-autocomplete convention). Deterministic ids are SSR/hydration-safe and
2485
2587
  // stable across snapshots, unlike a Math.random() seed.
@@ -2511,6 +2613,24 @@ class TnInputComponent {
2511
2613
  * (e.g. `Validators.min`/`max`), which work because the control emits real numbers.
2512
2614
  */
2513
2615
  allowDecimals = input(true, ...(ngDevMode ? [{ debugName: "allowDecimals" }] : []));
2616
+ /**
2617
+ * Unit standard for the `Size` input type — `iec` (base-2, `KiB`/`MiB`) or
2618
+ * `si` (base-10, `kB`/`MB`). Drives both the formatted display and how bare
2619
+ * numbers are scaled when parsing. Ignored unless `inputType` is `Size`.
2620
+ */
2621
+ sizeStandard = input('iec', ...(ngDevMode ? [{ debugName: "sizeStandard" }] : []));
2622
+ /**
2623
+ * Unit assumed when a user types a bare number (no unit) into a `Size` field —
2624
+ * e.g. with the default `MiB`, typing `200` yields 200 MiB. Accepts IEC
2625
+ * (`KiB`), short (`KB`), or human (`K`) spellings. Ignored unless `inputType`
2626
+ * is `Size`.
2627
+ */
2628
+ sizeDefaultUnit = input('MiB', ...(ngDevMode ? [{ debugName: "sizeDefaultUnit" }] : []));
2629
+ /**
2630
+ * Decimal places used when formatting a `Size` field's value for display
2631
+ * (e.g. `1.5 GiB`). Ignored unless `inputType` is `Size`.
2632
+ */
2633
+ sizeRound = input(2, ...(ngDevMode ? [{ debugName: "sizeRound" }] : []));
2514
2634
  // Icon inputs
2515
2635
  prefixIcon = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIcon" }] : []));
2516
2636
  prefixIconLibrary = input(undefined, ...(ngDevMode ? [{ debugName: "prefixIconLibrary" }] : []));
@@ -2522,19 +2642,21 @@ class TnInputComponent {
2522
2642
  hasSuffixIcon = computed(() => !!this.suffixIcon(), ...(ngDevMode ? [{ debugName: "hasSuffixIcon" }] : []));
2523
2643
  // Numeric mode state
2524
2644
  isNumeric = computed(() => this.inputType() === InputType.Number, ...(ngDevMode ? [{ debugName: "isNumeric" }] : []));
2645
+ /** True when the field is in data-size mode (human-readable string ⇄ byte-count model). */
2646
+ isSize = computed(() => this.inputType() === InputType.Size, ...(ngDevMode ? [{ debugName: "isSize" }] : []));
2525
2647
  /** True when the field only accepts whole numbers (i.e. `allowDecimals` is false). */
2526
2648
  integerOnly = computed(() => !this.allowDecimals(), ...(ngDevMode ? [{ debugName: "integerOnly" }] : []));
2527
- /** The `type` attribute actually rendered. Number mode uses a text input + inputmode to avoid the native type="number" footguns. */
2528
- resolvedType = computed(() => (this.isNumeric() ? InputType.PlainText : this.inputType()), ...(ngDevMode ? [{ debugName: "resolvedType" }] : []));
2529
- /** `inputmode` hint: numeric keypad for integers, decimal keypad otherwise. Null (omitted) when not in number mode. */
2649
+ /** The `type` attribute actually rendered. Number and size modes use a text input: number avoids the native type="number" footguns, size must accept unit letters. */
2650
+ resolvedType = computed(() => (this.isNumeric() || this.isSize() ? InputType.PlainText : this.inputType()), ...(ngDevMode ? [{ debugName: "resolvedType" }] : []));
2651
+ /** `inputmode` hint: numeric keypad for integers, decimal keypad otherwise. Null (omitted) outside number mode size fields accept unit letters, so they keep the full keyboard. */
2530
2652
  numericInputMode = computed(() => {
2531
2653
  if (!this.isNumeric()) {
2532
2654
  return null;
2533
2655
  }
2534
2656
  return this.integerOnly() ? 'numeric' : 'decimal';
2535
2657
  }, ...(ngDevMode ? [{ debugName: "numericInputMode" }] : []));
2536
- /** Number mode is always single-line; it wins over `multiline` if both are set. */
2537
- showTextarea = computed(() => this.multiline() && !this.isNumeric(), ...(ngDevMode ? [{ debugName: "showTextarea" }] : []));
2658
+ /** Number and size modes are always single-line; they win over `multiline` if both are set. */
2659
+ showTextarea = computed(() => this.multiline() && !this.isNumeric() && !this.isSize(), ...(ngDevMode ? [{ debugName: "showTextarea" }] : []));
2538
2660
  // Unique per instance: a hard-coded id produced duplicate ids when multiple
2539
2661
  // tn-inputs share a page (invalid HTML, and it breaks any future label `for=`,
2540
2662
  // aria-describedby, or getElementById targeting this input).
@@ -2567,6 +2689,12 @@ class TnInputComponent {
2567
2689
  // Accepts undefined as well as the declared types: Angular may hand writeValue an
2568
2690
  // undefined model at init, and it maps to the empty display like null.
2569
2691
  writeValue(value) {
2692
+ if (this.isSize()) {
2693
+ // The model is a byte count; show it as a human-readable string. Empty/null
2694
+ // stays blank, and a non-numeric model maps to blank (formatSize returns '').
2695
+ this.value = formatSize(value, this.sizeStandard(), this.sizeRound());
2696
+ return;
2697
+ }
2570
2698
  // Display the model verbatim via String(); do NOT sanitize here. Sanitizing a
2571
2699
  // canonical number string would corrupt fractions in integer mode (3.5 -> "35"),
2572
2700
  // silently diverging the display from the form model.
@@ -2587,6 +2715,13 @@ class TnInputComponent {
2587
2715
  // Component methods (template-bound; protected — not part of the public API)
2588
2716
  onValueChange(event) {
2589
2717
  const target = event.target;
2718
+ if (this.isSize()) {
2719
+ // Keep the raw text in the field as the user types (unit letters and all);
2720
+ // emit the parsed byte count, mapping invalid/partial input to null (never 0).
2721
+ this.value = target.value;
2722
+ this.onChange(parseSize(this.value, this.sizeDefaultUnit(), this.sizeStandard()));
2723
+ return;
2724
+ }
2590
2725
  if (this.isNumeric()) {
2591
2726
  const el = target;
2592
2727
  const sanitized = this.sanitizeNumeric(el.value);
@@ -2607,6 +2742,26 @@ class TnInputComponent {
2607
2742
  this.onChange(this.value);
2608
2743
  }
2609
2744
  onBlur() {
2745
+ if (this.isSize() && this.value !== '') {
2746
+ // Canonicalize the display on blur: "2048 KiB" -> "2 MiB", "200tib" -> "200 TiB".
2747
+ // Leave unparseable text in place so the consumer's validators can flag it.
2748
+ const bytes = parseSize(this.value, this.sizeDefaultUnit(), this.sizeStandard());
2749
+ if (bytes !== null) {
2750
+ const canonical = formatSize(bytes, this.sizeStandard(), this.sizeRound());
2751
+ // Only rewrite + re-emit when the canonical form actually differs from what
2752
+ // the user left in the field. This both:
2753
+ // (a) skips a no-op onChange that would mark an untouched, pre-filled
2754
+ // control dirty (falsely tripping "unsaved changes" guards), and
2755
+ // (b) still re-syncs the model when rounding is lossy — e.g. an edited
2756
+ // "1.755 GiB" canonicalizes to a different string, so it emits the
2757
+ // byte count parsed back from the rounded display, keeping
2758
+ // parseSize(display) === model.
2759
+ if (canonical !== this.value) {
2760
+ this.value = canonical;
2761
+ this.onChange(parseSize(canonical, this.sizeDefaultUnit(), this.sizeStandard()));
2762
+ }
2763
+ }
2764
+ }
2610
2765
  this.onTouched();
2611
2766
  }
2612
2767
  /** Strips any character that can't appear in the current numeric mode (single leading '-', single '.' for decimals). */
@@ -2637,7 +2792,7 @@ class TnInputComponent {
2637
2792
  return Number.isNaN(parsed) ? null : parsed;
2638
2793
  }
2639
2794
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2640
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnInputComponent, isStandalone: true, selector: "tn-input", inputs: { inputType: { classPropertyName: "inputType", publicName: "inputType", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, allowDecimals: { classPropertyName: "allowDecimals", publicName: "allowDecimals", isSignal: true, isRequired: false, transformFunction: null }, prefixIcon: { classPropertyName: "prefixIcon", publicName: "prefixIcon", isSignal: true, isRequired: false, transformFunction: null }, prefixIconLibrary: { classPropertyName: "prefixIconLibrary", publicName: "prefixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIcon: { classPropertyName: "suffixIcon", publicName: "suffixIcon", isSignal: true, isRequired: false, transformFunction: null }, suffixIconLibrary: { classPropertyName: "suffixIconLibrary", publicName: "suffixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIconAriaLabel: { classPropertyName: "suffixIconAriaLabel", publicName: "suffixIconAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSuffixAction: "onSuffixAction" }, providers: [
2795
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnInputComponent, isStandalone: true, selector: "tn-input", inputs: { inputType: { classPropertyName: "inputType", publicName: "inputType", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, allowDecimals: { classPropertyName: "allowDecimals", publicName: "allowDecimals", isSignal: true, isRequired: false, transformFunction: null }, sizeStandard: { classPropertyName: "sizeStandard", publicName: "sizeStandard", isSignal: true, isRequired: false, transformFunction: null }, sizeDefaultUnit: { classPropertyName: "sizeDefaultUnit", publicName: "sizeDefaultUnit", isSignal: true, isRequired: false, transformFunction: null }, sizeRound: { classPropertyName: "sizeRound", publicName: "sizeRound", isSignal: true, isRequired: false, transformFunction: null }, prefixIcon: { classPropertyName: "prefixIcon", publicName: "prefixIcon", isSignal: true, isRequired: false, transformFunction: null }, prefixIconLibrary: { classPropertyName: "prefixIconLibrary", publicName: "prefixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIcon: { classPropertyName: "suffixIcon", publicName: "suffixIcon", isSignal: true, isRequired: false, transformFunction: null }, suffixIconLibrary: { classPropertyName: "suffixIconLibrary", publicName: "suffixIconLibrary", isSignal: true, isRequired: false, transformFunction: null }, suffixIconAriaLabel: { classPropertyName: "suffixIconAriaLabel", publicName: "suffixIconAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onSuffixAction: "onSuffixAction" }, providers: [
2641
2796
  {
2642
2797
  provide: NG_VALUE_ACCESSOR,
2643
2798
  useExisting: forwardRef(() => TnInputComponent),
@@ -2654,7 +2809,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
2654
2809
  multi: true
2655
2810
  }
2656
2811
  ], template: "<div\n class=\"tn-input-container\"\n [class.tn-input-container--has-prefix]=\"hasPrefixIcon()\"\n [class.tn-input-container--has-suffix]=\"hasSuffixIcon()\"\n>\n <!-- Prefix icon -->\n @if (hasPrefixIcon()) {\n <tn-icon\n class=\"tn-input__prefix-icon\"\n size=\"sm\"\n aria-hidden=\"true\"\n [name]=\"prefixIcon()!\"\n [library]=\"prefixIconLibrary()\"\n />\n }\n\n <!-- Regular input field -->\n @if (!showTextarea()) {\n <input\n #inputEl\n class=\"tn-input\"\n tnTestIdType=\"input\"\n [id]=\"id\"\n [value]=\"value\"\n [type]=\"resolvedType()\"\n [attr.inputmode]=\"numericInputMode()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [tnTestId]=\"testId()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n />\n }\n\n <!-- Textarea field -->\n @if (showTextarea()) {\n <textarea\n #inputEl\n class=\"tn-input tn-textarea\"\n tnTestIdType=\"textarea\"\n [id]=\"id\"\n [value]=\"value\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.placeholder]=\"placeholder()\"\n [tnTestId]=\"testId()\"\n [rows]=\"rows()\"\n [disabled]=\"isDisabled()\"\n (input)=\"onValueChange($event)\"\n (blur)=\"onBlur()\"\n ></textarea>\n }\n\n <!-- Suffix icon action button -->\n @if (hasSuffixIcon()) {\n <button\n type=\"button\"\n class=\"tn-input__suffix-action\"\n [attr.aria-label]=\"suffixIconAriaLabel()\"\n [disabled]=\"isDisabled()\"\n (click)=\"onSuffixAction.emit($event)\"\n >\n <tn-icon\n size=\"sm\"\n [name]=\"suffixIcon()!\"\n [library]=\"suffixIconLibrary()\"\n />\n </button>\n }\n</div>\n", styles: [".tn-input-container{position:relative;display:flex;align-items:center;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.tn-input-container:focus-within{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-input-container:has(.tn-input:disabled){background-color:var(--tn-alt-bg1, #f8f9fa);opacity:.6;cursor:not-allowed}.tn-input-container:has(.tn-input.error){border-color:var(--tn-error, #dc3545)}.tn-input-container:has(.tn-input.error):focus-within{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-input{display:block;flex:1;min-width:0;min-height:2.5rem;padding:.5rem .75rem;font-size:1rem;line-height:1.5;color:var(--tn-fg1, #212529);background:transparent;border:none;border-radius:.375rem;outline:none;box-sizing:border-box}.tn-input::placeholder{color:var(--tn-alt-fg1, #999);opacity:1}.tn-input:disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed}.tn-input-container--has-prefix .tn-input{padding-left:.75rem;border-left:1px solid var(--tn-lines, #d1d5db);border-top-left-radius:0;border-bottom-left-radius:0}.tn-input-container--has-suffix .tn-input{padding-right:.25rem}.tn-input__prefix-icon{flex-shrink:0;margin-left:.75rem;margin-right:.75rem;color:var(--tn-fg2, #6c757d);pointer-events:none}.tn-input__suffix-action{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;margin-right:.375rem;padding:.25rem;border:none;border-radius:.25rem;background:transparent;color:var(--tn-fg2, #6c757d);cursor:pointer;transition:background-color .15s ease-in-out,color .15s ease-in-out}.tn-input__suffix-action:hover:not(:disabled){background-color:var(--tn-bg3, #f3f4f6);color:var(--tn-fg1, #1f2937)}.tn-input__suffix-action:focus-visible{outline:2px solid var(--tn-primary, #2563eb);outline-offset:1px}.tn-input__suffix-action:disabled{cursor:not-allowed;pointer-events:none}.tn-textarea{min-height:6rem;resize:vertical;line-height:1.4}@media(prefers-reduced-motion:reduce){.tn-input-container,.tn-input__suffix-action{transition:none}}@media(prefers-contrast:high){.tn-input-container{border-width:2px}}\n"] }]
2657
- }], propDecorators: { inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], inputType: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputType", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], allowDecimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDecimals", required: false }] }], prefixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIcon", required: false }] }], prefixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIconLibrary", required: false }] }], suffixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIcon", required: false }] }], suffixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconLibrary", required: false }] }], suffixIconAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconAriaLabel", required: false }] }], onSuffixAction: [{ type: i0.Output, args: ["onSuffixAction"] }] } });
2812
+ }], propDecorators: { inputEl: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }], inputType: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputType", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], allowDecimals: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDecimals", required: false }] }], sizeStandard: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeStandard", required: false }] }], sizeDefaultUnit: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeDefaultUnit", required: false }] }], sizeRound: [{ type: i0.Input, args: [{ isSignal: true, alias: "sizeRound", required: false }] }], prefixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIcon", required: false }] }], prefixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIconLibrary", required: false }] }], suffixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIcon", required: false }] }], suffixIconLibrary: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconLibrary", required: false }] }], suffixIconAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIconAriaLabel", required: false }] }], onSuffixAction: [{ type: i0.Output, args: ["onSuffixAction"] }] } });
2658
2813
 
2659
2814
  /**
2660
2815
  * Harness for interacting with tn-icon in tests.
@@ -2976,6 +3131,22 @@ class TnInputHarness extends ComponentHarness {
2976
3131
  const parsed = integerMode ? parseInt(raw, 10) : parseFloat(raw);
2977
3132
  return Number.isNaN(parsed) ? null : parsed;
2978
3133
  }
3134
+ /**
3135
+ * Gets the displayed value of a `Size`-type input parsed into a byte count,
3136
+ * mirroring what the control emits to the form model.
3137
+ *
3138
+ * The harness reads only the DOM, so it can't see the component's configured
3139
+ * `sizeStandard`/`sizeDefaultUnit` — pass them here to match the field under
3140
+ * test. Defaults to IEC base-2 with a `MiB` default unit (the component
3141
+ * defaults). Empty/invalid input resolves to `null`.
3142
+ *
3143
+ * @param standard Unit standard the field uses (defaults to `'iec'`).
3144
+ * @param defaultUnit Unit assumed for bare numbers (defaults to `'MiB'`).
3145
+ * @returns Promise resolving to the byte count, or null.
3146
+ */
3147
+ async getByteValue(standard = 'iec', defaultUnit = 'MiB') {
3148
+ return parseSize(await this.getValue(), defaultUnit, standard);
3149
+ }
2979
3150
  /**
2980
3151
  * Gets the `inputmode` attribute (e.g. 'numeric' or 'decimal' in number mode).
2981
3152
  *
@@ -15177,5 +15348,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
15177
15348
  * Generated bundle index. Do not edit.
15178
15349
  */
15179
15350
 
15180
- export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_FORM_FIELD_ERRORS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuItemComponent, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, composeTestId, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, kebabTestSegment, libIconMarker, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
15351
+ export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, StripMntPrefixPipe, THEME_MAP, THEME_STORAGE_KEY, TN_FORM_FIELD_ERRORS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarComponent, TnCalendarHeaderComponent, TnCardComponent, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFilePickerComponent, TnFilePickerPopupComponent, TnFormFieldComponent, TnFormFieldHarness, TnHeaderCellDefDirective, TnIconButtonComponent, TnIconButtonHarness, TnIconComponent, TnIconHarness, TnIconRegistryService, TnIconTesting, TnInputComponent, TnInputDirective, TnInputHarness, TnKeyboardShortcutComponent, TnKeyboardShortcutService, TnListAvatarDirective, TnListComponent, TnListIconDirective, TnListItemComponent, TnListItemLineDirective, TnListItemPrimaryDirective, TnListItemSecondaryDirective, TnListItemTitleDirective, TnListItemTrailingDirective, TnListOptionComponent, TnListSubheaderComponent, TnMenuActivateHoverDirective, TnMenuComponent, TnMenuHarness, TnMenuItemComponent, TnMenuTesting, TnMenuTriggerDirective, TnMonthViewComponent, TnMultiYearViewComponent, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioHarness, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeNodeComponent, TnTreeNodeOutletDirective, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, composeTestId, createLucideLibrary, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, formatSize, kebabTestSegment, libIconMarker, parseSize, registerLucideIcons, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
15181
15352
  //# sourceMappingURL=truenas-ui-components.mjs.map