@truenas/ui-components 0.7.1 → 0.7.2

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.
@@ -652,7 +652,7 @@ function injectTnLabels(token) {
652
652
  return isSignal(provided) ? provided : signal(provided).asReadonly();
653
653
  }
654
654
 
655
- let nextId$5 = 0;
655
+ let nextId$6 = 0;
656
656
  /** English defaults used when no `TN_AUTOCOMPLETE_LABELS` provider is registered. */
657
657
  const TN_AUTOCOMPLETE_DEFAULT_LABELS = {
658
658
  placeholder: 'Type to search...',
@@ -672,7 +672,7 @@ class TnAutocompleteComponent {
672
672
  overlay = inject(Overlay);
673
673
  viewContainerRef = inject(ViewContainerRef);
674
674
  /** Unique instance ID for ARIA linkage */
675
- uid = `tn-autocomplete-${nextId$5++}`;
675
+ uid = `tn-autocomplete-${nextId$6++}`;
676
676
  /**
677
677
  * All available options. The `label` is displayed; the `value` is committed
678
678
  * to the form control. A written value is resolved back to its option's
@@ -6184,7 +6184,7 @@ function libIconMarker(iconName) {
6184
6184
  // Module-level counter for deterministic, unique instance ids (matches the
6185
6185
  // tn-autocomplete convention). Deterministic ids are SSR/hydration-safe and
6186
6186
  // stable across snapshots, unlike a Math.random() seed.
6187
- let nextId$4 = 0;
6187
+ let nextId$5 = 0;
6188
6188
  class TnInputComponent {
6189
6189
  inputEl = viewChild.required('inputEl');
6190
6190
  inputType = input(InputType.PlainText, ...(ngDevMode ? [{ debugName: "inputType" }] : []));
@@ -6361,7 +6361,7 @@ class TnInputComponent {
6361
6361
  // Unique per instance: a hard-coded id produced duplicate ids when multiple
6362
6362
  // tn-inputs share a page (invalid HTML, and it breaks any future label `for=`,
6363
6363
  // aria-describedby, or getElementById targeting this input).
6364
- id = `tn-input-${nextId$4++}`;
6364
+ id = `tn-input-${nextId$5++}`;
6365
6365
  // Protected: the display string is owned by the component and driven through the
6366
6366
  // CVA flow (writeValue / onValueChange). External writes would bypass that flow.
6367
6367
  // A signal so the `[value]` binding reflects writeValue reactively — a plain field
@@ -7628,7 +7628,7 @@ class TnChipHarness extends ComponentHarness {
7628
7628
  }
7629
7629
  }
7630
7630
 
7631
- let nextId$3 = 0;
7631
+ let nextId$4 = 0;
7632
7632
  /**
7633
7633
  * An editable, multi-value chip input — tokenized entry where typed text
7634
7634
  * becomes removable `tn-chip`s alongside an inline text field. Text is
@@ -7673,7 +7673,7 @@ class TnChipInputComponent {
7673
7673
  overlay = inject(Overlay);
7674
7674
  viewContainerRef = inject(ViewContainerRef);
7675
7675
  /** Unique instance id for ARIA linkage between the input and its dropdown. */
7676
- uid = `tn-chip-input-${nextId$3++}`;
7676
+ uid = `tn-chip-input-${nextId$4++}`;
7677
7677
  /** Placeholder shown in the text field when it is empty. */
7678
7678
  placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
7679
7679
  /** Disables the whole control — chips become non-removable and the field read-only. */
@@ -9762,6 +9762,403 @@ class TnCheckboxHarness extends ComponentHarness {
9762
9762
  }
9763
9763
  }
9764
9764
 
9765
+ let nextId$3 = 0;
9766
+ /**
9767
+ * A `role="group"` wrapper that owns the selection for a set of `tn-checkbox`es, as one control
9768
+ * whose value is the ARRAY of checked option values.
9769
+ *
9770
+ * `tn-checkbox` is a boolean `ControlValueAccessor` — one control per box. A multi-select field
9771
+ * ("which USB devices to pass through", "which catalog trains to offer") has a single
9772
+ * array-valued control instead, and nothing in the library spoke that shape: consumers either
9773
+ * exploded it into one boolean control per option and reassembled the array by hand on submit, or
9774
+ * kept a bespoke component outside the library. This is that shape, with the same
9775
+ * `options`/`compareWith`/`testId` surface as {@link TnRadioGroupComponent} so the single-select
9776
+ * and multi-select forms of the same field read alike.
9777
+ *
9778
+ * ```html
9779
+ * <tn-form-field label="USB Devices" tooltip="Devices to pass through to the container">
9780
+ * <tn-checkbox-group formControlName="usb_devices" [options]="usbOptions" />
9781
+ * </tn-form-field>
9782
+ * ```
9783
+ *
9784
+ * Inside a `tn-form-field` the group is a normal control: the field names it via
9785
+ * `aria-labelledby`, infers the required indicator from its validators, and renders its validation
9786
+ * message — none of which a loose pile of checkboxes gets.
9787
+ *
9788
+ * Options come from the `options` input only. Unlike `tn-radio`, `tn-checkbox` is not group-aware,
9789
+ * so a projected child would render as an independent boolean control that silently ignores the
9790
+ * group's value; the group therefore projects nothing rather than accepting content it cannot
9791
+ * drive.
9792
+ */
9793
+ class TnCheckboxGroupComponent {
9794
+ /**
9795
+ * Options to render, one checkbox each.
9796
+ *
9797
+ * Options are tracked by `value`, so object values must be referentially stable — rebuilding
9798
+ * them as fresh literals on every change detection pass re-creates the whole list. Same
9799
+ * requirement the `compareWith` case implies; hold the objects in a field.
9800
+ */
9801
+ options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
9802
+ /**
9803
+ * Lays the options out in a wrapping row of equal columns instead of stacking them. Column width
9804
+ * comes from `--tn-checkbox-group-inline-basis` (default `50%`), and collapses to one column on
9805
+ * a narrow viewport.
9806
+ */
9807
+ inline = input(false, ...(ngDevMode ? [{ debugName: "inline" }] : []));
9808
+ /**
9809
+ * Explicit accessible name for the group. Inside a `tn-form-field` with a label this is
9810
+ * unnecessary — the field names the group automatically — but a group with neither is announced
9811
+ * as an unlabeled group.
9812
+ *
9813
+ * A required group appends "required" to whatever this says, since that is the only channel
9814
+ * `role="group"` leaves open for it — see the `required` input.
9815
+ */
9816
+ ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
9817
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
9818
+ /**
9819
+ * Whether the group announces itself as required.
9820
+ *
9821
+ * Deliberately NOT propagated to the options' native `required`, which is the opposite of the
9822
+ * choice `tn-radio-group` makes — and the difference is in the HTML, not in taste. A required
9823
+ * radio is satisfied by any one option in the `name` group; a required checkbox is satisfied
9824
+ * only by ITSELF being checked, so propagating it here would have the browser demand every box.
9825
+ * "At least one" is a validator's job.
9826
+ *
9827
+ * Nor does it become `aria-required`, which is the OTHER place `tn-radio-group` cannot be
9828
+ * copied: WAI-ARIA 1.2 supports only the globals plus `aria-activedescendant`/`aria-disabled`
9829
+ * on `role="group"`, so an `aria-required` there is dropped by assistive tech and flagged by
9830
+ * axe, whereas `role="radiogroup"` genuinely supports it. The state instead rides in the
9831
+ * group's accessible NAME, as a "required" suffix — see {@link labelledBy}.
9832
+ */
9833
+ required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : []));
9834
+ /**
9835
+ * Test-id base for the group and, scoped by the option label, for every option: base
9836
+ * `usb_devices` yields `checkbox-group-usb-devices` on the group and
9837
+ * `checkbox-usb-devices-web-cam` on its options. Falls back to the bound control name.
9838
+ */
9839
+ testId = input(undefined, ...(ngDevMode ? [{ debugName: "testId" }] : []));
9840
+ /**
9841
+ * Custom comparator for matching option values against the selected ones. Provide it when option
9842
+ * values are objects — the default is identity, so a structurally-equal-but-distinct object
9843
+ * would leave the group rendering nothing as checked.
9844
+ *
9845
+ * Called with an option value on the right and a currently-selected value on the left, and must
9846
+ * tolerate `null`/`undefined` on either side rather than dereferencing straight into a property.
9847
+ *
9848
+ * @example
9849
+ * ```ts
9850
+ * compareWith = (a, b) => a?.id === b?.id;
9851
+ * ```
9852
+ */
9853
+ compareWith = input(...(ngDevMode ? [undefined, { debugName: "compareWith" }] : []));
9854
+ /** Emits the new value array on every user toggle (not on programmatic writes). */
9855
+ change = output();
9856
+ /**
9857
+ * ARIA wiring from an enclosing `tn-form-field` (label, error/hint, invalid, required).
9858
+ * All-null standalone, or when `ariaLabel` overrides the field's label.
9859
+ */
9860
+ fieldAria = injectTnFormFieldAria(this.ariaLabel);
9861
+ /** Test-id base, falling back to the bound control name when `testId` is unset. */
9862
+ resolvedTestId = controlTestId(this.testId);
9863
+ selectedValues = signal([], ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
9864
+ formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
9865
+ /**
9866
+ * Combined disabled state (own input plus a disabled bound control). The native `disabled` on
9867
+ * each option is what blocks interaction; `aria-disabled` on the group is what tells a screen
9868
+ * reader landing on the container about it, and gives styling a hook for the group as a whole.
9869
+ */
9870
+ isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
9871
+ /** Whether the group announces itself as required: its own input, or the field's inferred state. */
9872
+ ariaRequired = computed(() => this.required() || this.fieldAria.required() === true, ...(ngDevMode ? [{ debugName: "ariaRequired" }] : []));
9873
+ /** Id of the visually hidden "required" text this group contributes to its own name. */
9874
+ requiredTextId = `tn-checkbox-group-required-${nextId$3++}`;
9875
+ /**
9876
+ * `aria-label` for the group: the `ariaLabel` input with the required-ness folded in, since
9877
+ * `role="group"` gives it nowhere else to go (see the `required` input).
9878
+ */
9879
+ resolvedAriaLabel = computed(() => {
9880
+ const label = this.ariaLabel();
9881
+ if (!label) {
9882
+ return null;
9883
+ }
9884
+ return this.ariaRequired() ? `${label} required` : label;
9885
+ }, ...(ngDevMode ? [{ debugName: "resolvedAriaLabel" }] : []));
9886
+ /**
9887
+ * `aria-labelledby` for the group: the enclosing field's label, plus this group's own hidden
9888
+ * "required" text when required. A multi-id `aria-labelledby` concatenates, so the field keeps
9889
+ * naming the group and the required-ness reaches a screen reader as part of that name.
9890
+ *
9891
+ * Null when the name comes from `ariaLabel` instead — that path carries the suffix itself,
9892
+ * because `aria-labelledby` would otherwise win over the very label it is meant to extend.
9893
+ */
9894
+ labelledBy = computed(() => {
9895
+ const fieldLabel = this.fieldAria.labelledby();
9896
+ if (!fieldLabel) {
9897
+ return null;
9898
+ }
9899
+ return this.ariaRequired() ? `${fieldLabel} ${this.requiredTextId}` : fieldLabel;
9900
+ }, ...(ngDevMode ? [{ debugName: "labelledBy" }] : []));
9901
+ onChange = () => { };
9902
+ onTouched = () => { };
9903
+ /** Whether `value` is among the currently selected values. */
9904
+ isSelected(value) {
9905
+ const comparator = this.compareWith();
9906
+ if (comparator) {
9907
+ return this.selectedValues().some((selected) => comparator(selected, value));
9908
+ }
9909
+ return this.selectedValues().includes(value);
9910
+ }
9911
+ /**
9912
+ * Adds or removes an option's value.
9913
+ *
9914
+ * The emitted array is rebuilt in `options` order rather than appended to in click order, so the
9915
+ * value a given set of checked boxes produces is the same however the user got there — which is
9916
+ * what makes a payload diff (and a spec asserting one) stable.
9917
+ *
9918
+ * Ordering is all the rebuild changes: an already-selected value is re-emitted as the object the
9919
+ * consumer wrote, not as the equal-under-`compareWith` object the option holds. A control loaded
9920
+ * with `{ id: 2, name: 'usb-cam' }` against an option of `{ id: 2 }` keeps its `name` when an
9921
+ * unrelated box is toggled; only a newly checked option contributes the option's own value.
9922
+ */
9923
+ toggle(value, checked) {
9924
+ const isCurrentlySelected = this.isSelected(value);
9925
+ if (checked === isCurrentlySelected) {
9926
+ return;
9927
+ }
9928
+ const next = this.options()
9929
+ .map((option) => option.value)
9930
+ .filter((optionValue) => (this.matches(optionValue, value) ? checked : this.isSelected(optionValue)))
9931
+ .map((optionValue) => this.selectedValues().find((selected) => this.matches(optionValue, selected)) ?? optionValue);
9932
+ // Values written by the consumer that no longer correspond to an option are kept: a control
9933
+ // loaded with a device that has since been unplugged would otherwise be silently pruned by an
9934
+ // unrelated toggle.
9935
+ const unknownValues = this.selectedValues().filter((selected) => !this.options().some((option) => this.matches(option.value, selected)));
9936
+ this.selectedValues.set([...next, ...unknownValues]);
9937
+ this.onChange(this.selectedValues());
9938
+ this.onTouched();
9939
+ this.change.emit(this.selectedValues());
9940
+ }
9941
+ matches(a, b) {
9942
+ const comparator = this.compareWith();
9943
+ return comparator ? comparator(a, b) : a === b;
9944
+ }
9945
+ // ── ControlValueAccessor ──
9946
+ writeValue(value) {
9947
+ this.selectedValues.set(value ?? []);
9948
+ }
9949
+ registerOnChange(fn) {
9950
+ this.onChange = fn;
9951
+ }
9952
+ registerOnTouched(fn) {
9953
+ this.onTouched = fn;
9954
+ }
9955
+ setDisabledState(isDisabled) {
9956
+ this.formDisabled.set(isDisabled);
9957
+ }
9958
+ /**
9959
+ * Marks the control touched when focus leaves the group. `focusout` also fires while moving
9960
+ * between the group's own options, which is harmless — the user has interacted either way — and
9961
+ * it is the only blur signal available, since a checkbox's blur does not bubble.
9962
+ */
9963
+ onFocusOut() {
9964
+ this.onTouched();
9965
+ }
9966
+ /** Per-option test-id segments: the group's base scoped by the option label. */
9967
+ optionTestId(option) {
9968
+ return scopeTestId(this.resolvedTestId(), option.label);
9969
+ }
9970
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCheckboxGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
9971
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnCheckboxGroupComponent, isStandalone: true, selector: "tn-checkbox-group", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { change: "change" }, host: { classAttribute: "tn-checkbox-group-host" }, providers: [
9972
+ {
9973
+ provide: NG_VALUE_ACCESSOR,
9974
+ useExisting: forwardRef(() => TnCheckboxGroupComponent),
9975
+ multi: true
9976
+ }
9977
+ ], ngImport: i0, template: "<div\n class=\"tn-checkbox-group\"\n role=\"group\"\n tnTestIdType=\"checkbox-group\"\n [class.tn-checkbox-group--inline]=\"inline()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"fieldAria.describedby()\"\n [attr.aria-invalid]=\"fieldAria.invalid()\"\n [attr.aria-disabled]=\"isDisabled() ? true : null\"\n [tnTestId]=\"resolvedTestId()\"\n (focusout)=\"onFocusOut()\"\n>\n @for (option of options(); track option.value) {\n <tn-checkbox\n class=\"tn-checkbox-group__option\"\n [label]=\"option.label\"\n [checked]=\"isSelected(option.value)\"\n [disabled]=\"isDisabled() || !!option.disabled\"\n [testId]=\"optionTestId(option)\"\n (change)=\"toggle(option.value, $event)\"\n />\n }\n</div>\n\n<!-- The required-ness rides in the group's accessible NAME, because `role=\"group\"` does not\n support `aria-required` \u2014 see the `required` input's docblock. It sits OUTSIDE the group so\n the text is not also read as group content on the way past the options; `aria-labelledby`\n resolves ids document-wide, so it names the group from here just the same. -->\n@if (ariaRequired()) {\n <span class=\"cdk-visually-hidden\" [attr.id]=\"requiredTextId\">required</span>\n}\n", styles: [".tn-checkbox-group-host{display:block}.tn-checkbox-group{display:flex;flex-direction:column;gap:var(--tn-checkbox-group-gap, 8px)}.tn-checkbox-group--inline{flex-direction:row;flex-wrap:wrap;column-gap:var(--tn-checkbox-group-inline-gap, 16px)}.tn-checkbox-group--inline .tn-checkbox-group__option{flex:1 1 calc(var(--tn-checkbox-group-inline-basis, 50%) - var(--tn-checkbox-group-inline-gap, 16px));box-sizing:border-box}@media(max-width:768px){.tn-checkbox-group--inline .tn-checkbox-group__option{flex-basis:100%}}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }, { kind: "directive", type: TnTestIdDirective, selector: "[tnTestId]", inputs: ["tnTestId", "tnTestIdType"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
9978
+ }
9979
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnCheckboxGroupComponent, decorators: [{
9980
+ type: Component,
9981
+ args: [{ selector: 'tn-checkbox-group', standalone: true, imports: [TnCheckboxComponent, TnTestIdDirective], providers: [
9982
+ {
9983
+ provide: NG_VALUE_ACCESSOR,
9984
+ useExisting: forwardRef(() => TnCheckboxGroupComponent),
9985
+ multi: true
9986
+ }
9987
+ ], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
9988
+ 'class': 'tn-checkbox-group-host'
9989
+ }, template: "<div\n class=\"tn-checkbox-group\"\n role=\"group\"\n tnTestIdType=\"checkbox-group\"\n [class.tn-checkbox-group--inline]=\"inline()\"\n [attr.aria-label]=\"resolvedAriaLabel()\"\n [attr.aria-labelledby]=\"labelledBy()\"\n [attr.aria-describedby]=\"fieldAria.describedby()\"\n [attr.aria-invalid]=\"fieldAria.invalid()\"\n [attr.aria-disabled]=\"isDisabled() ? true : null\"\n [tnTestId]=\"resolvedTestId()\"\n (focusout)=\"onFocusOut()\"\n>\n @for (option of options(); track option.value) {\n <tn-checkbox\n class=\"tn-checkbox-group__option\"\n [label]=\"option.label\"\n [checked]=\"isSelected(option.value)\"\n [disabled]=\"isDisabled() || !!option.disabled\"\n [testId]=\"optionTestId(option)\"\n (change)=\"toggle(option.value, $event)\"\n />\n }\n</div>\n\n<!-- The required-ness rides in the group's accessible NAME, because `role=\"group\"` does not\n support `aria-required` \u2014 see the `required` input's docblock. It sits OUTSIDE the group so\n the text is not also read as group content on the way past the options; `aria-labelledby`\n resolves ids document-wide, so it names the group from here just the same. -->\n@if (ariaRequired()) {\n <span class=\"cdk-visually-hidden\" [attr.id]=\"requiredTextId\">required</span>\n}\n", styles: [".tn-checkbox-group-host{display:block}.tn-checkbox-group{display:flex;flex-direction:column;gap:var(--tn-checkbox-group-gap, 8px)}.tn-checkbox-group--inline{flex-direction:row;flex-wrap:wrap;column-gap:var(--tn-checkbox-group-inline-gap, 16px)}.tn-checkbox-group--inline .tn-checkbox-group__option{flex:1 1 calc(var(--tn-checkbox-group-inline-basis, 50%) - var(--tn-checkbox-group-inline-gap, 16px));box-sizing:border-box}@media(max-width:768px){.tn-checkbox-group--inline .tn-checkbox-group__option{flex-basis:100%}}.cdk-visually-hidden{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}\n"] }]
9990
+ }], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", 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 }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], change: [{ type: i0.Output, args: ["change"] }] } });
9991
+
9992
+ /**
9993
+ * Harness for interacting with `tn-checkbox-group` in tests.
9994
+ * Drives the group as a whole — reading the checked set, replacing it wholesale, toggling one
9995
+ * option by label — rather than reaching for each `tn-checkbox` individually.
9996
+ *
9997
+ * @example
9998
+ * ```typescript
9999
+ * const group = await loader.getHarness(TnCheckboxGroupHarness.with({ testId: 'checkbox-group-trains' }));
10000
+ * // Labels, not option values — the DOM only carries what the user can read.
10001
+ * await group.setValue(['Stable', 'Enterprise']);
10002
+ * expect(await group.getValue()).toEqual(['Stable', 'Enterprise']);
10003
+ * ```
10004
+ */
10005
+ class TnCheckboxGroupHarness extends ComponentHarness {
10006
+ /** The selector for the host element of a `TnCheckboxGroupComponent` instance. */
10007
+ static hostSelector = 'tn-checkbox-group';
10008
+ _root = this.locatorFor('.tn-checkbox-group');
10009
+ _checkboxes = this.locatorForAll(TnCheckboxHarness);
10010
+ /**
10011
+ * Gets a `HarnessPredicate` that can be used to search for a checkbox group with specific
10012
+ * attributes.
10013
+ *
10014
+ * @param options Options for filtering which checkbox group instances are considered a match.
10015
+ * @returns A `HarnessPredicate` configured with the given options.
10016
+ *
10017
+ * @example
10018
+ * ```typescript
10019
+ * // Find by accessible name
10020
+ * const group = await loader.getHarness(TnCheckboxGroupHarness.with({ ariaLabel: 'USB Devices' }));
10021
+ *
10022
+ * // Find by testId
10023
+ * const group = await loader.getHarness(TnCheckboxGroupHarness.with({ testId: 'checkbox-group-usb-devices' }));
10024
+ * ```
10025
+ */
10026
+ static with(options = {}) {
10027
+ return new HarnessPredicate(TnCheckboxGroupHarness, options)
10028
+ .addOption('ariaLabel', options.ariaLabel, (harness, ariaLabel) => HarnessPredicate.stringMatches(harness.getAriaLabel(), ariaLabel))
10029
+ .addOption('testId', options.testId, async (harness, testId) => {
10030
+ return (await harness.getTestId()) === testId;
10031
+ });
10032
+ }
10033
+ /**
10034
+ * Gets the harnesses for the group's options, in DOM order.
10035
+ *
10036
+ * @example
10037
+ * ```typescript
10038
+ * const options = await group.getOptions();
10039
+ * expect(options).toHaveLength(3);
10040
+ * ```
10041
+ */
10042
+ async getOptions() {
10043
+ return this._checkboxes();
10044
+ }
10045
+ /**
10046
+ * Gets the label text of every option, in DOM order.
10047
+ *
10048
+ * @example
10049
+ * ```typescript
10050
+ * expect(await group.getOptionLabels()).toEqual(['Stable', 'Enterprise']);
10051
+ * ```
10052
+ */
10053
+ async getOptionLabels() {
10054
+ const checkboxes = await this._checkboxes();
10055
+ return Promise.all(checkboxes.map((checkbox) => checkbox.getLabelText()));
10056
+ }
10057
+ /**
10058
+ * Gets the labels of the currently checked options, in DOM order.
10059
+ *
10060
+ * Labels rather than values, because the values live in the bound control and the DOM only ever
10061
+ * carries what the user can read.
10062
+ *
10063
+ * @example
10064
+ * ```typescript
10065
+ * expect(await group.getValue()).toEqual(['Stable']);
10066
+ * ```
10067
+ */
10068
+ async getValue() {
10069
+ const checkboxes = await this._checkboxes();
10070
+ const checked = [];
10071
+ for (const checkbox of checkboxes) {
10072
+ if (await checkbox.isChecked()) {
10073
+ checked.push(await checkbox.getLabelText());
10074
+ }
10075
+ }
10076
+ return checked;
10077
+ }
10078
+ /**
10079
+ * Makes the checked set exactly the options with the given labels — checking the ones listed and
10080
+ * unchecking every other.
10081
+ *
10082
+ * @param labels Label text of the options that should end up checked.
10083
+ *
10084
+ * @example
10085
+ * ```typescript
10086
+ * await group.setValue(['Stable']);
10087
+ * ```
10088
+ */
10089
+ async setValue(labels) {
10090
+ const checkboxes = await this._checkboxes();
10091
+ for (const checkbox of checkboxes) {
10092
+ const label = await checkbox.getLabelText();
10093
+ if (labels.includes(label)) {
10094
+ await checkbox.check();
10095
+ }
10096
+ else {
10097
+ await checkbox.uncheck();
10098
+ }
10099
+ }
10100
+ }
10101
+ /**
10102
+ * Toggles the option with the given label. Throws when the group has no such option, so a
10103
+ * renamed or missing option fails loudly instead of silently leaving the selection unchanged.
10104
+ *
10105
+ * @param label Label text of the option to toggle.
10106
+ *
10107
+ * @example
10108
+ * ```typescript
10109
+ * await group.toggle('Enterprise');
10110
+ * ```
10111
+ */
10112
+ async toggle(label) {
10113
+ const checkbox = await this.locatorForOptional(TnCheckboxHarness.with({ label }))();
10114
+ if (!checkbox) {
10115
+ const available = (await this.getOptionLabels()).join(', ');
10116
+ throw new Error(`No option labelled "${label}" in this tn-checkbox-group (available: ${available}).`);
10117
+ }
10118
+ await checkbox.toggle();
10119
+ }
10120
+ /**
10121
+ * Checks whether every option is disabled — the state a disabled group or a disabled bound form
10122
+ * control produces. Use {@link getOptions} to inspect a single per-option `disabled`.
10123
+ *
10124
+ * @example
10125
+ * ```typescript
10126
+ * expect(await group.isDisabled()).toBe(true);
10127
+ * ```
10128
+ */
10129
+ async isDisabled() {
10130
+ const checkboxes = await this._checkboxes();
10131
+ if (!checkboxes.length) {
10132
+ return false;
10133
+ }
10134
+ const states = await Promise.all(checkboxes.map((checkbox) => checkbox.isDisabled()));
10135
+ return states.every(Boolean);
10136
+ }
10137
+ /**
10138
+ * Gets the group's accessible name as written by the `ariaLabel` input, or null when the name
10139
+ * comes from an enclosing `tn-form-field` (see {@link getAriaLabelledBy}).
10140
+ */
10141
+ async getAriaLabel() {
10142
+ return (await this._root()).getAttribute('aria-label');
10143
+ }
10144
+ /** Gets the id the group's accessible name is delegated to, or null. */
10145
+ async getAriaLabelledBy() {
10146
+ return (await this._root()).getAttribute('aria-labelledby');
10147
+ }
10148
+ /**
10149
+ * Gets the test ID attribute value of the group root.
10150
+ *
10151
+ * @example
10152
+ * ```typescript
10153
+ * expect(await group.getTestId()).toBe('checkbox-group-trains');
10154
+ * ```
10155
+ */
10156
+ async getTestId() {
10157
+ const root = await this._root();
10158
+ return (await root.getAttribute('data-testid')) ?? (await root.getAttribute('data-test'));
10159
+ }
10160
+ }
10161
+
9765
10162
  /** DI token under which `tn-radio-group` exposes its {@link TnRadioGroupApi} to child radios. */
9766
10163
  const TN_RADIO_GROUP = new InjectionToken('TN_RADIO_GROUP');
9767
10164
 
@@ -28219,5 +28616,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImpor
28219
28616
  * Generated bundle index. Do not edit.
28220
28617
  */
28221
28618
 
28222
- 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_DEFAULT_FALLBACK_LABELS, TN_DIALOG_CHROME_LABELS, TN_DIALOG_DEFAULT_CHROME_LABELS, TN_DIALOG_SHELL_DEFAULT_LABEL, TN_DRAWER_CONTENT_LABEL, TN_DRAWER_DEFAULT_LABEL, TN_FALLBACK_LABELS, 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, injectTnFallbackName, injectTnFormFieldAria, injectTnLabels, isPathWithinRoot, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, normalizeRootPath, optionTestId, parseLabelMarkup, parseSize, parseSizeParts, registerLucideIcons, resolveErrorMessage, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
28619
+ 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_DEFAULT_FALLBACK_LABELS, TN_DIALOG_CHROME_LABELS, TN_DIALOG_DEFAULT_CHROME_LABELS, TN_DIALOG_SHELL_DEFAULT_LABEL, TN_DRAWER_CONTENT_LABEL, TN_DRAWER_DEFAULT_LABEL, TN_FALLBACK_LABELS, 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, TnCheckboxGroupComponent, TnCheckboxGroupHarness, 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, injectTnFallbackName, injectTnFormFieldAria, injectTnLabels, isPathWithinRoot, kebabTestSegment, labelMarkupToHtml, labelMarkupToText, libIconMarker, normalizeRootPath, optionTestId, parseLabelMarkup, parseSize, parseSizeParts, registerLucideIcons, resolveErrorMessage, scopeTestId, setupLucideIntegration, tnIconMarker, writeTestId };
28223
28620
  //# sourceMappingURL=truenas-ui-components.mjs.map