@truenas/ui-components 0.6.1 → 0.7.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.
|
@@ -5746,6 +5746,13 @@ class TnIconButtonHarness extends ComponentHarness {
|
|
|
5746
5746
|
// Ordered binary/decimal prefixes; the array index doubles as the power applied
|
|
5747
5747
|
// to the base (B = base^0, K = base^1, M = base^2, ...).
|
|
5748
5748
|
const UNIT_PREFIXES = ['B', 'K', 'M', 'G', 'T', 'P', 'E'];
|
|
5749
|
+
// Display spellings for each standard, indexed by the same power as
|
|
5750
|
+
// UNIT_PREFIXES. These are the symbols `filesize` emits, so a value this module
|
|
5751
|
+
// renders itself is spelled exactly like one `formatSize` renders.
|
|
5752
|
+
const UNIT_SYMBOLS = {
|
|
5753
|
+
iec: ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'],
|
|
5754
|
+
si: ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB'],
|
|
5755
|
+
};
|
|
5749
5756
|
/**
|
|
5750
5757
|
* Formats a raw byte count into a human-readable string (e.g. `2 GiB`).
|
|
5751
5758
|
*
|
|
@@ -5784,6 +5791,17 @@ function formatSize(bytes, standard = 'iec', round = 2) {
|
|
|
5784
5791
|
* @param standard Unit standard (defaults to IEC base-2).
|
|
5785
5792
|
*/
|
|
5786
5793
|
function parseSize(raw, defaultUnit = 'MiB', standard = 'iec') {
|
|
5794
|
+
return parseSizeParts(raw, defaultUnit, standard)?.bytes ?? null;
|
|
5795
|
+
}
|
|
5796
|
+
/**
|
|
5797
|
+
* {@link parseSize}, keeping the parts rather than collapsing them to a byte
|
|
5798
|
+
* count. The unit matters when the text has to be re-rendered: the number the
|
|
5799
|
+
* user wrote and the unit they wrote it in always denote their byte count
|
|
5800
|
+
* exactly, which a rounded rendering may not.
|
|
5801
|
+
*
|
|
5802
|
+
* @internal
|
|
5803
|
+
*/
|
|
5804
|
+
function parseSizeParts(raw, defaultUnit = 'MiB', standard = 'iec') {
|
|
5787
5805
|
if (raw === null || raw === undefined) {
|
|
5788
5806
|
return null;
|
|
5789
5807
|
}
|
|
@@ -5806,9 +5824,101 @@ function parseSize(raw, defaultUnit = 'MiB', standard = 'iec') {
|
|
|
5806
5824
|
return null;
|
|
5807
5825
|
}
|
|
5808
5826
|
const base = standard === 'si' ? 1000 : 1024;
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5827
|
+
return {
|
|
5828
|
+
// Round to whole bytes: a byte count is an integer, and rounding absorbs the
|
|
5829
|
+
// floating-point drift of e.g. 1.1 * 1024.
|
|
5830
|
+
bytes: Math.round(num * base ** exponent),
|
|
5831
|
+
digits: match[1],
|
|
5832
|
+
exponent,
|
|
5833
|
+
};
|
|
5834
|
+
}
|
|
5835
|
+
/**
|
|
5836
|
+
* Renders a byte count in the largest unit that expresses it EXACTLY within
|
|
5837
|
+
* `round` decimals, or `null` when no suitable unit does.
|
|
5838
|
+
*
|
|
5839
|
+
* The point is a field the user types into. `formatSize` always picks the
|
|
5840
|
+
* natural unit and rounds to fit — 1 572 864 000 becomes `1.46 GiB`, which is a
|
|
5841
|
+
* fine way to READ a size but a lie to hand back to someone who typed
|
|
5842
|
+
* `1500 MiB`: it no longer denotes the value the field holds. One unit down
|
|
5843
|
+
* usually does denote it exactly, so this looks there.
|
|
5844
|
+
*
|
|
5845
|
+
* It looks exactly one step down and no further, which is what keeps the result
|
|
5846
|
+
* readable: a value that is exact in neither its own unit nor the next is
|
|
5847
|
+
* always exact in bytes, and `1567663063 B` helps nobody. Such a value has no
|
|
5848
|
+
* exact spelling worth showing, so this returns `null` and the caller falls
|
|
5849
|
+
* back to the rounded rendering.
|
|
5850
|
+
*
|
|
5851
|
+
* @param bytes The byte count to render. Non-integer and unsafe magnitudes
|
|
5852
|
+
* return `null` — a byte count is a whole number, and past
|
|
5853
|
+
* `MAX_SAFE_INTEGER` "exactly" stops meaning anything.
|
|
5854
|
+
* @param standard Unit standard (defaults to IEC base-2).
|
|
5855
|
+
* @param round Decimal places the rendering may use (defaults to 2).
|
|
5856
|
+
* @internal
|
|
5857
|
+
*/
|
|
5858
|
+
function formatSizeExact(bytes, standard = 'iec', round = 2) {
|
|
5859
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0) {
|
|
5860
|
+
return null;
|
|
5861
|
+
}
|
|
5862
|
+
const symbols = UNIT_SYMBOLS[standard];
|
|
5863
|
+
if (bytes === 0) {
|
|
5864
|
+
return `0 ${symbols[0]}`;
|
|
5865
|
+
}
|
|
5866
|
+
const base = standard === 'si' ? 1000 : 1024;
|
|
5867
|
+
// The unit `formatSize` would pick, and the one below it.
|
|
5868
|
+
const natural = Math.min(symbols.length - 1, Math.floor(Math.log(bytes) / Math.log(base)));
|
|
5869
|
+
for (let exponent = natural; exponent >= Math.max(0, natural - 1); exponent--) {
|
|
5870
|
+
const scaled = bytes / base ** exponent;
|
|
5871
|
+
// Exact in this unit when the rounded rendering IS the value, and it still
|
|
5872
|
+
// multiplies back to the byte count we started from.
|
|
5873
|
+
if (Number(scaled.toFixed(round)) === scaled && scaled * base ** exponent === bytes) {
|
|
5874
|
+
return `${scaled} ${symbols[exponent]}`;
|
|
5875
|
+
}
|
|
5876
|
+
}
|
|
5877
|
+
return null;
|
|
5878
|
+
}
|
|
5879
|
+
/**
|
|
5880
|
+
* Formats a byte count for a field the user edits: {@link formatSizeExact} when
|
|
5881
|
+
* some unit states the value exactly, and {@link formatSize}'s rounded
|
|
5882
|
+
* rendering when none does.
|
|
5883
|
+
*
|
|
5884
|
+
* @param bytes The byte count to format.
|
|
5885
|
+
* @param standard Unit standard (defaults to IEC base-2).
|
|
5886
|
+
* @param round Decimal places to round to (defaults to 2).
|
|
5887
|
+
* @internal
|
|
5888
|
+
*/
|
|
5889
|
+
function formatSizeForEditing(bytes, standard = 'iec', round = 2) {
|
|
5890
|
+
const rounded = formatSize(bytes, standard, round);
|
|
5891
|
+
if (rounded === '') {
|
|
5892
|
+
return '';
|
|
5893
|
+
}
|
|
5894
|
+
return formatSizeExact(Number(bytes), standard, round) ?? rounded;
|
|
5895
|
+
}
|
|
5896
|
+
/**
|
|
5897
|
+
* Tidies a size the user typed into its canonical spelling, without ever
|
|
5898
|
+
* restating it as a value it does not denote.
|
|
5899
|
+
*
|
|
5900
|
+
* `2048 KiB` becomes `2 MiB` and `200tib` becomes `200 TiB`, because both
|
|
5901
|
+
* renderings are exact. `1500` becomes `1500 MiB` rather than `1.46 GiB`, and
|
|
5902
|
+
* `1.755 GiB` is left at `1.755 GiB`: where no unit expresses the value within
|
|
5903
|
+
* `round` decimals, the number and unit the user wrote do, so those are kept
|
|
5904
|
+
* and only the spelling is normalized.
|
|
5905
|
+
*
|
|
5906
|
+
* Returns `null` for text {@link parseSize} cannot read, which the caller leaves
|
|
5907
|
+
* on screen for its validators to flag.
|
|
5908
|
+
*
|
|
5909
|
+
* @param raw The text to canonicalize.
|
|
5910
|
+
* @param defaultUnit Unit assumed when the input carries no unit (defaults to `MiB`).
|
|
5911
|
+
* @param standard Unit standard (defaults to IEC base-2).
|
|
5912
|
+
* @param round Decimal places a rendering may use (defaults to 2).
|
|
5913
|
+
* @internal
|
|
5914
|
+
*/
|
|
5915
|
+
function canonicalizeSize(raw, defaultUnit = 'MiB', standard = 'iec', round = 2) {
|
|
5916
|
+
const parts = parseSizeParts(raw, defaultUnit, standard);
|
|
5917
|
+
if (!parts) {
|
|
5918
|
+
return null;
|
|
5919
|
+
}
|
|
5920
|
+
return (formatSizeExact(parts.bytes, standard, round)
|
|
5921
|
+
?? `${parts.digits} ${UNIT_SYMBOLS[standard][parts.exponent]}`);
|
|
5812
5922
|
}
|
|
5813
5923
|
/**
|
|
5814
5924
|
* Resolves a unit spelling to its prefix power (B → 0, K → 1, M → 2, ...).
|
|
@@ -6011,6 +6121,12 @@ class TnInputComponent {
|
|
|
6011
6121
|
/**
|
|
6012
6122
|
* Decimal places used when formatting a `Size` field's value for display
|
|
6013
6123
|
* (e.g. `1.5 GiB`). Ignored unless `inputType` is `Size`.
|
|
6124
|
+
*
|
|
6125
|
+
* Display only: the form model always holds the exact byte count the typed
|
|
6126
|
+
* text denotes, and is never re-read from the display. The display avoids
|
|
6127
|
+
* rounding where it can — `1500M` shows as `1500 MiB`, not as a `1.46 GiB`
|
|
6128
|
+
* that means 1 567 663 063 — and where a value fits no unit exactly (a size
|
|
6129
|
+
* read back from the server, say) it is rounded for reading only.
|
|
6014
6130
|
*/
|
|
6015
6131
|
sizeRound = input(2, ...(ngDevMode ? [{ debugName: "sizeRound" }] : []));
|
|
6016
6132
|
/**
|
|
@@ -6136,7 +6252,10 @@ class TnInputComponent {
|
|
|
6136
6252
|
if (this.isSize()) {
|
|
6137
6253
|
// The model is a byte count; show it as a human-readable string. Empty/null
|
|
6138
6254
|
// stays blank, and a non-numeric model maps to blank (formatSize returns '').
|
|
6139
|
-
|
|
6255
|
+
// `formatSizeForEditing` prefers a unit that states the value exactly, so a
|
|
6256
|
+
// field the user can edit shows '1500 MiB' rather than a '1.46 GiB' that
|
|
6257
|
+
// would parse back ~5 MB short of what it holds.
|
|
6258
|
+
this.value.set(formatSizeForEditing(value, this.sizeStandard(), this.sizeRound()));
|
|
6140
6259
|
return;
|
|
6141
6260
|
}
|
|
6142
6261
|
// Display the model verbatim via String(); do NOT sanitize here. Sanitizing a
|
|
@@ -6187,23 +6306,24 @@ class TnInputComponent {
|
|
|
6187
6306
|
}
|
|
6188
6307
|
onBlur() {
|
|
6189
6308
|
if (this.isSize() && this.value() !== '') {
|
|
6190
|
-
// Canonicalize the
|
|
6191
|
-
// Leave unparseable text in place so the
|
|
6192
|
-
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6309
|
+
// Canonicalize the DISPLAY on blur: "2048 KiB" -> "2 MiB", "200tib" ->
|
|
6310
|
+
// "200 TiB", "1500" -> "1500 MiB". Leave unparseable text in place so the
|
|
6311
|
+
// consumer's validators can flag it.
|
|
6312
|
+
//
|
|
6313
|
+
// `canonicalizeSize` never restates the value as something it is not: where
|
|
6314
|
+
// no unit expresses it within `sizeRound` decimals it keeps the number and
|
|
6315
|
+
// unit the user wrote. So the text left on screen always denotes the byte
|
|
6316
|
+
// count in the model — "1500" tidies to "1500 MiB", not to a "1.46 GiB"
|
|
6317
|
+
// that means 1_567_663_063, ~5 MB short of the 1_572_864_000 stored.
|
|
6318
|
+
//
|
|
6319
|
+
// The model is still deliberately NOT re-emitted from that text. The byte
|
|
6320
|
+
// count `onValueChange` emitted while typing is already exact, and emitting
|
|
6321
|
+
// again would mark a control dirty for a field the user only tabbed through.
|
|
6322
|
+
const canonical = canonicalizeSize(this.value(), this.sizeDefaultUnit(), this.sizeStandard(), this.sizeRound());
|
|
6323
|
+
if (canonical !== null) {
|
|
6324
|
+
// A no-op for a pre-filled control the user merely tabbed through: the
|
|
6325
|
+
// signal already holds that exact string, so nothing repaints.
|
|
6326
|
+
this.value.set(canonical);
|
|
6207
6327
|
}
|
|
6208
6328
|
}
|
|
6209
6329
|
this.onTouched();
|
|
@@ -9514,6 +9634,12 @@ class TnRadioComponent {
|
|
|
9514
9634
|
name = input(undefined, ...(ngDevMode ? [{ debugName: "name" }] : []));
|
|
9515
9635
|
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
9516
9636
|
required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
|
|
9637
|
+
/**
|
|
9638
|
+
* Test-id base. The element type this composes with is `radio-button`, not `radio`: the id
|
|
9639
|
+
* identifies one radio BUTTON within a `radio-group`, matching the `radio-group`/`radio-button`
|
|
9640
|
+
* pair every other grouped control in this library uses (`button-toggle-group`/`button-toggle`).
|
|
9641
|
+
* So `testId="email"` yields `radio-button-email`.
|
|
9642
|
+
*/
|
|
9517
9643
|
testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
9518
9644
|
/** Test-id base, falling back to the bound control name when `testId` is unset. */
|
|
9519
9645
|
resolvedTestId = controlTestId(this.testId);
|
|
@@ -9627,7 +9753,7 @@ class TnRadioComponent {
|
|
|
9627
9753
|
useExisting: forwardRef(() => TnRadioComponent),
|
|
9628
9754
|
multi: true
|
|
9629
9755
|
}
|
|
9630
|
-
], viewQueries: [{ propertyName: "radioEl", first: true, predicate: ["radioEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"resolvedName()\"\n [value]=\"value()\"\n [checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"isRequired()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-error-text, var(--tn-red, #b91c1c))}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }] });
|
|
9756
|
+
], viewQueries: [{ propertyName: "radioEl", first: true, predicate: ["radioEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio-button\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"resolvedName()\"\n [value]=\"value()\"\n [checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"isRequired()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-error-text, var(--tn-red, #b91c1c))}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: A11yModule }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }, { kind: "pipe", type: LabelMarkupPipe, name: "tnLabelMarkup" }] });
|
|
9631
9757
|
}
|
|
9632
9758
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnRadioComponent, decorators: [{
|
|
9633
9759
|
type: Component,
|
|
@@ -9637,7 +9763,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
9637
9763
|
useExisting: forwardRef(() => TnRadioComponent),
|
|
9638
9764
|
multi: true
|
|
9639
9765
|
}
|
|
9640
|
-
], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"resolvedName()\"\n [value]=\"value()\"\n [checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"isRequired()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-error-text, var(--tn-red, #b91c1c))}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"] }]
|
|
9766
|
+
], template: "<div [ngClass]=\"classes()\">\n <label class=\"tn-radio__label\" tnTestIdType=\"radio-button\" [for]=\"id\" [tnTestId]=\"resolvedTestId()\">\n <input\n #radioEl\n type=\"radio\"\n class=\"tn-radio__input\"\n [id]=\"id\"\n [name]=\"resolvedName()\"\n [value]=\"value()\"\n [checked]=\"checked()\"\n [disabled]=\"isDisabled()\"\n [required]=\"isRequired()\"\n [attr.aria-describedby]=\"error() ? id + '-error' : null\"\n [attr.aria-invalid]=\"error() ? 'true' : null\"\n (change)=\"onRadioChange($event)\"\n />\n <span class=\"tn-radio__checkmark\"></span>\n <span class=\"tn-radio__text\" [innerHTML]=\"label() | tnLabelMarkup\"></span>\n </label>\n\n @if (error()) {\n <div\n class=\"tn-radio__error\"\n role=\"alert\"\n [id]=\"id + '-error'\"\n >\n {{ error() }}\n </div>\n }\n</div>", styles: [".tn-radio{display:inline-block;margin-bottom:.5rem}.tn-radio__label{display:flex;align-items:center;cursor:pointer;-webkit-user-select:none;user-select:none;gap:.5rem}.tn-radio__label:hover .tn-radio__checkmark{border-color:var(--tn-primary, #007cba)}.tn-radio__input{position:absolute;opacity:0;cursor:pointer;height:0;width:0}.tn-radio__input:focus+.tn-radio__checkmark{outline:2px solid var(--tn-primary, #007cba);outline-offset:2px}.tn-radio__input:checked+.tn-radio__checkmark{border-color:var(--tn-primary, #007cba);background-color:var(--tn-primary, #007cba)}.tn-radio__input:checked+.tn-radio__checkmark:after{display:block}.tn-radio__input:disabled+.tn-radio__checkmark{border-color:var(--tn-lines, #e5e7eb);background-color:var(--tn-alt-bg1, #f8f9fa);cursor:not-allowed}.tn-radio__checkmark{position:relative;height:18px;width:18px;border:2px solid var(--tn-lines, #e5e7eb);border-radius:50%;background-color:transparent;transition:all .2s ease;flex-shrink:0}.tn-radio__checkmark:after{content:\"\";position:absolute;display:none;top:50%;left:50%;width:8px;height:8px;border-radius:50%;background-color:#fff;transform:translate(-50%,-50%)}.tn-radio__text ::ng-deep code{font-family:var(--tn-font-family-monospace, monospace);font-size:.875em;background:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #ccc);border-radius:4px;padding:.125em .45em}.tn-radio__text{color:var(--tn-fg1, #000000);font-size:14px;line-height:1.4}.tn-radio__error{margin-top:.25rem;font-size:12px;color:var(--tn-error-text, var(--tn-red, #b91c1c))}.tn-radio--disabled .tn-radio__label{cursor:not-allowed;opacity:.6}.tn-radio--disabled .tn-radio__text{color:var(--tn-fg2, #6c757d)}.tn-radio--error .tn-radio__checkmark{border-color:var(--tn-red, #dc3545)}\n"] }]
|
|
9641
9767
|
}], propDecorators: { radioEl: [{ type: i0.ViewChild, args: ['radioEl', { isSignal: true }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], error: [{ type: i0.Input, args: [{ isSignal: true, alias: "error", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
|
|
9642
9768
|
|
|
9643
9769
|
/**
|
|
@@ -9835,7 +9961,7 @@ class TnRadioGroupComponent {
|
|
|
9835
9961
|
/**
|
|
9836
9962
|
* Test-id base for the group and, scoped by the option label, for every option rendered from
|
|
9837
9963
|
* `options`: base `encryption` yields `radio-group-encryption` on the group and
|
|
9838
|
-
* `radio-encryption-passphrase` on its options. Falls back to the bound control name.
|
|
9964
|
+
* `radio-button-encryption-passphrase` on its options. Falls back to the bound control name.
|
|
9839
9965
|
*
|
|
9840
9966
|
* Projected `<tn-radio>` children keep their own test-id resolution — the group does not reach
|
|
9841
9967
|
* into content it did not render — so give each one a `testId` of its own.
|
|
@@ -28024,5 +28150,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
|
|
|
28024
28150
|
* Generated bundle index. Do not edit.
|
|
28025
28151
|
*/
|
|
28026
28152
|
|
|
28027
|
-
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, THEME_MAP, THEME_STORAGE_KEY, TN_AUTOCOMPLETE_DEFAULT_LABELS, TN_AUTOCOMPLETE_LABELS, TN_BRANDED_SPINNER_DEFAULT_LABEL, TN_CALENDAR_INTL, TN_CALENDAR_INTL_DEFAULTS, TN_DIALOG_CHROME_LABELS, TN_DIALOG_DEFAULT_CHROME_LABELS, TN_DIALOG_SHELL_DEFAULT_LABEL, TN_DRAWER_CONTENT_LABEL, TN_DRAWER_DEFAULT_LABEL, TN_FORM_FIELD_CONTEXT, TN_FORM_FIELD_DISMISSIBLE_ERRORS, TN_FORM_FIELD_ERRORS, TN_FORM_LIST_CONTEXT, TN_PARTICLE_PROGRESS_BAR_DEFAULT_LABEL, TN_PROGRESS_BAR_DEFAULT_LABEL, TN_RADIO_GROUP, TN_SELECT_DEFAULT_LABELS, TN_SELECT_LABELS, TN_SIDE_PANEL_CONTENT_LABEL, TN_SIDE_PANEL_DEFAULT_LABEL, TN_SIDE_PANEL_OVERFLOW_TOLERANCE_PX, TN_SPINNER_DEFAULT_LABEL, TN_STEPPER_STATUS_COMPLETED, TN_STEPPER_STATUS_ERROR, TN_TABLE_DEFAULT_LABELS, TN_TABLE_LABELS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TABLE_SCROLL_REGION_LABEL, TN_TAB_PANEL_CONTENT_LABEL, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TN_TOAST_ANNOUNCE_DELAY_MS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerActionHarness, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarCellHarness, TnCalendarComponent, TnCalendarHarness, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerHarness, TnFilePickerPopupComponent, TnFormErrorsComponent, TnFormErrorsHarness, TnFormFieldComponent, TnFormFieldHarness, TnFormListComponent, TnFormListHarness, TnFormListItemComponent, TnFormListItemHarness, TnFormSectionComponent, TnFormSectionHarness, 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, TnNestedTreeDataSource, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioGroupComponent, TnRadioGroupHarness, TnRadioHarness, TnRowActionsDefDirective, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnStepperHarness, TnStepperNextDirective, TnStepperPreviousDirective, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTableTesting, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTooltipHarness, TnTooltipTesting, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeHarness, TnTreeNodeComponent, TnTreeNodeHarness, TnTreeNodeOutletDirective, TnTreeVirtualScrollNodeOutletDirective, TnTreeVirtualScrollViewComponent, TnTreeVirtualScrollViewHarness, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, allowsCurrentDirectorySelection, composeTestId, controlTestId, createFlatTreeControl, createLucideLibrary, createNestedTreeControl, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, defaultTreeItemSize, formatSize, getSelectableTypes, injectTnCalendarIntl, injectTnFormFieldAria, injectTnLabels, isPathWithinRoot, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, normalizeRootPath, optionTestId, parseLabelMarkup, parseSize, registerLucideIcons, resolveErrorMessage, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
|
|
28153
|
+
export { CommonShortcuts, DEFAULT_THEME, DiskIconComponent, DiskType, FileSizePipe, InputType, LIGHT_THEME, LabelMarkupPipe, LabelTextPipe, LinuxModifierKeys, LinuxShortcuts, ModifierKeys, QuickShortcuts, ShortcutBuilder, THEME_MAP, THEME_STORAGE_KEY, TN_AUTOCOMPLETE_DEFAULT_LABELS, TN_AUTOCOMPLETE_LABELS, TN_BRANDED_SPINNER_DEFAULT_LABEL, TN_CALENDAR_INTL, TN_CALENDAR_INTL_DEFAULTS, TN_DIALOG_CHROME_LABELS, TN_DIALOG_DEFAULT_CHROME_LABELS, TN_DIALOG_SHELL_DEFAULT_LABEL, TN_DRAWER_CONTENT_LABEL, TN_DRAWER_DEFAULT_LABEL, TN_FORM_FIELD_CONTEXT, TN_FORM_FIELD_DISMISSIBLE_ERRORS, TN_FORM_FIELD_ERRORS, TN_FORM_LIST_CONTEXT, TN_PARTICLE_PROGRESS_BAR_DEFAULT_LABEL, TN_PROGRESS_BAR_DEFAULT_LABEL, TN_RADIO_GROUP, TN_SELECT_DEFAULT_LABELS, TN_SELECT_LABELS, TN_SIDE_PANEL_CONTENT_LABEL, TN_SIDE_PANEL_DEFAULT_LABEL, TN_SIDE_PANEL_OVERFLOW_TOLERANCE_PX, TN_SPINNER_DEFAULT_LABEL, TN_STEPPER_STATUS_COMPLETED, TN_STEPPER_STATUS_ERROR, TN_TABLE_DEFAULT_LABELS, TN_TABLE_LABELS, TN_TABLE_PAGER_DEFAULT_LABELS, TN_TABLE_PAGER_LABELS, TN_TABLE_SCROLL_REGION_LABEL, TN_TAB_PANEL_CONTENT_LABEL, TN_TEST_ATTR, TN_THEME_DEFINITIONS, TN_TOAST_ANNOUNCE_DELAY_MS, TnAutocompleteComponent, TnAutocompleteHarness, TnBannerActionDirective, TnBannerActionHarness, TnBannerComponent, TnBannerHarness, TnBrandedSpinnerComponent, TnButtonComponent, TnButtonHarness, TnButtonToggleComponent, TnButtonToggleGroupComponent, TnButtonToggleGroupHarness, TnButtonToggleHarness, TnCalendarCellHarness, TnCalendarComponent, TnCalendarHarness, TnCalendarHeaderComponent, TnCardComponent, TnCardFooterActionsDirective, TnCardHeaderActionsDirective, TnCardHeaderDirective, TnCellDefDirective, TnCheckboxComponent, TnCheckboxHarness, TnCheckboxLabelDirective, TnChipComponent, TnChipHarness, TnChipInputComponent, TnChipInputHarness, TnConfirmDialogComponent, TnDateInputComponent, TnDateInputHarness, TnDateRangeInputComponent, TnDateRangeInputHarness, TnDetailRowDefDirective, TnDialog, TnDialogHarness, TnDialogShellComponent, TnDialogTesting, TnDividerComponent, TnDividerDirective, TnDrawerComponent, TnDrawerContainerComponent, TnDrawerContainerHarness, TnDrawerContentComponent, TnDrawerHarness, TnEmptyComponent, TnEmptyHarness, TnExpansionPanelComponent, TnExpansionPanelHarness, TnFileInputComponent, TnFileInputHarness, TnFilePickerComponent, TnFilePickerHarness, TnFilePickerPopupComponent, TnFormErrorsComponent, TnFormErrorsHarness, TnFormFieldComponent, TnFormFieldHarness, TnFormListComponent, TnFormListHarness, TnFormListItemComponent, TnFormListItemHarness, TnFormSectionComponent, TnFormSectionHarness, 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, TnNestedTreeDataSource, TnNestedTreeNodeComponent, TnParticleProgressBarComponent, TnProgressBarComponent, TnRadioComponent, TnRadioGroupComponent, TnRadioGroupHarness, TnRadioHarness, TnRowActionsDefDirective, TnSelectComponent, TnSelectHarness, TnSelectionListComponent, TnSidePanelActionDirective, TnSidePanelComponent, TnSidePanelHarness, TnSidePanelHeaderActionDirective, TnSlideToggleComponent, TnSlideToggleHarness, TnSliderComponent, TnSliderThumbDirective, TnSliderWithLabelDirective, TnSpinnerComponent, TnSpriteLoaderService, TnStepComponent, TnStepperComponent, TnStepperHarness, TnStepperNextDirective, TnStepperPreviousDirective, TnTabComponent, TnTabHarness, TnTabPanelComponent, TnTabPanelHarness, TnTableColumnDirective, TnTableComponent, TnTableHarness, TnTablePagerComponent, TnTablePagerHarness, TnTableTesting, TnTabsComponent, TnTabsHarness, TnTestIdDirective, TnTheme, TnThemeService, TnTimeInputComponent, TnToastComponent, TnToastMock, TnToastPosition, TnToastRef, TnToastService, TnToastTesting, TnToastType, TnTooltipComponent, TnTooltipDirective, TnTooltipHarness, TnTooltipTesting, TnTreeComponent, TnTreeFlatDataSource, TnTreeFlattener, TnTreeHarness, TnTreeNodeComponent, TnTreeNodeHarness, TnTreeNodeOutletDirective, TnTreeVirtualScrollNodeOutletDirective, TnTreeVirtualScrollViewComponent, TnTreeVirtualScrollViewHarness, TruncatePathPipe, WindowsModifierKeys, WindowsShortcuts, allowsCurrentDirectorySelection, canonicalizeSize, composeTestId, controlTestId, createFlatTreeControl, createLucideLibrary, createNestedTreeControl, createShortcut, defaultSpriteBasePath, defaultSpriteConfigPath, defaultTreeItemSize, formatSize, formatSizeExact, formatSizeForEditing, getSelectableTypes, injectTnCalendarIntl, injectTnFormFieldAria, injectTnLabels, isPathWithinRoot, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, normalizeRootPath, optionTestId, parseLabelMarkup, parseSize, parseSizeParts, registerLucideIcons, resolveErrorMessage, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
|
|
28028
28154
|
//# sourceMappingURL=truenas-ui-components.mjs.map
|