@truenas/ui-components 0.1.31 → 0.1.32

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.
@@ -4333,10 +4333,15 @@ class TnSelectComponent {
4333
4333
  placeholder = input('Select an option', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
4334
4334
  disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
4335
4335
  testId = input('', ...(ngDevMode ? [{ debugName: "testId" }] : []));
4336
+ multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : []));
4337
+ compareWith = input(...(ngDevMode ? [undefined, { debugName: "compareWith" }] : []));
4336
4338
  selectionChange = output();
4339
+ /** Emits the full array of selected values after each toggle in multiple mode. */
4340
+ multiSelectionChange = output();
4337
4341
  // Internal state signals
4338
4342
  isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
4339
4343
  selectedValue = signal(null, ...(ngDevMode ? [{ debugName: "selectedValue" }] : []));
4344
+ selectedValues = signal([], ...(ngDevMode ? [{ debugName: "selectedValues" }] : []));
4340
4345
  formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : []));
4341
4346
  // Computed disabled state (combines input and form state)
4342
4347
  isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
@@ -4367,7 +4372,17 @@ class TnSelectComponent {
4367
4372
  }
4368
4373
  // ControlValueAccessor implementation
4369
4374
  writeValue(value) {
4370
- this.selectedValue.set(value);
4375
+ if (this.multiple()) {
4376
+ if (Array.isArray(value)) {
4377
+ this.selectedValues.set(value);
4378
+ }
4379
+ else {
4380
+ this.selectedValues.set(value != null ? [value] : []);
4381
+ }
4382
+ }
4383
+ else {
4384
+ this.selectedValue.set(Array.isArray(value) ? value[0] ?? null : value);
4385
+ }
4371
4386
  }
4372
4387
  registerOnChange(fn) {
4373
4388
  this.onChange = fn;
@@ -4392,8 +4407,16 @@ class TnSelectComponent {
4392
4407
  this.isOpen.set(false);
4393
4408
  this.onTouched();
4394
4409
  }
4395
- onOptionClick(option) {
4396
- this.selectOption(option);
4410
+ onOptionClick(option, groupDisabled = false) {
4411
+ if (option.disabled || groupDisabled) {
4412
+ return;
4413
+ }
4414
+ if (this.multiple()) {
4415
+ this.toggleOption(option);
4416
+ }
4417
+ else {
4418
+ this.selectOption(option);
4419
+ }
4397
4420
  }
4398
4421
  selectOption(option) {
4399
4422
  if (option.disabled) {
@@ -4403,19 +4426,46 @@ class TnSelectComponent {
4403
4426
  this.onChange(option.value);
4404
4427
  this.selectionChange.emit(option.value);
4405
4428
  this.closeDropdown();
4406
- this.cdr.markForCheck(); // Trigger change detection
4429
+ this.cdr.markForCheck();
4430
+ }
4431
+ toggleOption(option) {
4432
+ const current = this.selectedValues();
4433
+ const index = current.findIndex(v => this.compareValues(v, option.value));
4434
+ let updated;
4435
+ if (index >= 0) {
4436
+ updated = current.filter((_, i) => i !== index);
4437
+ }
4438
+ else {
4439
+ updated = [...current, option.value];
4440
+ }
4441
+ this.selectedValues.set(updated);
4442
+ this.onChange(updated);
4443
+ this.multiSelectionChange.emit(updated);
4444
+ this.cdr.markForCheck();
4407
4445
  }
4408
- // Computed properties
4409
- isSelected = computed(() => (option) => {
4446
+ isOptionSelected(option) {
4447
+ if (this.multiple()) {
4448
+ return this.selectedValues().some(v => this.compareValues(v, option.value));
4449
+ }
4410
4450
  return this.compareValues(this.selectedValue(), option.value);
4411
- }, ...(ngDevMode ? [{ debugName: "isSelected" }] : []));
4451
+ }
4412
4452
  getDisplayText = computed(() => {
4453
+ if (this.multiple()) {
4454
+ const values = this.selectedValues();
4455
+ if (values.length === 0) {
4456
+ return this.placeholder();
4457
+ }
4458
+ const labels = values
4459
+ .map(v => this.findOptionByValue(v)?.label ?? String(v))
4460
+ .filter(Boolean);
4461
+ return labels.join(', ');
4462
+ }
4413
4463
  const value = this.selectedValue();
4414
4464
  if (value === null || value === undefined) {
4415
4465
  return this.placeholder();
4416
4466
  }
4417
4467
  const option = this.findOptionByValue(value);
4418
- return option ? option.label : value;
4468
+ return option ? option.label : String(value);
4419
4469
  }, ...(ngDevMode ? [{ debugName: "getDisplayText" }] : []));
4420
4470
  findOptionByValue(value) {
4421
4471
  // Search in regular options first
@@ -4436,6 +4486,10 @@ class TnSelectComponent {
4436
4486
  return this.options().length > 0 || this.optionGroups().length > 0;
4437
4487
  }, ...(ngDevMode ? [{ debugName: "hasAnyOptions" }] : []));
4438
4488
  compareValues(a, b) {
4489
+ const customCompare = this.compareWith();
4490
+ if (customCompare) {
4491
+ return customCompare(a, b);
4492
+ }
4439
4493
  if (a === b) {
4440
4494
  return true;
4441
4495
  }
@@ -4445,6 +4499,8 @@ class TnSelectComponent {
4445
4499
  return false;
4446
4500
  }
4447
4501
  // Keyboard navigation
4502
+ // TODO: Add ArrowUp/ArrowDown option navigation, Enter/Space toggle,
4503
+ // and aria-activedescendant tracking for full keyboard accessibility.
4448
4504
  onKeydown(event) {
4449
4505
  switch (event.key) {
4450
4506
  case 'Enter':
@@ -4469,24 +4525,24 @@ class TnSelectComponent {
4469
4525
  }
4470
4526
  }
4471
4527
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4472
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, providers: [
4528
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.0", type: TnSelectComponent, isStandalone: true, selector: "tn-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, optionGroups: { classPropertyName: "optionGroups", publicName: "optionGroups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, testId: { classPropertyName: "testId", publicName: "testId", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange", multiSelectionChange: "multiSelectionChange" }, providers: [
4473
4529
  {
4474
4530
  provide: NG_VALUE_ACCESSOR,
4475
4531
  useExisting: forwardRef(() => TnSelectComponent),
4476
4532
  multi: true
4477
4533
  }
4478
- ], ngImport: i0, template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"selectedValue() === null || selectedValue() === undefined\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"isSelected()(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isSelected()(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled || group.disabled ? null : 0\"\n [class.selected]=\"isSelected()(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isSelected()(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n\n {{ option.label }}\n </div>\n }\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n No options available\n </div>\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:200px}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option:hover:not(.disabled){background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa)!important;color:var(--tn-fg1, #212529)}.tn-select-option.selected:hover{background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"] });
4534
+ ], ngImport: i0, template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + testId() + '-' + $index\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + testId() + '-' + $index\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled || group.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.enter)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.space)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n No options available\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:200px}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option:hover:not(.disabled){background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa)!important;color:var(--tn-fg1, #212529)}.tn-select-option.selected:hover{background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"], dependencies: [{ kind: "component", type: TnCheckboxComponent, selector: "tn-checkbox", inputs: ["label", "hideLabel", "disabled", "required", "indeterminate", "testId", "error", "checked"], outputs: ["change"] }] });
4479
4535
  }
4480
4536
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnSelectComponent, decorators: [{
4481
4537
  type: Component,
4482
- args: [{ selector: 'tn-select', standalone: true, imports: [], providers: [
4538
+ args: [{ selector: 'tn-select', standalone: true, imports: [TnCheckboxComponent], providers: [
4483
4539
  {
4484
4540
  provide: NG_VALUE_ACCESSOR,
4485
4541
  useExisting: forwardRef(() => TnSelectComponent),
4486
4542
  multi: true
4487
4543
  }
4488
- ], template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"selectedValue() === null || selectedValue() === undefined\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"isSelected()(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isSelected()(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled || group.disabled ? null : 0\"\n [class.selected]=\"isSelected()(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isSelected()(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n\n {{ option.label }}\n </div>\n }\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n No options available\n </div>\n }\n </div>\n </div>\n }\n</div>", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:200px}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option:hover:not(.disabled){background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa)!important;color:var(--tn-fg1, #212529)}.tn-select-option.selected:hover{background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"] }]
4489
- }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }] } });
4544
+ ], template: "<div class=\"tn-select-container\" [attr.data-testid]=\"testId()\">\n <!-- Select Trigger -->\n <div\n class=\"tn-select-trigger\"\n role=\"combobox\"\n tabindex=\"0\"\n [class.disabled]=\"isDisabled()\"\n [class.open]=\"isOpen()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-haspopup]=\"true\"\n [attr.aria-controls]=\"isOpen() ? 'tn-select-dropdown-' + testId() : null\"\n [attr.aria-label]=\"placeholder()\"\n (click)=\"toggleDropdown()\"\n (keydown)=\"onKeydown($event)\">\n\n <!-- Display Text -->\n <span\n class=\"tn-select-text\"\n [class.placeholder]=\"multiple() ? selectedValues().length === 0 : (selectedValue() === null || selectedValue() === undefined)\">\n {{ getDisplayText() }}\n </span>\n\n <!-- Dropdown Arrow -->\n <div class=\"tn-select-arrow\" [class.open]=\"isOpen()\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <polyline points=\"6,9 12,15 18,9\" />\n </svg>\n </div>\n </div>\n\n <!-- Dropdown Menu -->\n @if (isOpen()) {\n <div\n class=\"tn-select-dropdown\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.id]=\"'tn-select-dropdown-' + testId()\">\n\n <!-- Options List -->\n <div class=\"tn-select-options\">\n <!-- Regular Options -->\n @for (option of options(); track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option)\"\n (keydown.enter)=\"onOptionClick(option)\"\n (keydown.space)=\"onOptionClick(option)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n\n <!-- Option Groups -->\n @for (group of optionGroups(); track $index; let isFirst = $first) {\n <!-- Group Separator (not shown before first group if we have regular options) -->\n @if (!isFirst || options().length > 0) {\n <div\n class=\"tn-select-separator\"\n role=\"separator\">\n </div>\n }\n\n <div role=\"group\" [attr.aria-labelledby]=\"'tn-select-group-' + testId() + '-' + $index\">\n <!-- Group Label -->\n <div\n class=\"tn-select-group-label\"\n [attr.id]=\"'tn-select-group-' + testId() + '-' + $index\"\n [class.disabled]=\"group.disabled\">\n {{ group.label }}\n </div>\n\n <!-- Group Options -->\n @for (option of group.options; track $index) {\n <div\n class=\"tn-select-option\"\n role=\"option\"\n [attr.tabindex]=\"option.disabled || group.disabled ? null : 0\"\n [class.selected]=\"isOptionSelected(option)\"\n [class.disabled]=\"option.disabled || group.disabled\"\n [attr.aria-selected]=\"isOptionSelected(option)\"\n [attr.aria-disabled]=\"option.disabled || group.disabled\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.enter)=\"onOptionClick(option, !!group.disabled)\"\n (keydown.space)=\"onOptionClick(option, !!group.disabled)\">\n @if (multiple()) {\n <tn-checkbox\n class=\"tn-select-check\"\n label=\"\"\n [checked]=\"isOptionSelected(option)\"\n [disabled]=\"true\"\n [hideLabel]=\"true\" />\n }\n {{ option.label }}\n </div>\n }\n </div>\n }\n\n <!-- No Options Message -->\n @if (!hasAnyOptions()) {\n <div class=\"tn-select-no-options\">\n No options available\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".tn-select-container{position:relative;width:100%;font-family:var(--tn-font-family-body, \"Inter\"),sans-serif}.tn-select-label{display:block;margin-bottom:.5rem;font-size:.875rem;font-weight:500;color:var(--tn-fg1, #333);line-height:1.4}.tn-select-label.required .required-asterisk{color:var(--tn-error, #dc3545);margin-left:.25rem}.tn-select-trigger{position:relative;display:flex;align-items:center;min-height:2.5rem;padding:.5rem 2.5rem .5rem .75rem;background-color:var(--tn-bg1, #ffffff);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;cursor:pointer;transition:all .15s ease-in-out;outline:none;box-sizing:border-box}.tn-select-trigger:hover:not(.disabled){border-color:var(--tn-primary, #007bff)}.tn-select-trigger:focus-visible{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.open{border-color:var(--tn-primary, #007bff);box-shadow:0 0 0 2px #007bff40}.tn-select-trigger.error{border-color:var(--tn-error, #dc3545)}.tn-select-trigger.error:focus-visible,.tn-select-trigger.error.open{border-color:var(--tn-error, #dc3545);box-shadow:0 0 0 2px #dc354540}.tn-select-trigger.disabled{background-color:var(--tn-alt-bg1, #f8f9fa);color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-text{flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--tn-fg1, #212529)}.tn-select-text.placeholder{color:var(--tn-alt-fg1, #999)}.tn-select-arrow{position:absolute;right:.75rem;top:50%;transform:translateY(-50%);color:var(--tn-fg2, #6c757d);transition:transform .15s ease-in-out;pointer-events:none}.tn-select-arrow.open{transform:translateY(-50%) rotate(180deg)}.tn-select-arrow svg{display:block}.tn-select-dropdown{position:absolute;top:100%;left:0;right:0;z-index:1000;margin-top:.25rem;background-color:var(--tn-bg2, #f5f5f5);border:1px solid var(--tn-lines, #d1d5db);border-radius:.375rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;max-height:200px;overflow:hidden}.tn-select-options{overflow-y:auto;padding:.25rem 0;max-height:200px}.tn-select-option{display:flex;align-items:center;padding:.5rem .75rem;cursor:pointer;color:var(--tn-fg1, #212529);transition:background-color .15s ease-in-out;pointer-events:auto;position:relative;z-index:1001}.tn-select-option:hover:not(.disabled){background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.selected{background-color:var(--tn-alt-bg1, #f8f9fa)!important;color:var(--tn-fg1, #212529)}.tn-select-option.selected:hover{background-color:var(--tn-alt-bg2, #f8f9fa)!important}.tn-select-option.disabled{color:var(--tn-fg2, #6c757d);cursor:not-allowed;opacity:.6}.tn-select-check{margin-right:.5rem;flex-shrink:0;pointer-events:none}.tn-select-separator{height:1px;background:var(--tn-lines, #e0e0e0);margin:.25rem 0}.tn-select-group-label{padding:.375rem .75rem;font-size:.75rem;font-weight:600;color:var(--tn-alt-fg1, #9ca3af);text-transform:uppercase;letter-spacing:.05em;cursor:default;-webkit-user-select:none;user-select:none}.tn-select-group-label.disabled{opacity:.6}.tn-select-no-options{padding:1rem .75rem;text-align:center;color:var(--tn-fg2, #6c757d);font-style:italic}.tn-select-error{margin-top:.25rem;font-size:.75rem;color:var(--tn-error, #dc3545)}@media(prefers-reduced-motion:reduce){.tn-select-trigger,.tn-select-option,.tn-select-arrow{transition:none}}@media(prefers-contrast:high){.tn-select-trigger{border-width:2px}.tn-select-option.selected{outline:2px solid var(--tn-fg1, #000);outline-offset:-2px}}\n"] }]
4545
+ }], ctorParameters: () => [], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], optionGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionGroups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], testId: [{ type: i0.Input, args: [{ isSignal: true, alias: "testId", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], multiSelectionChange: [{ type: i0.Output, args: ["multiSelectionChange"] }] } });
4490
4546
 
4491
4547
  /**
4492
4548
  * Harness for interacting with tn-select in tests.
@@ -7890,7 +7946,7 @@ class TnTimeInputComponent {
7890
7946
  useExisting: forwardRef(() => TnTimeInputComponent),
7891
7947
  multi: true
7892
7948
  }
7893
- ], ngImport: i0, template: "<tn-select\n [options]=\"timeSelectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"testId()\"\n [ngModel]=\"_value\"\n (selectionChange)=\"onSelectionChange($event)\" />\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "disabled", "testId"], outputs: ["selectionChange"] }] });
7949
+ ], ngImport: i0, template: "<tn-select\n [options]=\"timeSelectOptions()\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"isDisabled()\"\n [testId]=\"testId()\"\n [ngModel]=\"_value\"\n (selectionChange)=\"onSelectionChange($event)\" />\n", styles: [":host{display:block;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: TnSelectComponent, selector: "tn-select", inputs: ["options", "optionGroups", "placeholder", "disabled", "testId", "multiple", "compareWith"], outputs: ["selectionChange", "multiSelectionChange"] }] });
7894
7950
  }
7895
7951
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.0", ngImport: i0, type: TnTimeInputComponent, decorators: [{
7896
7952
  type: Component,