@haloduck/ui 2.0.18 → 2.0.20

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.
@@ -7,17 +7,17 @@ import { BehaviorSubject, zip, Subject, takeUntil, tap, combineLatest, switchMap
7
7
  import { ulid } from 'ulid';
8
8
  import * as i1 from '@angular/common';
9
9
  import { CommonModule, DecimalPipe, DatePipe } from '@angular/common';
10
- import * as i1$2 from '@jsverse/transloco';
10
+ import * as i2$1 from '@jsverse/transloco';
11
11
  import { provideTranslocoScope, TranslocoModule, TranslocoService } from '@jsverse/transloco';
12
12
  import * as i2 from '@angular/common/http';
13
- import * as i1$3 from '@angular/cdk/overlay';
13
+ import * as i1$2 from '@angular/cdk/overlay';
14
14
  import { Overlay } from '@angular/cdk/overlay';
15
15
  import { ComponentPortal } from '@angular/cdk/portal';
16
16
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
17
17
  import { CoreService } from '@haloduck/core';
18
- import * as i1$5 from '@angular/router';
18
+ import * as i1$4 from '@angular/router';
19
19
  import { Router, NavigationEnd, RouterLink } from '@angular/router';
20
- import * as i1$4 from '@angular/platform-browser';
20
+ import * as i1$3 from '@angular/platform-browser';
21
21
  import { Title } from '@angular/platform-browser';
22
22
  import * as THREE from 'three';
23
23
  import { STLLoader, OrbitControls } from 'three-stdlib';
@@ -342,6 +342,8 @@ class SelectDropdownComponent {
342
342
  _filteredOptions = signal([], ...(ngDevMode ? [{ debugName: "_filteredOptions" }] : []));
343
343
  _options = [];
344
344
  _selectedOptionIds = [];
345
+ manualInputValues = {};
346
+ activeManualKey = null;
345
347
  selectedChange = new EventEmitter();
346
348
  useFilter = false;
347
349
  multiselect = true;
@@ -350,57 +352,221 @@ class SelectDropdownComponent {
350
352
  set options(value) {
351
353
  this._options = value || [];
352
354
  this._filteredOptions.set(this._options);
355
+ this.seedManualInputs();
353
356
  }
354
357
  get options() {
355
358
  return this._filteredOptions();
356
359
  }
357
360
  set selectedOptionIds(value) {
358
361
  this._selectedOptionIds = value || [];
362
+ this.seedManualInputs();
359
363
  }
360
364
  get selectedOptions() {
361
365
  return this._selectedOptionIds;
362
366
  }
363
367
  onFilterInput(event) {
364
368
  const value = event.target.value.toLowerCase();
365
- this._filteredOptions.set(this._options.filter((opt) => opt.value.toLowerCase().includes(value)));
369
+ this._filteredOptions.set(this._options.filter((opt) => opt.value.toLowerCase().includes(value) || opt.isSticky === true));
366
370
  }
367
371
  onToggleOption(option) {
368
372
  if (this.asButton) {
369
- this._selectedOptionIds = [option.id || option.value];
373
+ this.setSelected([option.id || option.value]);
374
+ return;
370
375
  }
371
- else {
372
- if (this.multiselect) {
373
- if (!this._selectedOptionIds.includes(option.id || option.value)) {
374
- this._selectedOptionIds.push(option.id || option.value);
376
+ // Handle manual input option
377
+ if (option.shouldManualInput) {
378
+ const key = this.getManualKey(option);
379
+ const prefix = option.manualPrefix || '';
380
+ const typed = (this.manualInputValues[key] || '').trim();
381
+ const isSelected = this.isOptionSelected(option);
382
+ if (isSelected) {
383
+ // Deselect current manual value
384
+ if (this.multiselect) {
385
+ let next;
386
+ if (prefix) {
387
+ next = this._selectedOptionIds.filter((id) => !id.startsWith(prefix));
388
+ }
389
+ else {
390
+ next = this._selectedOptionIds.filter((id) => id !== typed);
391
+ }
392
+ this.setSelected(next);
375
393
  }
376
394
  else {
377
- this._selectedOptionIds.splice(this._selectedOptionIds.indexOf(option.id || option.value), 1);
395
+ this.setSelected([]);
378
396
  }
397
+ // Clear input text when deselected
398
+ this.manualInputValues[key] = '';
399
+ // Hide input
400
+ this.activeManualKey = null;
379
401
  }
380
402
  else {
381
- if (this.atLeastOne) {
382
- this._selectedOptionIds = [option.id || option.value];
403
+ // open manual input without changing selection yet
404
+ this.activeManualKey = key;
405
+ // ensure there is an entry in manualInputValues
406
+ if (this.manualInputValues[key] === undefined) {
407
+ this.manualInputValues[key] = '';
408
+ }
409
+ // focus input on next tick
410
+ setTimeout(() => {
411
+ const selector = `input[data-manual-key="${key}"]`;
412
+ const el = document.querySelector(selector);
413
+ el?.focus();
414
+ el?.select();
415
+ });
416
+ }
417
+ return;
418
+ }
419
+ // Non-manual options
420
+ if (this.multiselect) {
421
+ const id = option.id || option.value;
422
+ if (option.isExclusive) {
423
+ if (this._selectedOptionIds.includes(id)) {
424
+ this.setSelected([]);
383
425
  }
384
426
  else {
385
- if (this._selectedOptionIds.includes(option.id || option.value)) {
386
- this._selectedOptionIds = [];
387
- }
388
- else {
389
- this._selectedOptionIds = [option.id || option.value];
427
+ this.setSelected([id]);
428
+ }
429
+ }
430
+ else {
431
+ // remove any exclusive selections
432
+ let next = this._selectedOptionIds.filter((selectedId) => !this.isIdExclusive(selectedId));
433
+ if (!next.includes(id)) {
434
+ next = [id, ...next];
435
+ }
436
+ else {
437
+ next = next.filter((x) => x !== id);
438
+ }
439
+ this.setSelected(next);
440
+ }
441
+ }
442
+ else {
443
+ const id = option.id || option.value;
444
+ if (this.atLeastOne) {
445
+ this.setSelected([id]);
446
+ }
447
+ else {
448
+ if (this._selectedOptionIds.includes(id)) {
449
+ this.setSelected([]);
450
+ }
451
+ else {
452
+ this.setSelected([id]);
453
+ }
454
+ }
455
+ }
456
+ }
457
+ isOptionSelected(option) {
458
+ const id = option.id || option.value;
459
+ if (option.shouldManualInput) {
460
+ const prefix = option.manualPrefix;
461
+ if (prefix) {
462
+ // selected only if there exists a value with more than prefix
463
+ return this._selectedOptionIds.some((selectedId) => typeof selectedId === 'string' && selectedId.startsWith(prefix) && selectedId.length > prefix.length);
464
+ }
465
+ // no prefix: consider selected if any selected id equals current typed value (non-empty)
466
+ const key = this.getManualKey(option);
467
+ const typed = (this.manualInputValues[key] || '').trim();
468
+ return typed !== '' && this._selectedOptionIds.includes(typed);
469
+ }
470
+ return this._selectedOptionIds.includes(id);
471
+ }
472
+ isIdExclusive(id) {
473
+ for (const o of this._options) {
474
+ if (!o.isExclusive)
475
+ continue;
476
+ const refId = o.id || o.value;
477
+ if (refId === id)
478
+ return true;
479
+ if (o.shouldManualInput && o.manualPrefix && id?.startsWith(o.manualPrefix)) {
480
+ return true;
481
+ }
482
+ }
483
+ return false;
484
+ }
485
+ getManualKey(option) {
486
+ return option.manualPrefix || (option.id || option.value);
487
+ }
488
+ seedManualInputs() {
489
+ const manualOptions = this._options.filter((o) => o.shouldManualInput);
490
+ for (const opt of manualOptions) {
491
+ const prefix = opt.manualPrefix || '';
492
+ const key = this.getManualKey(opt);
493
+ const existingWithPrefix = this._selectedOptionIds.find((id) => prefix ? id?.startsWith(prefix) : false);
494
+ const value = existingWithPrefix
495
+ ? existingWithPrefix.substring(prefix.length)
496
+ : '';
497
+ this.manualInputValues[key] = value;
498
+ }
499
+ }
500
+ onManualInputChange(option, value) {
501
+ const key = this.getManualKey(option);
502
+ this.manualInputValues[key] = value;
503
+ // Dynamically reflect selection as user types, only when non-empty
504
+ const prefix = option.manualPrefix || '';
505
+ const sentinel = option.id || option.value;
506
+ const trimmed = (value || '').trim();
507
+ if (this.multiselect) {
508
+ if (option.isExclusive) {
509
+ if (trimmed === '') {
510
+ this.setSelected([]);
511
+ }
512
+ else {
513
+ this.setSelected([`${prefix}${trimmed}`]);
514
+ }
515
+ }
516
+ else {
517
+ let next = this._selectedOptionIds.filter((id) => !this.isIdExclusive(id));
518
+ // remove same prefix or sentinel
519
+ next = next.filter((id) => (prefix ? !id.startsWith(prefix) : id !== sentinel));
520
+ if (trimmed !== '') {
521
+ const toAdd = `${prefix}${trimmed}`;
522
+ if (!next.includes(toAdd)) {
523
+ next = [toAdd, ...next];
390
524
  }
391
525
  }
526
+ this.setSelected(next);
392
527
  }
393
528
  }
394
- // prevent [null]
395
- this._selectedOptionIds = this._selectedOptionIds.filter((id) => id !== null);
396
- this.selectedChange.emit(this._selectedOptionIds);
529
+ else {
530
+ // single select
531
+ if (trimmed === '') {
532
+ this.setSelected([]);
533
+ }
534
+ else {
535
+ this.setSelected([`${prefix}${trimmed}`]);
536
+ }
537
+ }
538
+ }
539
+ onManualInputConfirm(option) {
540
+ const key = this.getManualKey(option);
541
+ const raw = (this.manualInputValues[key] || '').trim();
542
+ // Use same logic as onManualInputChange
543
+ this.onManualInputChange(option, raw);
544
+ }
545
+ onManualInputBlur(option) {
546
+ const key = this.getManualKey(option);
547
+ const raw = (this.manualInputValues[key] || '').trim();
548
+ if (raw === '') {
549
+ // hide input on blur when empty
550
+ this.activeManualKey = null;
551
+ }
552
+ }
553
+ emitSelectedChange() {
554
+ const payload = [...this._selectedOptionIds];
555
+ setTimeout(() => this.selectedChange.emit(payload));
556
+ }
557
+ setSelected(next) {
558
+ const sanitized = next.filter((id) => id != null);
559
+ setTimeout(() => {
560
+ this._selectedOptionIds = sanitized;
561
+ this.emitSelectedChange();
562
+ });
397
563
  }
398
564
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
399
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectDropdownComponent, isStandalone: true, selector: "haloduck-select-dropdown", inputs: { useFilter: "useFilter", multiselect: "multiselect", atLeastOne: "atLeastOne", asButton: "asButton", options: "options", selectedOptionIds: "selectedOptionIds" }, outputs: { selectedChange: "selectedChange" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 cursor-pointer hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( _selectedOptionIds.includes(option.id || option.value)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n {{ option.value }}\n </div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
565
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectDropdownComponent, isStandalone: true, selector: "haloduck-select-dropdown", inputs: { useFilter: "useFilter", multiselect: "multiselect", atLeastOne: "atLeastOne", asButton: "asButton", options: "options", selectedOptionIds: "selectedOptionIds" }, outputs: { selectedChange: "selectedChange" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n [class.cursor-pointer]=\"!option.shouldManualInput\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( isOptionSelected(option)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n @if(option.shouldManualInput && (isOptionSelected(option) || (activeManualKey === (option.manualPrefix ? option.manualPrefix : (option.id || option.value))))) {\n <input type=\"text\"\n [(ngModel)]=\"manualInputValues[option.manualPrefix ? option.manualPrefix : (option.id || option.value)]\"\n [attr.data-manual-key]=\"option.manualPrefix ? option.manualPrefix : (option.id || option.value)\"\n (ngModelChange)=\"onManualInputChange(option, $event)\"\n (click)=\"$event.stopPropagation()\"\n (blur)=\"onManualInputBlur(option)\"\n (keydown.enter)=\"onManualInputConfirm(option)\"\n class=\"text-light-on-control dark:text-dark-on-control rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-2 py-1 text-sm/6 bg-light-control dark:bg-dark-control w-full\" />\n } @else {\n {{ option.value }}\n }\n </div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
400
566
  }
401
567
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectDropdownComponent, decorators: [{
402
568
  type: Component,
403
- args: [{ selector: 'haloduck-select-dropdown', imports: [TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 cursor-pointer hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( _selectedOptionIds.includes(option.id || option.value)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n {{ option.value }}\n </div>\n }\n </div>\n</div>\n" }]
569
+ args: [{ selector: 'haloduck-select-dropdown', imports: [TranslocoModule, FormsModule], providers: [provideTranslocoScope('haloduck')], template: "<div id=\"dropdown\"\n class=\"max-w-full mt-2 absolute z-40 bg-light-background dark:bg-dark-background text-light-on-background dark:text-dark-on-background border border-light-inactive dark:border-dark-inactive rounded max-h-60 flex flex-col gap-2\">\n @if (useFilter && _options.length >= 5) {\n <input #inputFilter\n id=\"inputFilter\"\n type=\"text\"\n [placeholder]=\"'haloduck.ui.select.Keyword...' | transloco\"\n (input)=\"onFilterInput($event)\"\n class=\"text-light-inactive dark:text-dark-inactive rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-3 py-1.5 text-sm/6 bg-light-control dark:bg-dark-control m-2\" />\n }\n <div class=\"overflow-y-auto\">\n @for ( option of _filteredOptions(); track (option.id) ? option.id : option.value) {\n <div class=\"px-3 py-2 text-sm/6 hover:bg-light-secondary/60 dark:hover:bg-dark-secondary/60 flex items-center justify-start whitespace-nowrap\"\n [class.cursor-pointer]=\"!option.shouldManualInput\"\n (click)=\"onToggleOption(option)\">\n @if(!asButton) {\n @if( isOptionSelected(option)) {\n <svg class=\"w-4 h-4 text-light-primary dark:text-dark-primary inline-block mr-2\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M16.707 5.293a1 1 0 00-1.414 0L8 12.586 4.707 9.293a1 1 0 00-1.414 1.414l4 4a1 1 0 001.414 0l8-8a1 1 0 000-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <div class=\"w-4 h-4 inline-block mr-2\"></div>\n }\n }\n @if(option.shouldManualInput && (isOptionSelected(option) || (activeManualKey === (option.manualPrefix ? option.manualPrefix : (option.id || option.value))))) {\n <input type=\"text\"\n [(ngModel)]=\"manualInputValues[option.manualPrefix ? option.manualPrefix : (option.id || option.value)]\"\n [attr.data-manual-key]=\"option.manualPrefix ? option.manualPrefix : (option.id || option.value)\"\n (ngModelChange)=\"onManualInputChange(option, $event)\"\n (click)=\"$event.stopPropagation()\"\n (blur)=\"onManualInputBlur(option)\"\n (keydown.enter)=\"onManualInputConfirm(option)\"\n class=\"text-light-on-control dark:text-dark-on-control rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary px-2 py-1 text-sm/6 bg-light-control dark:bg-dark-control w-full\" />\n } @else {\n {{ option.value }}\n }\n </div>\n }\n </div>\n</div>\n" }]
404
570
  }], propDecorators: { selectedChange: [{
405
571
  type: Output
406
572
  }], useFilter: [{
@@ -421,6 +587,7 @@ class SelectComponent {
421
587
  translateService = inject(TranslocoService);
422
588
  overlay = inject(Overlay);
423
589
  viewContainerRef = inject(ViewContainerRef);
590
+ cdr = inject(ChangeDetectorRef);
424
591
  overlayRef = null;
425
592
  selectedChange = new EventEmitter();
426
593
  disabled = false;
@@ -455,12 +622,8 @@ class SelectComponent {
455
622
  label;
456
623
  isDropdownOpen = signal(false, ...(ngDevMode ? [{ debugName: "isDropdownOpen" }] : []));
457
624
  selectedOptionIds = [];
458
- get selectedOptions() {
459
- if (this.options) {
460
- return this.options.filter((option) => this.selectedOptionIds.includes(option.id || option.value));
461
- }
462
- return [];
463
- }
625
+ displayedSelectedOptions = [];
626
+ selectedIdToDisplayOption = {};
464
627
  onClick(event) {
465
628
  if (this.disabled) {
466
629
  return;
@@ -505,13 +668,22 @@ class SelectComponent {
505
668
  componentRef.instance.atLeastOne = this.atLeastOne;
506
669
  componentRef.instance.asButton = this.asButton;
507
670
  componentRef.instance.selectedChange.subscribe((selectedOptionIds) => {
508
- this.selectedChange.emit(selectedOptionIds);
509
- this.selectedOptionIds = selectedOptionIds;
510
- this.onChange(this.getSelectedValue());
511
- if (!this.multiselect) {
512
- this.isDropdownOpen.set(false);
513
- this.overlayRef?.detach();
514
- }
671
+ const payload = Array.isArray(selectedOptionIds)
672
+ ? [...selectedOptionIds]
673
+ : selectedOptionIds;
674
+ setTimeout(() => {
675
+ this.selectedOptionIds = payload;
676
+ this.recomputeSelectedOptions();
677
+ this.onChange(this.getSelectedValue());
678
+ this.selectedChange.emit(payload);
679
+ this.cdr.markForCheck();
680
+ // Keep dropdown open if a manual-input option is selected (to allow typing)
681
+ const shouldKeepOpenForManual = this.isManualSelection(this.selectedOptionIds);
682
+ if (!this.multiselect && !shouldKeepOpenForManual) {
683
+ this.isDropdownOpen.set(false);
684
+ this.overlayRef?.detach();
685
+ }
686
+ });
515
687
  });
516
688
  this.isDropdownOpen.set(true);
517
689
  }
@@ -534,6 +706,7 @@ class SelectComponent {
534
706
  event.stopPropagation();
535
707
  event.preventDefault();
536
708
  this.selectedOptionIds = this.selectedOptionIds.filter((selected) => selected !== (option.id || option.value));
709
+ this.recomputeSelectedOptions();
537
710
  this.onChange(this.getSelectedValue());
538
711
  this.selectedChange.emit(this.selectedOptionIds);
539
712
  this.isDropdownOpen.set(false);
@@ -555,11 +728,12 @@ class SelectComponent {
555
728
  onTouched = () => { };
556
729
  writeValue(value) {
557
730
  if (Array.isArray(value)) {
558
- this.selectedOptionIds = value;
731
+ this.selectedOptionIds = value.filter(id => id !== null && id !== undefined && id !== '');
559
732
  }
560
733
  else {
561
- this.selectedOptionIds = [value];
734
+ this.selectedOptionIds = (value !== null && value !== undefined && value !== '') ? [value] : [];
562
735
  }
736
+ this.recomputeSelectedOptions();
563
737
  }
564
738
  registerOnChange(fn) {
565
739
  this.onChange = fn;
@@ -588,6 +762,105 @@ class SelectComponent {
588
762
  }
589
763
  });
590
764
  }
765
+ ngOnChanges(changes) {
766
+ if (changes['options']) {
767
+ this.recomputeSelectedOptions();
768
+ }
769
+ }
770
+ recomputeSelectedOptions() {
771
+ const options = this.options || [];
772
+ const result = [];
773
+ const nextMap = {};
774
+ const selectedIds = [...this.selectedOptionIds];
775
+ const consumed = new Set();
776
+ const resolveDisplayForId = (selectedId) => {
777
+ // 1) exact match by id or value
778
+ const exact = options.find((opt) => (opt.id || opt.value) === selectedId);
779
+ if (exact) {
780
+ return exact;
781
+ }
782
+ // 2) manual prefix match → create synthetic option for display
783
+ const manual = options.find((opt) => {
784
+ if (!opt.shouldManualInput)
785
+ return false;
786
+ if (opt.manualPrefix) {
787
+ return selectedId.startsWith(opt.manualPrefix);
788
+ }
789
+ // when no prefix, match by sentinel id/value
790
+ return (opt.id || opt.value) === selectedId;
791
+ });
792
+ if (manual) {
793
+ const cached = this.selectedIdToDisplayOption[selectedId];
794
+ return cached || { ...manual, id: selectedId, value: selectedId };
795
+ }
796
+ // 3) fallback when options array is missing the corresponding option definition
797
+ const cached = this.selectedIdToDisplayOption[selectedId];
798
+ return cached || { id: selectedId, value: selectedId };
799
+ };
800
+ if (this.multiselect) {
801
+ // Order by options sequence first
802
+ for (const opt of options) {
803
+ if (opt.shouldManualInput) {
804
+ const manualId = opt.manualPrefix
805
+ ? selectedIds.find((id) => id.startsWith(opt.manualPrefix))
806
+ : selectedIds.find((id) => id === (opt.id || opt.value));
807
+ if (manualId) {
808
+ const display = resolveDisplayForId(manualId);
809
+ result.push(display);
810
+ nextMap[manualId] = display;
811
+ consumed.add(manualId);
812
+ }
813
+ }
814
+ else {
815
+ const id = opt.id || opt.value;
816
+ if (selectedIds.includes(id)) {
817
+ const display = resolveDisplayForId(id);
818
+ result.push(display);
819
+ nextMap[id] = display;
820
+ consumed.add(id);
821
+ }
822
+ }
823
+ }
824
+ // Append any remaining selected ids in their existing order
825
+ for (const selectedId of selectedIds) {
826
+ if (consumed.has(selectedId))
827
+ continue;
828
+ const display = resolveDisplayForId(selectedId);
829
+ result.push(display);
830
+ nextMap[selectedId] = display;
831
+ }
832
+ }
833
+ else {
834
+ // Single select: keep as-is (only one item), but still resolve display
835
+ for (const selectedId of selectedIds) {
836
+ const display = resolveDisplayForId(selectedId);
837
+ result.push(display);
838
+ nextMap[selectedId] = display;
839
+ }
840
+ }
841
+ this.displayedSelectedOptions = result;
842
+ this.selectedIdToDisplayOption = nextMap;
843
+ }
844
+ isManualSelection(selectedIds) {
845
+ const options = this.options || [];
846
+ for (const id of selectedIds) {
847
+ const manual = options.find((opt) => opt.shouldManualInput);
848
+ if (!manual)
849
+ continue;
850
+ if (manual.manualPrefix) {
851
+ if (id.startsWith(manual.manualPrefix))
852
+ return true;
853
+ // allow initial sentinel selection to keep open
854
+ if ((manual.id || manual.value) === id)
855
+ return true;
856
+ }
857
+ else {
858
+ if ((manual.id || manual.value) === id)
859
+ return true;
860
+ }
861
+ }
862
+ return false;
863
+ }
591
864
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
592
865
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SelectComponent, isStandalone: true, selector: "haloduck-select", inputs: { disabled: "disabled", loading: "loading", variant: "variant", asButton: "asButton", useIcon: "useIcon", useFilter: "useFilter", multiselect: "multiselect", placeholder: "placeholder", atLeastOne: "atLeastOne", showAll: "showAll", options: "options", layout: "layout", labelWidth: "labelWidth", value: "value" }, outputs: { selectedChange: "selectedChange" }, providers: [
593
866
  {
@@ -596,7 +869,7 @@ class SelectComponent {
596
869
  multi: true,
597
870
  },
598
871
  provideTranslocoScope('haloduck'),
599
- ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }, { propertyName: "label", first: true, predicate: ["label"], descendants: true }], ngImport: i0, template: "<div class=\"flex gap-2 items-center w-full\"\n [ngClass]=\"{'flex-col items-start': layout === 'vertical'}\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control {{ labelWidth }}\"\n [ngClass]=\"{'w-full': layout === 'vertical'}\">\n <!-- <ng-content select=\"[slot=label]\"></ng-content> -->\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\">&nbsp;</div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && selectedOptions && selectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of selectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && selectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ selectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ selectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-light-inactive dark:text-dark-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content select=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
872
+ ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }, { propertyName: "label", first: true, predicate: ["label"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<div class=\"flex gap-2 items-center w-full\"\n [ngClass]=\"{'flex-col items-start': layout === 'vertical'}\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control {{ labelWidth }}\"\n [ngClass]=\"{'w-full': layout === 'vertical'}\">\n <!-- <ng-content select=\"[slot=label]\"></ng-content> -->\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\">&nbsp;</div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && displayedSelectedOptions && displayedSelectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of displayedSelectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && displayedSelectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ displayedSelectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ displayedSelectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-light-inactive dark:text-dark-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content select=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
600
873
  }
601
874
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SelectComponent, decorators: [{
602
875
  type: Component,
@@ -607,7 +880,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
607
880
  multi: true,
608
881
  },
609
882
  provideTranslocoScope('haloduck'),
610
- ], template: "<div class=\"flex gap-2 items-center w-full\"\n [ngClass]=\"{'flex-col items-start': layout === 'vertical'}\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control {{ labelWidth }}\"\n [ngClass]=\"{'w-full': layout === 'vertical'}\">\n <!-- <ng-content select=\"[slot=label]\"></ng-content> -->\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\">&nbsp;</div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && selectedOptions && selectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of selectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && selectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ selectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ selectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-light-inactive dark:text-dark-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content select=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n" }]
883
+ ], template: "<div class=\"flex gap-2 items-center w-full\"\n [ngClass]=\"{'flex-col items-start': layout === 'vertical'}\">\n <label #label\n class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control {{ labelWidth }}\"\n [ngClass]=\"{'w-full': layout === 'vertical'}\">\n <!-- <ng-content select=\"[slot=label]\"></ng-content> -->\n <ng-content></ng-content>\n </label>\n <div #origin\n class=\"w-full flex-1 relative overflow-visible rounded-md outline outline-light-inactive dark:outline-dark-inactive text-sm/6\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary focus:dark:outline-dark-primary': !disabled,\n 'bg-light-primary dark:bg-dark-primary text-light-on-primary dark:text-dark-on-primary' : variant === 'primary' && !disabled,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary' : variant === 'secondary' && !disabled,\n 'bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger' : variant === 'danger' && !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick($event)\"\n (keydown)=\"onKeyDown($event)\">\n <div class=\"px-3 py-1.5 text-sm/6 cursor-pointer flex flex-nowrap items-center justify-between overflow-hidden\">\n @if(loading){\n <div class=\"flex-1 flex items-center gap-2 w-full h-3\">\n <div class=\"animate-pulse bg-light-inactive dark:bg-dark-inactive rounded-md h-5 w-full\">&nbsp;</div>\n </div>\n }\n @else{\n <div class=\"flex-1\">\n @if (!asButton && displayedSelectedOptions && displayedSelectedOptions.length > 0) {\n @if (multiselect) {\n <div class=\"flex flex-wrap gap-x-2 gap-y-1 items-start\">\n @for (option of displayedSelectedOptions; track option; let i = $index) {\n @if (showAll || i === 0) {\n <span class=\"bg-light-secondary dark:bg-dark-secondary rounded-md flex items-center text-xs/6 text-light-on-secondary dark:text-dark-on-secondary\"\n [ngClass]=\"{'w-full h-full px-4': !multiselect, 'px-1': multiselect}\">\n {{ option.value }}\n @if (showAll || i === 0) {\n <div (click)=\"onDeselectOption($event, option)\"\n class=\"ml-2 text-light-danger dark:text-dark-danger hover:cursor-pointer\">\n <svg class=\"w-3 h-3\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M10 8.586l-2.293-2.293a1 1 0 00-1.414 1.414L8.586 10l-2.293 2.293a1 1 0 001.414 1.414L10 11.414l2.293 2.293a1 1 0 001.414-1.414L11.414 10l2.293-2.293a1 1 0 00-1.414-1.414L10 8.586z\"\n clip-rule=\"evenodd\" />\n </svg>\n </div>\n }\n </span>\n @if (!showAll && displayedSelectedOptions.length > 1) {\n <span class=\"text-light-on-control/80 dark:text-dark-on-control/80 text-xs/6\">\n +{{ displayedSelectedOptions.length - 1 }}\n </span>\n }\n }\n }\n </div>\n } @else {\n <span class=\"text-light-on-control dark:text-dark-on-control overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary dark:text-dark-on-primary': variant === 'primary' && !disabled,\n 'text-light-on-secondary dark:text-dark-on-secondary': variant === 'secondary' && !disabled,\n 'text-light-on-danger dark:text-dark-on-danger': variant === 'danger' && !disabled,\n }\">{{ displayedSelectedOptions[0].value }}</span>\n }\n } @else {\n <span class=\"text-light-inactive dark:text-dark-inactive overflow-hidden overflow-ellipsis\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (useIcon) {\n <ng-content select=\"buttonIcon\"></ng-content>\n } @else {\n {{ placeholder }}\n }\n </span>\n }\n </div>\n }\n <div class=\"text-light-on-control dark:text-dark-on-control\"\n [ngClass]=\"{\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n 'text-light-on-primary/80 dark:text-dark-on-primary/80': variant === 'primary' && !disabled,\n 'text-light-on-secondary/80 dark:text-dark-on-secondary/80': variant === 'secondary' && !disabled,\n 'text-light-on-danger/80 dark:text-dark-on-danger/80': variant === 'danger' && !disabled,\n }\">\n @if (isDropdownOpen()) {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n } @else {\n <svg class=\"w-4 h-4 ml-2 inline-block\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path fill-rule=\"evenodd\"\n d=\"M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 011.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z\"\n clip-rule=\"evenodd\" />\n </svg>\n }\n </div>\n </div>\n </div>\n</div>\n" }]
611
884
  }], ctorParameters: () => [], propDecorators: { selectedChange: [{
612
885
  type: Output
613
886
  }], disabled: [{
@@ -851,7 +1124,7 @@ class CalendarComponent {
851
1124
  });
852
1125
  }
853
1126
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
854
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CalendarComponent, isStandalone: true, selector: "haloduck-calendar", inputs: { singleDate: "singleDate", firstDayOfWeek: "firstDayOfWeek", selectedDate: "selectedDate", selectedDateRange: "selectedDateRange" }, outputs: { dateChange: "dateChange", dateRangeChange: "dateRangeChange" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "monthSelect", first: true, predicate: ["monthSelect"], descendants: true }], ngImport: i0, template: "<div id=\"dateRangeCalendar\"\n class=\"min-w-96 brightness-125 bg-light-background dark:bg-dark-background border border-light-inactive dark:border-dark-inactive rounded text-center p-4 flex flex-col gap-4\"\n tabindex=\"1\">\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(-1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"yearList\"\n [value]=\"currentYear + ''\"\n (selectedChange)=\"onSelectYear($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(-1)\">\n <span class=\"sr-only\">\uC774\uC804 \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"monthList\"\n [value]=\"currentMonth + ''\"\n (selectedChange)=\"onSelectMonth($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(1)\">\n <span class=\"sr-only\">\uB2E4\uC74C \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div>\n <div\n class=\"grid grid-cols-7 gap-[1px] text-xs/6 font-semibold text-light-on-background dark:text-dark-on-background\">\n <div>{{ 'haloduck.ui.calendar.Mon' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Tue' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Wed' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Thu' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Fri' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sat' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sun' | transloco }}</div>\n </div>\n\n <div class=\"isolate mt-2 grid grid-cols-7 gap-[1px] rounded-lg text-sm shadow overflow-hidden\">\n @for (date of listCalendarDate; track date.datetime) {\n <button type=\"button\"\n (click)=\"onSelectCalendarDate(date)\"\n class=\"py-1.5 hover:cursor-pointer bg-light-alternative dark:bg-dark-alternative text-light-on-alternative dark:text-dark-on-alternative\"\n [ngClass]=\"{'bg-light-control dark:bg-dark-control text-light-on-control dark:text-dark-on-control' : date.isCurrentMonth}\">\n <time [ngClass]=\"{'font-semibold bg-light-primary-light dark:bg-dark-primary-light text-on-light-primary-light dark:text-dark-on-primary-light': date.isToday && date.datetime !== selectedDateRange?.from,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': date.datetime === selectedDateRange?.from,\n 'bg-gray-500 font-semibold text-white' : date.isTheDate}\"\n class=\"mx-auto flex size-7 items-center justify-center rounded-full hover:font-bold\">{{date.date}}</time>\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "layout", "labelWidth", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
1127
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CalendarComponent, isStandalone: true, selector: "haloduck-calendar", inputs: { singleDate: "singleDate", firstDayOfWeek: "firstDayOfWeek", selectedDate: "selectedDate", selectedDateRange: "selectedDateRange" }, outputs: { dateChange: "dateChange", dateRangeChange: "dateRangeChange" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "monthSelect", first: true, predicate: ["monthSelect"], descendants: true }], ngImport: i0, template: "<div id=\"dateRangeCalendar\"\n class=\"min-w-96 brightness-125 bg-light-background dark:bg-dark-background border border-light-inactive dark:border-dark-inactive rounded text-center p-4 flex flex-col gap-4\"\n tabindex=\"1\">\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(-1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"yearList\"\n [value]=\"currentYear + ''\"\n (selectedChange)=\"onSelectYear($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\"\n (click)=\"onAdjustYear(1)\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div class=\"flex items-center\">\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(-1)\">\n <span class=\"sr-only\">\uC774\uC804 \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M11.78 5.22a.75.75 0 0 1 0 1.06L8.06 10l3.72 3.72a.75.75 0 1 1-1.06 1.06l-4.25-4.25a.75.75 0 0 1 0-1.06l4.25-4.25a.75.75 0 0 1 1.06 0Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n <div class=\"flex-auto text-sm font-semibold flex justify-center\">\n <div class=\"w-32\">\n <haloduck-select [multiselect]=\"false\"\n [atLeastOne]=\"true\"\n [options]=\"monthList\"\n [value]=\"currentMonth + ''\"\n (selectedChange)=\"onSelectMonth($event)\">\n </haloduck-select>\n </div>\n </div>\n <button type=\"button\"\n class=\"-m-1.5 flex flex-none items-center justify-center p-1.5 text-light-on-background dark:text-dark-on-background hover:scale-105 hover:cursor-pointer active:scale-95\n\"\n (click)=\"onAdjustMonth(1)\">\n <span class=\"sr-only\">\uB2E4\uC74C \uB2EC</span>\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M8.22 5.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L11.94 10 8.22 6.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </button>\n </div>\n\n <div>\n <div\n class=\"grid grid-cols-7 gap-[1px] text-xs/6 font-semibold text-light-on-background dark:text-dark-on-background\">\n <div>{{ 'haloduck.ui.calendar.Mon' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Tue' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Wed' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Thu' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Fri' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sat' | transloco }}</div>\n <div>{{ 'haloduck.ui.calendar.Sun' | transloco }}</div>\n </div>\n\n <div class=\"isolate mt-2 grid grid-cols-7 gap-[1px] rounded-lg text-sm shadow overflow-hidden\">\n @for (date of listCalendarDate; track date.datetime) {\n <button type=\"button\"\n (click)=\"onSelectCalendarDate(date)\"\n class=\"py-1.5 hover:cursor-pointer bg-light-alternative dark:bg-dark-alternative text-light-on-alternative dark:text-dark-on-alternative\"\n [ngClass]=\"{'bg-light-control dark:bg-dark-control text-light-on-control dark:text-dark-on-control' : date.isCurrentMonth}\">\n <time [ngClass]=\"{'font-semibold bg-light-primary-light dark:bg-dark-primary-light text-on-light-primary-light dark:text-dark-on-primary-light': date.isToday && date.datetime !== selectedDateRange?.from,\n 'bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary': date.datetime === selectedDateRange?.from,\n 'bg-gray-500 font-semibold text-white' : date.isTheDate}\"\n class=\"mx-auto flex size-7 items-center justify-center rounded-full hover:font-bold\">{{date.date}}</time>\n </button>\n }\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "layout", "labelWidth", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
855
1128
  }
856
1129
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CalendarComponent, decorators: [{
857
1130
  type: Component,
@@ -967,13 +1240,13 @@ class DialogService {
967
1240
  this.dialogStack.pop()?.dispose();
968
1241
  }
969
1242
  }
970
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, deps: [{ token: i1$3.Overlay }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable });
1243
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, deps: [{ token: i1$2.Overlay }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Injectable });
971
1244
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, providedIn: 'root' });
972
1245
  }
973
1246
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DialogService, decorators: [{
974
1247
  type: Injectable,
975
1248
  args: [{ providedIn: 'root' }]
976
- }], ctorParameters: () => [{ type: i1$3.Overlay }, { type: i0.Injector }] });
1249
+ }], ctorParameters: () => [{ type: i1$2.Overlay }, { type: i0.Injector }] });
977
1250
 
978
1251
  class ConfirmDialogComponent {
979
1252
  dialogService = inject(DialogService);
@@ -1058,7 +1331,7 @@ class CopyButtonComponent {
1058
1331
  }
1059
1332
  }
1060
1333
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CopyButtonComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1061
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CopyButtonComponent, isStandalone: true, selector: "haloduck-copy-button", inputs: { text: "text" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<ng-container *transloco=\"let _ts; read: 'haloduck'\">\n <button type=\"button\"\n class=\"group inline-flex items-center px-2 py-1 bg-light-alternative dark:bg-dark-alternative hover:brightness-125 rounded text-xs text-light-on-alternative dark:text-dark-on-alternative hover:cursor-pointer active:scale-95 transition-transform\"\n (click)=\"copyToClipboard($event)\"\n title=\"Copy to clipboard\">\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n [class]=\"'h-4 w-4 mr-1 transition-all duration-500 ' + (isAnimating ? 'scale-150 opacity-0' : 'scale-100 opacity-100')\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <rect x=\"9\"\n y=\"9\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n <rect x=\"3\"\n y=\"3\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n </svg>\n <span class=\"hidden group-hover:inline\">\n @if (isAnimating) {\n {{ _ts('ui.button.Copied') }}\n } @else {\n {{ _ts('ui.button.Copy') }}\n }\n </span>\n </button>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "directive", type: i1$2.TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
1334
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: CopyButtonComponent, isStandalone: true, selector: "haloduck-copy-button", inputs: { text: "text" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<ng-container *transloco=\"let _ts; read: 'haloduck'\">\n <button type=\"button\"\n class=\"group inline-flex items-center px-2 py-1 bg-light-alternative dark:bg-dark-alternative hover:brightness-125 rounded text-xs text-light-on-alternative dark:text-dark-on-alternative hover:cursor-pointer active:scale-95 transition-transform\"\n (click)=\"copyToClipboard($event)\"\n title=\"Copy to clipboard\">\n <svg xmlns=\"http://www.w3.org/2000/svg\"\n [class]=\"'h-4 w-4 mr-1 transition-all duration-500 ' + (isAnimating ? 'scale-150 opacity-0' : 'scale-100 opacity-100')\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\">\n <rect x=\"9\"\n y=\"9\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n <rect x=\"3\"\n y=\"3\"\n width=\"13\"\n height=\"13\"\n rx=\"2\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n fill=\"none\" />\n </svg>\n <span class=\"hidden group-hover:inline\">\n @if (isAnimating) {\n {{ _ts('ui.button.Copied') }}\n } @else {\n {{ _ts('ui.button.Copy') }}\n }\n </span>\n </button>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: TranslocoModule }, { kind: "directive", type: i2$1.TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
1062
1335
  }
1063
1336
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: CopyButtonComponent, decorators: [{
1064
1337
  type: Component,
@@ -1446,7 +1719,7 @@ class DateRangeComponent {
1446
1719
  multi: true,
1447
1720
  },
1448
1721
  provideTranslocoScope('haloduck'),
1449
- ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <haloduck-select [multiselect]=\"false\"\n [disabled]=\"disabled\"\n [options]=\"dateRangeOptions\"\n [atLeastOne]=\"true\"\n (click)=\"$event.stopPropagation()\"\n variant=\"primary\"\n placeholder=\"{{ 'haloduck.ui.calendar.Period' | transloco }}\"\n [(ngModel)]=\"selectedDateRangeOptionId\"\n (ngModelChange)=\"onDateRangeOptionChange($event)\">\n </haloduck-select>\n @if (getSelectedDateRangeString()) {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-on-control dark:text-dark-on-control': !disabled,\n 'text-light-on-control/60 dark:text-light-on-control/60': disabled,\n }\"> {{ getSelectedDateRangeString() }}</div>\n } @else {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !disabled,\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n }\"> {{ placeholder }}</div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "layout", "labelWidth", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
1722
+ ], viewQueries: [{ propertyName: "origin", first: true, predicate: ["origin"], descendants: true }], ngImport: i0, template: "<label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n</label>\n<div #origin\n class=\"relative mt-2 gap-2 rounded-md outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [ngClass]=\"{\n 'bg-light-control dark:bg-dark-control focus:outline-2 focus:outline-offset-2 focus:outline-light-primary dark:focus:outline-dark-primary': !disabled,\n 'bg-light-control/60 dark:bg-dark-control/60 text-light-on-control/60 dark:text-dark-on-control/60' : disabled,\n }\"\n [tabindex]=\"(disabled) ? -1 : 0\"\n (click)=\"onClick()\">\n <div class=\"flex items-center\">\n <haloduck-select [multiselect]=\"false\"\n [disabled]=\"disabled\"\n [options]=\"dateRangeOptions\"\n [atLeastOne]=\"true\"\n (click)=\"$event.stopPropagation()\"\n variant=\"primary\"\n placeholder=\"{{ 'haloduck.ui.calendar.Period' | transloco }}\"\n [(ngModel)]=\"selectedDateRangeOptionId\"\n (ngModelChange)=\"onDateRangeOptionChange($event)\">\n </haloduck-select>\n @if (getSelectedDateRangeString()) {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-on-control dark:text-dark-on-control': !disabled,\n 'text-light-on-control/60 dark:text-light-on-control/60': disabled,\n }\"> {{ getSelectedDateRangeString() }}</div>\n } @else {\n <div class=\"flex-1 sm:text-sm/6 text-center\"\n [ngClass]=\"{\n 'text-light-inactive dark:text-dark-inactive': !disabled,\n 'text-light-on-control/60 dark:text-dark-on-control/60': disabled,\n }\"> {{ placeholder }}</div>\n }\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "component", type: SelectComponent, selector: "haloduck-select", inputs: ["disabled", "loading", "variant", "asButton", "useIcon", "useFilter", "multiselect", "placeholder", "atLeastOne", "showAll", "options", "layout", "labelWidth", "value"], outputs: ["selectedChange"] }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
1450
1723
  }
1451
1724
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DateRangeComponent, decorators: [{
1452
1725
  type: Component,
@@ -1590,7 +1863,7 @@ class DrawCanvasComponent {
1590
1863
  multi: true,
1591
1864
  },
1592
1865
  provideTranslocoScope('haloduck'),
1593
- ], viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"draw-canvas-container flex flex-col gap-2\">\n <canvas #canvas width=\"800\" height=\"400\" class=\"max-w-full draw-canvas select-none\"></canvas>\n <haloduck-button variant=\"danger\" (click)=\"onReset()\">{{ 'haloduck.ui.draw.Clear' | transloco }}</haloduck-button>\n</div>\n", styles: [".draw-canvas-container{display:flex;flex-direction:column;align-items:center}.draw-canvas{border:1px solid #ccc;cursor:crosshair}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
1866
+ ], viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"draw-canvas-container flex flex-col gap-2\">\n <canvas #canvas width=\"800\" height=\"400\" class=\"max-w-full draw-canvas select-none\"></canvas>\n <haloduck-button variant=\"danger\" (click)=\"onReset()\">{{ 'haloduck.ui.draw.Clear' | transloco }}</haloduck-button>\n</div>\n", styles: [".draw-canvas-container{display:flex;flex-direction:column;align-items:center}.draw-canvas{border:1px solid #ccc;cursor:crosshair}\n"], dependencies: [{ kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
1594
1867
  }
1595
1868
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: DrawCanvasComponent, decorators: [{
1596
1869
  type: Component,
@@ -1779,7 +2052,7 @@ class FileUploaderComponent {
1779
2052
  multi: true,
1780
2053
  },
1781
2054
  provideTranslocoScope('haloduck'),
1782
- ], ngImport: i0, template: "<div class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n @if (!isUploading) {\n <label for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\">\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{ 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco }}</span>\n <input id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li class=\"flex items-center justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md\">\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl){\n <img [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\" />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{ 'haloduck.ui.file.Uploaded' | transloco }}</span>\n }@else{\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{ 'haloduck.ui.file.Uploading...' | transloco }}</span>\n }\n }@else{\n <button type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\">\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </li>\n }\n </ul>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
2055
+ ], ngImport: i0, template: "<div class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n @if (!isUploading) {\n <label for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\">\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{ 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco }}</span>\n <input id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li class=\"flex items-center justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md\">\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl){\n <img [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\" />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{ 'haloduck.ui.file.Uploaded' | transloco }}</span>\n }@else{\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{ 'haloduck.ui.file.Uploading...' | transloco }}</span>\n }\n }@else{\n <button type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\">\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </li>\n }\n </ul>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
1783
2056
  }
1784
2057
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FileUploaderComponent, decorators: [{
1785
2058
  type: Component,
@@ -2016,7 +2289,7 @@ class ImageUploaderComponent {
2016
2289
  multi: true,
2017
2290
  },
2018
2291
  provideTranslocoScope('haloduck'),
2019
- ], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col gap-1\">\n <label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n </label>\n <div class=\"h-full w-full p-2 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragEnd($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <!-- Show image preview if available -->\n @if (imageUrl) {\n @if (!disabled) {\n <div class=\"relative\">\n <button type=\"button\"\n class=\"absolute -top-1 -right-1 bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger rounded-full px-1 text-xs\"\n (click)=\"removeImage()\">\n x\n </button>\n </div>\n }\n <img [src]=\"imageUrl\"\n [id]=\"id + '-image'\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\"\n [ngClass]=\"{\n 'filter grayscale': disabled,\n }\"\n (click)=\"previewImage(imageUrl)\" />\n } @else {\n <!-- Show upload area -->\n <label for=\"{{id + '-input'}}\"\n class=\"flex flex-col items-center justify-center w-full h-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md\"\n [ngClass]=\"{'cursor-pointer hover:border-light-primary dark:hover:border-dark-primary': !disabled}\">\n @if (backgroundImage) {\n <img [src]=\"backgroundImage\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\" />\n }@else {\n <span class=\"text-light-inactive dark:text-dark-inactive text-sm p-2\">{{ 'haloduck.ui.file.Drag and drop an image here, or click to select' | transloco }}</span>\n }\n\n @if(!disabled){\n <input [id]=\"id + '-input'\"\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n [multiple]=\"passToSibling\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n }\n </label>\n }\n </div>\n</div>\n", styles: [".drag-over{border-color:#3b82f6}.relative{position:relative}button{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
2292
+ ], ngImport: i0, template: "<div class=\"w-full h-full flex flex-col gap-1\">\n <label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control\">\n <ng-content></ng-content>\n </label>\n <div class=\"h-full w-full p-2 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragstart)=\"onDragStart($event)\"\n (dragend)=\"onDragEnd($event)\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\">\n <!-- Show image preview if available -->\n @if (imageUrl) {\n @if (!disabled) {\n <div class=\"relative\">\n <button type=\"button\"\n class=\"absolute -top-1 -right-1 bg-light-danger dark:bg-dark-danger text-light-on-danger dark:text-dark-on-danger rounded-full px-1 text-xs\"\n (click)=\"removeImage()\">\n x\n </button>\n </div>\n }\n <img [src]=\"imageUrl\"\n [id]=\"id + '-image'\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\"\n [ngClass]=\"{\n 'filter grayscale': disabled,\n }\"\n (click)=\"previewImage(imageUrl)\" />\n } @else {\n <!-- Show upload area -->\n <label for=\"{{id + '-input'}}\"\n class=\"flex flex-col items-center justify-center w-full h-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md\"\n [ngClass]=\"{'cursor-pointer hover:border-light-primary dark:hover:border-dark-primary': !disabled}\">\n @if (backgroundImage) {\n <img [src]=\"backgroundImage\"\n [alt]=\"alt || ''\"\n class=\"w-full h-full rounded-md aspect-square\" />\n }@else {\n <span class=\"text-light-inactive dark:text-dark-inactive text-sm p-2\">{{ 'haloduck.ui.file.Drag and drop an image here, or click to select' | transloco }}</span>\n }\n\n @if(!disabled){\n <input [id]=\"id + '-input'\"\n type=\"file\"\n class=\"hidden\"\n accept=\"image/*\"\n [multiple]=\"passToSibling\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\" />\n }\n </label>\n }\n </div>\n</div>\n", styles: [".drag-over{border-color:#3b82f6}.relative{position:relative}button{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
2020
2293
  }
2021
2294
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: ImageUploaderComponent, decorators: [{
2022
2295
  type: Component,
@@ -2429,13 +2702,13 @@ class SideMenuItemComponent {
2429
2702
  navigateTo(link) {
2430
2703
  this.router.navigate(link);
2431
2704
  }
2432
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, deps: [{ token: i1$4.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
2705
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, deps: [{ token: i1$3.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
2433
2706
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: SideMenuItemComponent, isStandalone: true, selector: "haloduck-side-menu-item", inputs: { menuItem: "menuItem", depth: "depth", menuItemSelectHandler: "menuItemSelectHandler" }, ngImport: i0, template: "<a href=\"javascript:void(0)\"\n (click)=\"onMenuItemSelected(menuItem)\"\n class=\"flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:text-light-on-primary dark:hover:text-dark-on-primary hover:bg-light-primary dark:hover:bg-dark-primary hover:cursor-pointer\"\n [ngClass]=\"{\n 'font-bold text-light-on-secondary dark:bg-dark-on-secondary bg-light-secondary dark:bg-dark-secondary': menuItem.isSelected,\n }\">\n @if (menuItem.iconType === 'svg') {\n <div [innerHTML]=\"iconSvg\"></div>\n } @else {\n @if (menuItem.icon) {\n <img class=\"w-5 h-5\"\n [src]=\"menuItem.icon\"\n [alt]=\"menuItem.title\"\n [title]=\"menuItem.title\"\n [loading]=\"'lazy'\" />\n }\n }\n <span class=\"overflow-hidden text-ellipsis whitespace-nowrap\"\n title=\"{{ menuItem.title }}\"\n [ngStyle]=\"{'margin-left.px': depth ? depth * 16 : 0}\">\n {{ menuItem.title }}\n </span>\n</a>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }] });
2434
2707
  }
2435
2708
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: SideMenuItemComponent, decorators: [{
2436
2709
  type: Component,
2437
2710
  args: [{ selector: 'haloduck-side-menu-item', imports: [CommonModule], template: "<a href=\"javascript:void(0)\"\n (click)=\"onMenuItemSelected(menuItem)\"\n class=\"flex items-center gap-2 px-4 py-2 rounded-md cursor-pointer hover:text-light-on-primary dark:hover:text-dark-on-primary hover:bg-light-primary dark:hover:bg-dark-primary hover:cursor-pointer\"\n [ngClass]=\"{\n 'font-bold text-light-on-secondary dark:bg-dark-on-secondary bg-light-secondary dark:bg-dark-secondary': menuItem.isSelected,\n }\">\n @if (menuItem.iconType === 'svg') {\n <div [innerHTML]=\"iconSvg\"></div>\n } @else {\n @if (menuItem.icon) {\n <img class=\"w-5 h-5\"\n [src]=\"menuItem.icon\"\n [alt]=\"menuItem.title\"\n [title]=\"menuItem.title\"\n [loading]=\"'lazy'\" />\n }\n }\n <span class=\"overflow-hidden text-ellipsis whitespace-nowrap\"\n title=\"{{ menuItem.title }}\"\n [ngStyle]=\"{'margin-left.px': depth ? depth * 16 : 0}\">\n {{ menuItem.title }}\n </span>\n</a>\n" }]
2438
- }], ctorParameters: () => [{ type: i1$4.DomSanitizer }], propDecorators: { menuItem: [{
2711
+ }], ctorParameters: () => [{ type: i1$3.DomSanitizer }], propDecorators: { menuItem: [{
2439
2712
  type: Input
2440
2713
  }], depth: [{
2441
2714
  type: Input
@@ -2552,7 +2825,7 @@ class StlViewerComponent {
2552
2825
  }
2553
2826
  }
2554
2827
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: StlViewerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2555
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: StlViewerComponent, isStandalone: true, selector: "haloduck-stl-viewer", inputs: { fileUrl: "fileUrl", filename: "filename", context: "context" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["viewerContainer"], descendants: true }], ngImport: i0, template: "<div class=\"relative w-full h-full flex flex-col items-end\">\n <div class=\"w-full h-full flex items-start flex-col\">\n <div #viewerContainer\n class=\"w-full h-full rounded-md\"></div>\n @if (loadingPercentage < 100)\n {\n <div\n class=\"h-2 bg-light-primary-light dark:bg-dark-primary-light\"\n [ngStyle]=\"{ 'width': loadingPercentage + '%' }\">\n </div>\n }\n</div>\n<div class=\"absolute bottom-2 right-0 flex justify-end gap-2\">\n @if (context) {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"danger\"\n (click)=\"closeDialog()\">{{ 'haloduck.ui.button.Close' | transloco }}</haloduck-button>\n } @else {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"secondary\"\n (click)=\"showStl()\">{{ 'haloduck.ui.button.View' | transloco }}</haloduck-button>\n }\n</div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
2828
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: StlViewerComponent, isStandalone: true, selector: "haloduck-stl-viewer", inputs: { fileUrl: "fileUrl", filename: "filename", context: "context" }, providers: [provideTranslocoScope('haloduck')], viewQueries: [{ propertyName: "containerRef", first: true, predicate: ["viewerContainer"], descendants: true }], ngImport: i0, template: "<div class=\"relative w-full h-full flex flex-col items-end\">\n <div class=\"w-full h-full flex items-start flex-col\">\n <div #viewerContainer\n class=\"w-full h-full rounded-md\"></div>\n @if (loadingPercentage < 100)\n {\n <div\n class=\"h-2 bg-light-primary-light dark:bg-dark-primary-light\"\n [ngStyle]=\"{ 'width': loadingPercentage + '%' }\">\n </div>\n }\n</div>\n<div class=\"absolute bottom-2 right-0 flex justify-end gap-2\">\n @if (context) {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"danger\"\n (click)=\"closeDialog()\">{{ 'haloduck.ui.button.Close' | transloco }}</haloduck-button>\n } @else {\n <haloduck-button variant=\"primary\"\n (click)=\"download()\">{{ 'haloduck.ui.button.Download' | transloco }}</haloduck-button>\n <haloduck-button variant=\"secondary\"\n (click)=\"showStl()\">{{ 'haloduck.ui.button.View' | transloco }}</haloduck-button>\n }\n</div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
2556
2829
  }
2557
2830
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: StlViewerComponent, decorators: [{
2558
2831
  type: Component,
@@ -2673,7 +2946,7 @@ class TableComponent {
2673
2946
  ngOnInit() { }
2674
2947
  constructor() { }
2675
2948
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TableComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2676
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: TableComponent, isStandalone: true, selector: "haloduck-table", inputs: { tableLayout: "tableLayout", showHeader: "showHeader", useLoadMore: "useLoadMore", columns: "columns", rows: "rows", isLoading: "isLoading", isPaging: "isPaging", sort: "sort", expandedTemplate: "expandedTemplate", customTemplates: "customTemplates", lastEvaluatedKey: "lastEvaluatedKey" }, outputs: { onSortChange: "onSortChange", onLoadMore: "onLoadMore", onRowClick: "onRowClick", onRowDblClick: "onRowDblClick" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div class=\"w-full h-full shadow border border-light-inactive dark:border-dark-inactive rounded-lg overflow-auto\">\n <table class=\"w-full h-full\"\n [ngClass]=\"{\n 'table-fixed': tableLayout === 'fixed',\n 'table-auto': tableLayout === 'auto',\n }\">\n <colgroup>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <col class=\"{{ column.width || '' }}\" />\n }\n }\n </colgroup>\n @if (showHeader) {\n <thead class=\"bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-tl-lg rounded-tr-lg\">\n <tr>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <th scope=\"col\"\n class=\"{{ column.align || '' }} py-3.5 pl-4 pr-3 text-sm font-semibold text-light-on-background dark:text-dark-on-background whitespace-nowrap\">\n <span class=\"group inline-flex\">\n {{ column.label }}\n @if (column.sortable) {\n @switch (getSortDirection(column.sort?.field || column.key) | async)\n {\n @case('desc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @case('asc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'desc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @default {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"invisible ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive group-hover:visible group-focus:visible\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n }\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n }\n <tbody class=\"overflow-scroll\">\n @for (row of rows | async; track row['id']; let lastRow = $last) {\n <tr class=\"border-t border-light-inactive dark:border-dark-inactive {{ row.bgColor || '' }}\"\n (click)=\"onRowClicked(row)\"\n (dblclick)=\"onRowDblClicked(row)\"\n [ngClass]=\"{'rounded-b-lg': lastRow && (lastEvaluatedKey | async) === null, 'even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background': !row.bgColor }\"\n [ngStyle]=\"{ 'background-color': row.bgColor || '' }\">\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <td class=\"relative overflow-visible {{ column.align || '' }} whitespace-nowrap px-3 text-sm text-light-on-background dark:text-dark-on-background\"\n [ngClass]=\"{\n 'first:rounded-bl-lg last:rounded-br-lg': lastRow && (lastEvaluatedKey | async) === null,\n 'text-wrap': row.isExpanded && column.type !== 'custom',\n 'overflow-x-hidden text-ellipsis' : !row.isExpanded && column.type !== 'custom',\n 'py-2': column.type === 'custom',\n 'py-4': column.type !== 'custom'\n }\">\n @if (column.type === 'custom') {\n @if (column.customRenderTemplate) {\n <ng-container [ngTemplateOutlet]=\"customTemplates[column.customRenderTemplate]\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\"></ng-container>\n }\n }\n @else {\n {{ getColumnValue(row, column) | async }}\n }\n </td>\n }\n }\n </tr>\n\n @if (row.isExpanded && expandedTemplate) {\n <tr>\n <td [attr.colspan]=\"columns.length\">\n </td>\n </tr>\n <tr class=\"even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 pb-4 text-sm text-center\">\n <ng-container [ngTemplateOutlet]=\"expandedTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </td>\n </tr>\n }\n\n @if (lastRow && (lastEvaluatedKey | async) && !(isLoading | async) && useLoadMore) {\n <tr #loadMoreRow class=\"border-t border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n <div (click)=\"onLoadMoreClicked()\"\n class=\"cursor-pointer\">\n {{ 'haloduck.ui.table.Load More...' | transloco }}\n </div>\n </td>\n </tr>\n }\n\n } @empty {\n @if(!(isLoading | async)) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n {{ 'haloduck.ui.table.No data available.' | transloco }}\n </td>\n </tr>\n }\n }\n\n @if(isLoading | async) {\n @for ( i of [0,1,2,3,4]; track i; let lastRow = $last)\n {\n <tr class=\"bg-light-background dark:bg-dark-background border-t border-light-inactive/50 dark:border-dark-inactive/50\">\n @for (column of columns; track column.key) {\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm\"\n [ngClass]=\"{'first:rounded-bl-lg last:rounded-br-lg': lastRow}\">\n <div class=\"h-4 bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-md animate-pulse\"></div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2949
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: TableComponent, isStandalone: true, selector: "haloduck-table", inputs: { tableLayout: "tableLayout", showHeader: "showHeader", useLoadMore: "useLoadMore", columns: "columns", rows: "rows", isLoading: "isLoading", isPaging: "isPaging", sort: "sort", expandedTemplate: "expandedTemplate", customTemplates: "customTemplates", lastEvaluatedKey: "lastEvaluatedKey" }, outputs: { onSortChange: "onSortChange", onLoadMore: "onLoadMore", onRowClick: "onRowClick", onRowDblClick: "onRowDblClick" }, providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: "<div class=\"w-full h-full shadow border border-light-inactive dark:border-dark-inactive rounded-lg overflow-auto\">\n <table class=\"w-full h-full\"\n [ngClass]=\"{\n 'table-fixed': tableLayout === 'fixed',\n 'table-auto': tableLayout === 'auto',\n }\">\n <colgroup>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <col class=\"{{ column.width || '' }}\" />\n }\n }\n </colgroup>\n @if (showHeader) {\n <thead class=\"bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-tl-lg rounded-tr-lg\">\n <tr>\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <th scope=\"col\"\n class=\"{{ column.align || '' }} py-3.5 pl-4 pr-3 text-sm font-semibold text-light-on-background dark:text-dark-on-background whitespace-nowrap\">\n <span class=\"group inline-flex\">\n {{ column.label }}\n @if (column.sortable) {\n @switch (getSortDirection(column.sort?.field || column.key) | async)\n {\n @case('desc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M5.22 8.22a.75.75 0 0 1 1.06 0L10 11.94l3.72-3.72a.75.75 0 1 1 1.06 1.06l-4.25 4.25a.75.75 0 0 1-1.06 0L5.22 9.28a.75.75 0 0 1 0-1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @case('asc') {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'desc')\"\n class=\"ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n @default {\n <span (click)=\"onUpdateSort(column.sort?.field || column.key, 'asc')\"\n class=\"invisible ml-2 flex-none rounded text-light-on-inactive dark:text-dark-on-inactive group-hover:visible group-focus:visible\">\n <svg class=\"size-5\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M14.78 11.78a.75.75 0 0 1-1.06 0L10 8.06l-3.72 3.72a.75.75 0 1 1-1.06-1.06l4.25-4.25a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06Z\"\n clip-rule=\"evenodd\" />\n </svg>\n </span>\n }\n }\n }\n </span>\n </th>\n }\n }\n </tr>\n </thead>\n }\n <tbody class=\"overflow-scroll\">\n @for (row of rows | async; track row['id']; let lastRow = $last) {\n <tr class=\"border-t border-light-inactive dark:border-dark-inactive {{ row.bgColor || '' }}\"\n (click)=\"onRowClicked(row)\"\n (dblclick)=\"onRowDblClicked(row)\"\n [ngClass]=\"{'rounded-b-lg': lastRow && (lastEvaluatedKey | async) === null, 'even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background': !row.bgColor }\"\n [ngStyle]=\"{ 'background-color': row.bgColor || '' }\">\n @for (column of columns; track column.key) {\n @if (!column.hidden || !(column.hidden | async)) {\n <td class=\"relative overflow-visible {{ column.align || '' }} whitespace-nowrap px-3 text-sm text-light-on-background dark:text-dark-on-background\"\n [ngClass]=\"{\n 'first:rounded-bl-lg last:rounded-br-lg': lastRow && (lastEvaluatedKey | async) === null,\n 'text-wrap': row.isExpanded && column.type !== 'custom',\n 'overflow-x-hidden text-ellipsis' : !row.isExpanded && column.type !== 'custom',\n 'py-2': column.type === 'custom',\n 'py-4': column.type !== 'custom'\n }\">\n @if (column.type === 'custom') {\n @if (column.customRenderTemplate) {\n <ng-container [ngTemplateOutlet]=\"customTemplates[column.customRenderTemplate]\"\n [ngTemplateOutletContext]=\"{ $implicit: row, column: column }\"></ng-container>\n }\n }\n @else {\n {{ getColumnValue(row, column) | async }}\n }\n </td>\n }\n }\n </tr>\n\n @if (row.isExpanded && expandedTemplate) {\n <tr>\n <td [attr.colspan]=\"columns.length\">\n </td>\n </tr>\n <tr class=\"even:bg-light-alternative dark:even:bg-dark-alternative odd:bg-light-background dark:odd:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 pb-4 text-sm text-center\">\n <ng-container [ngTemplateOutlet]=\"expandedTemplate\"\n [ngTemplateOutletContext]=\"{ $implicit: row }\"></ng-container>\n </td>\n </tr>\n }\n\n @if (lastRow && (lastEvaluatedKey | async) && !(isLoading | async) && useLoadMore) {\n <tr #loadMoreRow class=\"border-t border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\">\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n <div (click)=\"onLoadMoreClicked()\"\n class=\"cursor-pointer\">\n {{ 'haloduck.ui.table.Load More...' | transloco }}\n </div>\n </td>\n </tr>\n }\n\n } @empty {\n @if(!(isLoading | async)) {\n <tr>\n <td [attr.colspan]=\"columns.length\"\n class=\"whitespace-nowrap px-3 py-4 text-sm text-light-on-background dark:text-dark-on-background text-center h-16 bg-light-background dark:bg-dark-background rounded-bl-lg rounded-br-lg\">\n {{ 'haloduck.ui.table.No data available.' | transloco }}\n </td>\n </tr>\n }\n }\n\n @if(isLoading | async) {\n @for ( i of [0,1,2,3,4]; track i; let lastRow = $last)\n {\n <tr class=\"bg-light-background dark:bg-dark-background border-t border-light-inactive/50 dark:border-dark-inactive/50\">\n @for (column of columns; track column.key) {\n <td class=\"whitespace-nowrap py-4 pl-4 pr-3 text-sm\"\n [ngClass]=\"{'first:rounded-bl-lg last:rounded-br-lg': lastRow}\">\n <div class=\"h-4 bg-light-inactive/50 dark:bg-dark-inactive/50 rounded-md animate-pulse\"></div>\n </td>\n }\n </tr>\n }\n }\n </tbody>\n </table>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2677
2950
  }
2678
2951
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TableComponent, decorators: [{
2679
2952
  type: Component,
@@ -3244,13 +3517,13 @@ class BreadcrumbService {
3244
3517
  }
3245
3518
  return breadcrumbs;
3246
3519
  }
3247
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, deps: [{ token: i1$5.Router }], target: i0.ɵɵFactoryTarget.Injectable });
3520
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, deps: [{ token: i1$4.Router }], target: i0.ɵɵFactoryTarget.Injectable });
3248
3521
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, providedIn: 'root' });
3249
3522
  }
3250
3523
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbService, decorators: [{
3251
3524
  type: Injectable,
3252
3525
  args: [{ providedIn: 'root' }]
3253
- }], ctorParameters: () => [{ type: i1$5.Router }] });
3526
+ }], ctorParameters: () => [{ type: i1$4.Router }] });
3254
3527
 
3255
3528
  class BreadcrumbComponent {
3256
3529
  breadcrumbService = inject(BreadcrumbService);
@@ -3261,7 +3534,7 @@ class BreadcrumbComponent {
3261
3534
  });
3262
3535
  }
3263
3536
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3264
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: BreadcrumbComponent, isStandalone: true, selector: "haloduck-breadcrumb", ngImport: i0, template: "<nav class=\"flex border-b border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\"\n aria-label=\"Breadcrumb\">\n <ol role=\"list\"\n class=\"flex w-full max-w-(--breakpoint-xl) space-x-4 px-4 sm:px-6 lg:px-8\">\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <a routerLink=\"/\"\n class=\"text-light-on-background dark:text-dark-on-background hover:scale-105\">\n <svg class=\"size-5 shrink-0\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M9.293 2.293a1 1 0 0 1 1.414 0l7 7A1 1 0 0 1 17 11h-1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6H3a1 1 0 0 1-.707-1.707l7-7Z\"\n clip-rule=\"evenodd\" />\n </svg>\n <span class=\"sr-only\">Home</span>\n </a>\n </div>\n </li>\n @for (breadcrumb of listBreadcrumb; track breadcrumb.label; let last = $last) {\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <svg class=\"h-full w-6 shrink-0 text-light-inactive dark:text-dark-inactive\"\n viewBox=\"0 0 24 44\"\n preserveAspectRatio=\"none\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path d=\"M.293 0l22 22-22 22h1.414l22-22-22-22H.293z\" />\n </svg>\n @if (!last) {\n <a href=\"javascript:void(0)\"\n [routerLink]=\"breadcrumb.url\"\n class=\"ml-4 text-sm font-medium hover:font-bold text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</a>\n } @else {\n <span class=\"ml-4 text-sm font-medium text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</nav>\n", styles: [""], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i1$2.TranslocoPipe, name: "transloco" }] });
3537
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: BreadcrumbComponent, isStandalone: true, selector: "haloduck-breadcrumb", ngImport: i0, template: "<nav class=\"flex border-b border-light-inactive dark:border-dark-inactive bg-light-background dark:bg-dark-background\"\n aria-label=\"Breadcrumb\">\n <ol role=\"list\"\n class=\"flex w-full max-w-(--breakpoint-xl) space-x-4 px-4 sm:px-6 lg:px-8\">\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <a routerLink=\"/\"\n class=\"text-light-on-background dark:text-dark-on-background hover:scale-105\">\n <svg class=\"size-5 shrink-0\"\n viewBox=\"0 0 20 20\"\n fill=\"currentColor\"\n aria-hidden=\"true\"\n data-slot=\"icon\">\n <path fill-rule=\"evenodd\"\n d=\"M9.293 2.293a1 1 0 0 1 1.414 0l7 7A1 1 0 0 1 17 11h-1v6a1 1 0 0 1-1 1h-2a1 1 0 0 1-1-1v-3a1 1 0 0 0-1-1H9a1 1 0 0 0-1 1v3a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1v-6H3a1 1 0 0 1-.707-1.707l7-7Z\"\n clip-rule=\"evenodd\" />\n </svg>\n <span class=\"sr-only\">Home</span>\n </a>\n </div>\n </li>\n @for (breadcrumb of listBreadcrumb; track breadcrumb.label; let last = $last) {\n <li class=\"flex\">\n <div class=\"flex items-center\">\n <svg class=\"h-full w-6 shrink-0 text-light-inactive dark:text-dark-inactive\"\n viewBox=\"0 0 24 44\"\n preserveAspectRatio=\"none\"\n fill=\"currentColor\"\n aria-hidden=\"true\">\n <path d=\"M.293 0l22 22-22 22h1.414l22-22-22-22H.293z\" />\n </svg>\n @if (!last) {\n <a href=\"javascript:void(0)\"\n [routerLink]=\"breadcrumb.url\"\n class=\"ml-4 text-sm font-medium hover:font-bold text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</a>\n } @else {\n <span class=\"ml-4 text-sm font-medium text-light-on-background dark:text-dark-on-background\">{{ breadcrumb.label | transloco }}</span>\n }\n </div>\n </li>\n }\n </ol>\n</nav>\n", styles: [""], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
3265
3538
  }
3266
3539
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: BreadcrumbComponent, decorators: [{
3267
3540
  type: Component,