@keepui/ui 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +741 -276
- package/fesm2022/keepui-ui.mjs +1509 -2
- package/fesm2022/keepui-ui.mjs.map +1 -1
- package/lib/components/signal-dropdown/signal-dropdown.component.d.ts +91 -0
- package/lib/components/signal-dropdown/signal-dropdown.component.d.ts.map +1 -0
- package/lib/components/signal-dropdown/signal-dropdown.types.d.ts +12 -0
- package/lib/components/signal-dropdown/signal-dropdown.types.d.ts.map +1 -0
- package/lib/components/signal-text-input/signal-text-input.component.d.ts +101 -0
- package/lib/components/signal-text-input/signal-text-input.component.d.ts.map +1 -0
- package/lib/components/signal-text-input/signal-text-input.types.d.ts +8 -0
- package/lib/components/signal-text-input/signal-text-input.types.d.ts.map +1 -0
- package/lib/components/signal-textarea/signal-textarea.component.d.ts +70 -0
- package/lib/components/signal-textarea/signal-textarea.component.d.ts.map +1 -0
- package/lib/components/signal-textarea/signal-textarea.types.d.ts +11 -0
- package/lib/components/signal-textarea/signal-textarea.types.d.ts.map +1 -0
- package/lib/components/stepper/stepper.component.d.ts +67 -0
- package/lib/components/stepper/stepper.component.d.ts.map +1 -0
- package/lib/components/stepper/stepper.types.d.ts +16 -0
- package/lib/components/stepper/stepper.types.d.ts.map +1 -0
- package/lib/components/tab-group/tab-group.component.d.ts +52 -0
- package/lib/components/tab-group/tab-group.component.d.ts.map +1 -0
- package/lib/components/tab-group/tab-group.types.d.ts +14 -0
- package/lib/components/tab-group/tab-group.types.d.ts.map +1 -0
- package/lib/i18n/keep-ui-translations.d.ts +4 -0
- package/lib/i18n/keep-ui-translations.d.ts.map +1 -1
- package/lib/i18n/translation-keys.d.ts +4 -0
- package/lib/i18n/translation-keys.d.ts.map +1 -1
- package/package.json +1 -1
- package/public-api.d.ts +10 -0
- package/public-api.d.ts.map +1 -1
- package/styles/index.css +1 -1
- package/styles/prebuilt.css +1 -1
package/fesm2022/keepui-ui.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, input, output, computed, ChangeDetectionStrategy, Component, inject, signal, makeEnvironmentProviders } from '@angular/core';
|
|
2
|
+
import { InjectionToken, Injectable, input, output, computed, ChangeDetectionStrategy, Component, inject, signal, DestroyRef, viewChild, model, effect, viewChildren, makeEnvironmentProviders } from '@angular/core';
|
|
3
3
|
import { TranslocoPipe, TRANSLOCO_SCOPE, TranslocoService, provideTransloco } from '@jsverse/transloco';
|
|
4
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
4
5
|
import { of } from 'rxjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -551,6 +552,10 @@ const KEEPUI_TRANSLATION_KEYS = {
|
|
|
551
552
|
PREVIEW_ALT: 'imagePreview.previewAlt',
|
|
552
553
|
ERROR_UNEXPECTED: 'imagePreview.errorUnexpected',
|
|
553
554
|
},
|
|
555
|
+
SIGNAL_TEXT_INPUT: {
|
|
556
|
+
SHOW_PASSWORD: 'signalTextInput.showPassword',
|
|
557
|
+
HIDE_PASSWORD: 'signalTextInput.hidePassword',
|
|
558
|
+
},
|
|
554
559
|
};
|
|
555
560
|
/** Ordered list of languages available in KeepUI. */
|
|
556
561
|
const KEEPUI_AVAILABLE_LANGUAGES = [
|
|
@@ -690,6 +695,1496 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
690
695
|
}]
|
|
691
696
|
}] });
|
|
692
697
|
|
|
698
|
+
const DROPDOWN_ITEM_HEIGHT = 40;
|
|
699
|
+
const DROPDOWN_PADDING = 8;
|
|
700
|
+
const DROPDOWN_GAP = 8;
|
|
701
|
+
/**
|
|
702
|
+
* Signal-based accessible dropdown / select component.
|
|
703
|
+
*
|
|
704
|
+
* Fully platform-agnostic — no native API usage. The panel opens in `fixed`
|
|
705
|
+
* position so it is never clipped by overflow-hidden ancestors. It repositions
|
|
706
|
+
* itself automatically on scroll and resize.
|
|
707
|
+
*
|
|
708
|
+
* The `value` and `touched` properties are `model()` signals so the component
|
|
709
|
+
* integrates seamlessly with Angular signal-based forms.
|
|
710
|
+
*
|
|
711
|
+
* ```html
|
|
712
|
+
* <keepui-signal-dropdown
|
|
713
|
+
* label="País"
|
|
714
|
+
* placeholder="Selecciona un país"
|
|
715
|
+
* [options]="countries"
|
|
716
|
+
* [(value)]="selectedCountry"
|
|
717
|
+
* />
|
|
718
|
+
* ```
|
|
719
|
+
*
|
|
720
|
+
* @typeParam T – type of each option's `value` property. Defaults to `string`.
|
|
721
|
+
*/
|
|
722
|
+
class SignalDropdownComponent {
|
|
723
|
+
constructor() {
|
|
724
|
+
this.destroyRef = inject(DestroyRef);
|
|
725
|
+
this.isOpen = signal(false);
|
|
726
|
+
this.openUpwards = signal(false);
|
|
727
|
+
this.panelTopPx = signal(0);
|
|
728
|
+
this.panelLeftPx = signal(0);
|
|
729
|
+
this.panelWidthPx = signal(0);
|
|
730
|
+
this.dropdownButton = viewChild('dropdownButton');
|
|
731
|
+
this.dropdownContainer = viewChild('dropdownContainer');
|
|
732
|
+
/** Optional label text rendered above the dropdown. */
|
|
733
|
+
this.label = input('');
|
|
734
|
+
/** Placeholder shown when no value is selected. */
|
|
735
|
+
this.placeholder = input('');
|
|
736
|
+
/** Array of options to display in the panel. */
|
|
737
|
+
this.options = input.required();
|
|
738
|
+
/** Layout width of the wrapper. @default 'full' */
|
|
739
|
+
this.width = input('full');
|
|
740
|
+
/** Marks the field as required. Adds `aria-required` and a visual asterisk. @default false */
|
|
741
|
+
this.required = input(false);
|
|
742
|
+
/** Human-readable error message shown below the dropdown. Takes precedence over `errors[0]`. */
|
|
743
|
+
this.errorMessage = input('');
|
|
744
|
+
/**
|
|
745
|
+
* Array of error strings. The first item is displayed when `errorMessage` is empty.
|
|
746
|
+
* Set together with `invalid=true` to trigger the error state.
|
|
747
|
+
*/
|
|
748
|
+
this.errors = input([]);
|
|
749
|
+
/**
|
|
750
|
+
* Stable `id` used to link the `<label>` with the trigger `<button>`.
|
|
751
|
+
* A random suffix is generated by default.
|
|
752
|
+
*/
|
|
753
|
+
this.selectId = input(`ku-dropdown-${Math.random().toString(36).slice(2, 8)}`);
|
|
754
|
+
/** Disables the dropdown. @default false */
|
|
755
|
+
this.disabled = input(false);
|
|
756
|
+
/**
|
|
757
|
+
* Forces the error visual state regardless of the `touched` model.
|
|
758
|
+
* Useful for external form validation. @default false
|
|
759
|
+
*/
|
|
760
|
+
this.invalid = input(false);
|
|
761
|
+
/** Currently selected value. Use `[(value)]` for two-way binding. */
|
|
762
|
+
this.value = model(null);
|
|
763
|
+
/** Whether the control has been interacted with. Use `[(touched)]` for two-way binding. */
|
|
764
|
+
this.touched = model(false);
|
|
765
|
+
/** Emitted when the selected value changes. */
|
|
766
|
+
this.valueChange = output();
|
|
767
|
+
this.errorId = computed(() => `${this.selectId()}-error`);
|
|
768
|
+
this.panelStyle = computed(() => `top:${this.panelTopPx()}px;left:${this.panelLeftPx()}px;width:${this.panelWidthPx()}px`);
|
|
769
|
+
this.widthClass = computed(() => {
|
|
770
|
+
const map = {
|
|
771
|
+
full: 'w-full',
|
|
772
|
+
half: 'w-1/2',
|
|
773
|
+
auto: 'w-auto',
|
|
774
|
+
};
|
|
775
|
+
return map[this.width()];
|
|
776
|
+
});
|
|
777
|
+
this.selectedLabel = computed(() => {
|
|
778
|
+
const current = this.value();
|
|
779
|
+
if (current === null)
|
|
780
|
+
return this.placeholder();
|
|
781
|
+
return this.options().find(o => o.value === current)?.label ?? this.placeholder();
|
|
782
|
+
});
|
|
783
|
+
this.selectedBadges = computed(() => {
|
|
784
|
+
const current = this.value();
|
|
785
|
+
if (current === null)
|
|
786
|
+
return [];
|
|
787
|
+
return this.options().find(o => o.value === current)?.badges ?? [];
|
|
788
|
+
});
|
|
789
|
+
this.showError = computed(() => this.touched() && (this.invalid() || this.errorMessage().length > 0 || this.errors().length > 0));
|
|
790
|
+
this.recalculatePosition = () => {
|
|
791
|
+
if (this.isOpen())
|
|
792
|
+
this.calculateDropdownPosition();
|
|
793
|
+
};
|
|
794
|
+
effect(() => {
|
|
795
|
+
if (this.isOpen()) {
|
|
796
|
+
this.calculateDropdownPosition();
|
|
797
|
+
this.addViewportListeners();
|
|
798
|
+
}
|
|
799
|
+
else {
|
|
800
|
+
this.removeViewportListeners();
|
|
801
|
+
}
|
|
802
|
+
});
|
|
803
|
+
this.destroyRef.onDestroy(() => this.removeViewportListeners());
|
|
804
|
+
}
|
|
805
|
+
onDocumentClick(event) {
|
|
806
|
+
const container = this.dropdownContainer()?.nativeElement;
|
|
807
|
+
if (container && !container.contains(event.target)) {
|
|
808
|
+
this.isOpen.set(false);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
toggleDropdown() {
|
|
812
|
+
if (this.disabled())
|
|
813
|
+
return;
|
|
814
|
+
this.isOpen.update(open => !open);
|
|
815
|
+
}
|
|
816
|
+
close() {
|
|
817
|
+
if (this.isOpen()) {
|
|
818
|
+
this.isOpen.set(false);
|
|
819
|
+
this.dropdownButton()?.nativeElement.focus();
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
selectOption(option) {
|
|
823
|
+
this.value.set(option.value);
|
|
824
|
+
this.isOpen.set(false);
|
|
825
|
+
this.dropdownButton()?.nativeElement.focus();
|
|
826
|
+
this.valueChange.emit(option.value);
|
|
827
|
+
}
|
|
828
|
+
onBlur() {
|
|
829
|
+
this.touched.set(true);
|
|
830
|
+
}
|
|
831
|
+
isSelected(optionValue) {
|
|
832
|
+
return this.value() === optionValue;
|
|
833
|
+
}
|
|
834
|
+
onButtonKeydown(event) {
|
|
835
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
836
|
+
event.preventDefault();
|
|
837
|
+
if (!this.isOpen()) {
|
|
838
|
+
this.isOpen.set(true);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
onOptionKeydown(event, option, index) {
|
|
843
|
+
const panel = this.dropdownContainer()?.nativeElement;
|
|
844
|
+
const optionButtons = panel?.querySelectorAll('[role="option"]');
|
|
845
|
+
if (!optionButtons)
|
|
846
|
+
return;
|
|
847
|
+
if (event.key === 'ArrowDown') {
|
|
848
|
+
event.preventDefault();
|
|
849
|
+
optionButtons[Math.min(index + 1, optionButtons.length - 1)]?.focus();
|
|
850
|
+
}
|
|
851
|
+
else if (event.key === 'ArrowUp') {
|
|
852
|
+
event.preventDefault();
|
|
853
|
+
if (index === 0) {
|
|
854
|
+
this.dropdownButton()?.nativeElement.focus();
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
optionButtons[index - 1]?.focus();
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
else if (event.key === 'Enter' || event.key === ' ') {
|
|
861
|
+
event.preventDefault();
|
|
862
|
+
this.selectOption(option);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
calculateDropdownPosition() {
|
|
866
|
+
const button = this.dropdownButton()?.nativeElement;
|
|
867
|
+
if (!button)
|
|
868
|
+
return;
|
|
869
|
+
const rect = button.getBoundingClientRect();
|
|
870
|
+
const estimatedHeight = Math.min(this.options().length * DROPDOWN_ITEM_HEIGHT + DROPDOWN_PADDING, 256);
|
|
871
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
872
|
+
const spaceAbove = rect.top;
|
|
873
|
+
const shouldOpenUpwards = spaceBelow < estimatedHeight + DROPDOWN_GAP &&
|
|
874
|
+
spaceAbove > estimatedHeight + DROPDOWN_GAP;
|
|
875
|
+
this.openUpwards.set(shouldOpenUpwards);
|
|
876
|
+
this.panelWidthPx.set(rect.width);
|
|
877
|
+
this.panelLeftPx.set(rect.left);
|
|
878
|
+
this.panelTopPx.set(shouldOpenUpwards
|
|
879
|
+
? rect.top - estimatedHeight - DROPDOWN_GAP
|
|
880
|
+
: rect.bottom + DROPDOWN_GAP);
|
|
881
|
+
}
|
|
882
|
+
addViewportListeners() {
|
|
883
|
+
this.removeViewportListeners();
|
|
884
|
+
window.addEventListener('scroll', this.recalculatePosition, true);
|
|
885
|
+
window.addEventListener('resize', this.recalculatePosition);
|
|
886
|
+
}
|
|
887
|
+
removeViewportListeners() {
|
|
888
|
+
window.removeEventListener('scroll', this.recalculatePosition, true);
|
|
889
|
+
window.removeEventListener('resize', this.recalculatePosition);
|
|
890
|
+
}
|
|
891
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SignalDropdownComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
892
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: SignalDropdownComponent, isStandalone: true, selector: "keepui-signal-dropdown", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: true, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, selectId: { classPropertyName: "selectId", publicName: "selectId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange", valueChange: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "close()" }, classAttribute: "block" }, viewQueries: [{ propertyName: "dropdownButton", first: true, predicate: ["dropdownButton"], descendants: true, isSignal: true }, { propertyName: "dropdownContainer", first: true, predicate: ["dropdownContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
893
|
+
<div class="flex flex-col gap-1" [class]="widthClass()">
|
|
894
|
+
|
|
895
|
+
@if (label()) {
|
|
896
|
+
<label [for]="selectId()" class="text-sm text-ku-gray-text">
|
|
897
|
+
{{ label() }}
|
|
898
|
+
@if (required()) {
|
|
899
|
+
<span class="text-ku-error-primary" aria-hidden="true"> *</span>
|
|
900
|
+
}
|
|
901
|
+
</label>
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
<div class="relative" #dropdownContainer>
|
|
905
|
+
|
|
906
|
+
<button
|
|
907
|
+
#dropdownButton
|
|
908
|
+
[id]="selectId()"
|
|
909
|
+
type="button"
|
|
910
|
+
[disabled]="disabled()"
|
|
911
|
+
[attr.aria-disabled]="disabled() ? true : null"
|
|
912
|
+
[attr.aria-expanded]="isOpen()"
|
|
913
|
+
[attr.aria-haspopup]="'listbox'"
|
|
914
|
+
[attr.aria-required]="required() ? true : null"
|
|
915
|
+
[attr.aria-invalid]="showError() ? true : null"
|
|
916
|
+
[attr.aria-describedby]="showError() ? errorId() : null"
|
|
917
|
+
(click)="toggleDropdown()"
|
|
918
|
+
(blur)="onBlur()"
|
|
919
|
+
(keydown)="onButtonKeydown($event)"
|
|
920
|
+
class="w-full inline-flex items-center gap-2 bg-ku-primary border rounded-xl
|
|
921
|
+
px-4 py-2.5 text-sm transition-all cursor-pointer min-h-[2.75rem]
|
|
922
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ku-action-primary
|
|
923
|
+
focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
924
|
+
[class]="showError() ? 'border-ku-error-primary' : 'border-ku-secondary-border'"
|
|
925
|
+
>
|
|
926
|
+
<span
|
|
927
|
+
class="flex-1 text-left flex items-center justify-between gap-2 min-w-0"
|
|
928
|
+
[class]="value() === null ? 'text-ku-gray-text opacity-70' : 'text-ku-primary-text'"
|
|
929
|
+
>
|
|
930
|
+
<span class="truncate">{{ selectedLabel() }}</span>
|
|
931
|
+
|
|
932
|
+
@if (selectedBadges().length) {
|
|
933
|
+
<span class="flex flex-wrap items-center gap-1.5 shrink-0">
|
|
934
|
+
@for (badge of selectedBadges(); track badge) {
|
|
935
|
+
<span class="inline-flex items-center rounded-full bg-ku-action-background
|
|
936
|
+
px-2 py-0.5 text-[11px] font-medium text-ku-action-primary">
|
|
937
|
+
{{ badge }}
|
|
938
|
+
</span>
|
|
939
|
+
}
|
|
940
|
+
</span>
|
|
941
|
+
}
|
|
942
|
+
</span>
|
|
943
|
+
|
|
944
|
+
<keepui-icon
|
|
945
|
+
name="chevron-down-icon"
|
|
946
|
+
[size]="18"
|
|
947
|
+
aria-hidden="true"
|
|
948
|
+
class="text-ku-gray-text transition-transform duration-200 shrink-0"
|
|
949
|
+
[class.rotate-180]="isOpen()"
|
|
950
|
+
/>
|
|
951
|
+
</button>
|
|
952
|
+
|
|
953
|
+
@if (isOpen()) {
|
|
954
|
+
<div
|
|
955
|
+
role="listbox"
|
|
956
|
+
[attr.aria-label]="label() || null"
|
|
957
|
+
class="fixed rounded-xl shadow-lg bg-ku-primary border border-ku-secondary-border
|
|
958
|
+
z-[1000] overflow-y-auto max-h-64"
|
|
959
|
+
[style]="panelStyle()"
|
|
960
|
+
>
|
|
961
|
+
@for (option of options(); track option.value; let i = $index) {
|
|
962
|
+
<button
|
|
963
|
+
type="button"
|
|
964
|
+
role="option"
|
|
965
|
+
[attr.aria-selected]="isSelected(option.value)"
|
|
966
|
+
(click)="selectOption(option)"
|
|
967
|
+
(keydown)="onOptionKeydown($event, option, i)"
|
|
968
|
+
class="w-full text-left cursor-pointer px-4 py-2 text-sm text-ku-primary-text
|
|
969
|
+
flex items-center gap-3 transition-colors min-h-[2.75rem]
|
|
970
|
+
hover:bg-ku-primary-hover focus-visible:outline-none
|
|
971
|
+
focus-visible:bg-ku-primary-hover"
|
|
972
|
+
[class]="isSelected(option.value) ? 'bg-ku-primary-hover' : ''"
|
|
973
|
+
>
|
|
974
|
+
<span class="flex-1 flex items-center justify-between gap-2 min-w-0">
|
|
975
|
+
<span class="truncate">{{ option.label }}</span>
|
|
976
|
+
|
|
977
|
+
@if (option.badges?.length) {
|
|
978
|
+
<span class="flex flex-wrap items-center gap-1.5 shrink-0">
|
|
979
|
+
@for (badge of option.badges ?? []; track badge) {
|
|
980
|
+
<span class="inline-flex items-center rounded-full bg-ku-action-background
|
|
981
|
+
px-2 py-0.5 text-[11px] font-medium text-ku-action-primary">
|
|
982
|
+
{{ badge }}
|
|
983
|
+
</span>
|
|
984
|
+
}
|
|
985
|
+
</span>
|
|
986
|
+
}
|
|
987
|
+
</span>
|
|
988
|
+
|
|
989
|
+
@if (isSelected(option.value)) {
|
|
990
|
+
<keepui-icon
|
|
991
|
+
name="check-icon"
|
|
992
|
+
[size]="16"
|
|
993
|
+
aria-hidden="true"
|
|
994
|
+
class="text-ku-action-primary shrink-0"
|
|
995
|
+
/>
|
|
996
|
+
}
|
|
997
|
+
</button>
|
|
998
|
+
}
|
|
999
|
+
</div>
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
</div>
|
|
1003
|
+
|
|
1004
|
+
@if (showError()) {
|
|
1005
|
+
<span
|
|
1006
|
+
[id]="errorId()"
|
|
1007
|
+
class="text-sm text-ku-error-primary"
|
|
1008
|
+
role="alert"
|
|
1009
|
+
>
|
|
1010
|
+
@if (errorMessage()) {
|
|
1011
|
+
{{ errorMessage() }}
|
|
1012
|
+
} @else if (errors().length > 0) {
|
|
1013
|
+
{{ errors()[0] }}
|
|
1014
|
+
}
|
|
1015
|
+
</span>
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
</div>
|
|
1019
|
+
`, isInline: true, dependencies: [{ kind: "component", type: IconComponent, selector: "keepui-icon", inputs: ["name", "size", "viewBox", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1020
|
+
}
|
|
1021
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SignalDropdownComponent, decorators: [{
|
|
1022
|
+
type: Component,
|
|
1023
|
+
args: [{
|
|
1024
|
+
selector: 'keepui-signal-dropdown',
|
|
1025
|
+
standalone: true,
|
|
1026
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1027
|
+
imports: [IconComponent],
|
|
1028
|
+
host: {
|
|
1029
|
+
class: 'block',
|
|
1030
|
+
'(document:click)': 'onDocumentClick($event)',
|
|
1031
|
+
'(document:keydown.escape)': 'close()',
|
|
1032
|
+
},
|
|
1033
|
+
template: `
|
|
1034
|
+
<div class="flex flex-col gap-1" [class]="widthClass()">
|
|
1035
|
+
|
|
1036
|
+
@if (label()) {
|
|
1037
|
+
<label [for]="selectId()" class="text-sm text-ku-gray-text">
|
|
1038
|
+
{{ label() }}
|
|
1039
|
+
@if (required()) {
|
|
1040
|
+
<span class="text-ku-error-primary" aria-hidden="true"> *</span>
|
|
1041
|
+
}
|
|
1042
|
+
</label>
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
<div class="relative" #dropdownContainer>
|
|
1046
|
+
|
|
1047
|
+
<button
|
|
1048
|
+
#dropdownButton
|
|
1049
|
+
[id]="selectId()"
|
|
1050
|
+
type="button"
|
|
1051
|
+
[disabled]="disabled()"
|
|
1052
|
+
[attr.aria-disabled]="disabled() ? true : null"
|
|
1053
|
+
[attr.aria-expanded]="isOpen()"
|
|
1054
|
+
[attr.aria-haspopup]="'listbox'"
|
|
1055
|
+
[attr.aria-required]="required() ? true : null"
|
|
1056
|
+
[attr.aria-invalid]="showError() ? true : null"
|
|
1057
|
+
[attr.aria-describedby]="showError() ? errorId() : null"
|
|
1058
|
+
(click)="toggleDropdown()"
|
|
1059
|
+
(blur)="onBlur()"
|
|
1060
|
+
(keydown)="onButtonKeydown($event)"
|
|
1061
|
+
class="w-full inline-flex items-center gap-2 bg-ku-primary border rounded-xl
|
|
1062
|
+
px-4 py-2.5 text-sm transition-all cursor-pointer min-h-[2.75rem]
|
|
1063
|
+
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ku-action-primary
|
|
1064
|
+
focus-visible:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1065
|
+
[class]="showError() ? 'border-ku-error-primary' : 'border-ku-secondary-border'"
|
|
1066
|
+
>
|
|
1067
|
+
<span
|
|
1068
|
+
class="flex-1 text-left flex items-center justify-between gap-2 min-w-0"
|
|
1069
|
+
[class]="value() === null ? 'text-ku-gray-text opacity-70' : 'text-ku-primary-text'"
|
|
1070
|
+
>
|
|
1071
|
+
<span class="truncate">{{ selectedLabel() }}</span>
|
|
1072
|
+
|
|
1073
|
+
@if (selectedBadges().length) {
|
|
1074
|
+
<span class="flex flex-wrap items-center gap-1.5 shrink-0">
|
|
1075
|
+
@for (badge of selectedBadges(); track badge) {
|
|
1076
|
+
<span class="inline-flex items-center rounded-full bg-ku-action-background
|
|
1077
|
+
px-2 py-0.5 text-[11px] font-medium text-ku-action-primary">
|
|
1078
|
+
{{ badge }}
|
|
1079
|
+
</span>
|
|
1080
|
+
}
|
|
1081
|
+
</span>
|
|
1082
|
+
}
|
|
1083
|
+
</span>
|
|
1084
|
+
|
|
1085
|
+
<keepui-icon
|
|
1086
|
+
name="chevron-down-icon"
|
|
1087
|
+
[size]="18"
|
|
1088
|
+
aria-hidden="true"
|
|
1089
|
+
class="text-ku-gray-text transition-transform duration-200 shrink-0"
|
|
1090
|
+
[class.rotate-180]="isOpen()"
|
|
1091
|
+
/>
|
|
1092
|
+
</button>
|
|
1093
|
+
|
|
1094
|
+
@if (isOpen()) {
|
|
1095
|
+
<div
|
|
1096
|
+
role="listbox"
|
|
1097
|
+
[attr.aria-label]="label() || null"
|
|
1098
|
+
class="fixed rounded-xl shadow-lg bg-ku-primary border border-ku-secondary-border
|
|
1099
|
+
z-[1000] overflow-y-auto max-h-64"
|
|
1100
|
+
[style]="panelStyle()"
|
|
1101
|
+
>
|
|
1102
|
+
@for (option of options(); track option.value; let i = $index) {
|
|
1103
|
+
<button
|
|
1104
|
+
type="button"
|
|
1105
|
+
role="option"
|
|
1106
|
+
[attr.aria-selected]="isSelected(option.value)"
|
|
1107
|
+
(click)="selectOption(option)"
|
|
1108
|
+
(keydown)="onOptionKeydown($event, option, i)"
|
|
1109
|
+
class="w-full text-left cursor-pointer px-4 py-2 text-sm text-ku-primary-text
|
|
1110
|
+
flex items-center gap-3 transition-colors min-h-[2.75rem]
|
|
1111
|
+
hover:bg-ku-primary-hover focus-visible:outline-none
|
|
1112
|
+
focus-visible:bg-ku-primary-hover"
|
|
1113
|
+
[class]="isSelected(option.value) ? 'bg-ku-primary-hover' : ''"
|
|
1114
|
+
>
|
|
1115
|
+
<span class="flex-1 flex items-center justify-between gap-2 min-w-0">
|
|
1116
|
+
<span class="truncate">{{ option.label }}</span>
|
|
1117
|
+
|
|
1118
|
+
@if (option.badges?.length) {
|
|
1119
|
+
<span class="flex flex-wrap items-center gap-1.5 shrink-0">
|
|
1120
|
+
@for (badge of option.badges ?? []; track badge) {
|
|
1121
|
+
<span class="inline-flex items-center rounded-full bg-ku-action-background
|
|
1122
|
+
px-2 py-0.5 text-[11px] font-medium text-ku-action-primary">
|
|
1123
|
+
{{ badge }}
|
|
1124
|
+
</span>
|
|
1125
|
+
}
|
|
1126
|
+
</span>
|
|
1127
|
+
}
|
|
1128
|
+
</span>
|
|
1129
|
+
|
|
1130
|
+
@if (isSelected(option.value)) {
|
|
1131
|
+
<keepui-icon
|
|
1132
|
+
name="check-icon"
|
|
1133
|
+
[size]="16"
|
|
1134
|
+
aria-hidden="true"
|
|
1135
|
+
class="text-ku-action-primary shrink-0"
|
|
1136
|
+
/>
|
|
1137
|
+
}
|
|
1138
|
+
</button>
|
|
1139
|
+
}
|
|
1140
|
+
</div>
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
</div>
|
|
1144
|
+
|
|
1145
|
+
@if (showError()) {
|
|
1146
|
+
<span
|
|
1147
|
+
[id]="errorId()"
|
|
1148
|
+
class="text-sm text-ku-error-primary"
|
|
1149
|
+
role="alert"
|
|
1150
|
+
>
|
|
1151
|
+
@if (errorMessage()) {
|
|
1152
|
+
{{ errorMessage() }}
|
|
1153
|
+
} @else if (errors().length > 0) {
|
|
1154
|
+
{{ errors()[0] }}
|
|
1155
|
+
}
|
|
1156
|
+
</span>
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
</div>
|
|
1160
|
+
`,
|
|
1161
|
+
}]
|
|
1162
|
+
}], ctorParameters: () => [] });
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Signal-based accessible text input supporting all common HTML input types,
|
|
1166
|
+
* leading/trailing icons, a trailing content slot, and a built-in
|
|
1167
|
+
* password-visibility toggle (when `type="password"`).
|
|
1168
|
+
*
|
|
1169
|
+
* The `value` and `touched` properties are `model()` signals so the component
|
|
1170
|
+
* integrates seamlessly with Angular signal-based forms.
|
|
1171
|
+
*
|
|
1172
|
+
* Password toggle labels are translated via Transloco (scope `'keepui'`).
|
|
1173
|
+
* Call `provideKeepUiI18n()` in your `app.config.ts` to activate i18n.
|
|
1174
|
+
*
|
|
1175
|
+
* ```html
|
|
1176
|
+
* <keepui-signal-text-input
|
|
1177
|
+
* label="Email"
|
|
1178
|
+
* type="email"
|
|
1179
|
+
* placeholder="usuario@ejemplo.com"
|
|
1180
|
+
* leadingIcon="mail-icon"
|
|
1181
|
+
* [(value)]="email"
|
|
1182
|
+
* />
|
|
1183
|
+
*
|
|
1184
|
+
* <!-- Password with toggle -->
|
|
1185
|
+
* <keepui-signal-text-input
|
|
1186
|
+
* label="Contraseña"
|
|
1187
|
+
* type="password"
|
|
1188
|
+
* [(value)]="password"
|
|
1189
|
+
* />
|
|
1190
|
+
* ```
|
|
1191
|
+
*/
|
|
1192
|
+
class SignalTextInputComponent {
|
|
1193
|
+
constructor() {
|
|
1194
|
+
this.isPasswordVisible = signal(false);
|
|
1195
|
+
this.inputRef = viewChild('inputRef');
|
|
1196
|
+
/** Translation key references (typed via KEEPUI_TRANSLATION_KEYS). */
|
|
1197
|
+
this.keys = KEEPUI_TRANSLATION_KEYS.SIGNAL_TEXT_INPUT;
|
|
1198
|
+
/** Optional label text rendered above the input. */
|
|
1199
|
+
this.label = input('');
|
|
1200
|
+
/** Placeholder passed to the underlying `<input>`. */
|
|
1201
|
+
this.placeholder = input('');
|
|
1202
|
+
/** HTML `type` attribute. Use `'password'` to enable the visibility toggle. @default 'text' */
|
|
1203
|
+
this.type = input('text');
|
|
1204
|
+
/** Layout width of the wrapper. @default 'full' */
|
|
1205
|
+
this.width = input('full');
|
|
1206
|
+
/** Name of the leading icon (SVG symbol ID). Leave empty for no icon. */
|
|
1207
|
+
this.leadingIcon = input('');
|
|
1208
|
+
/** Name of the trailing icon (SVG symbol ID). Ignored when `type="password"`. */
|
|
1209
|
+
this.trailingIcon = input('');
|
|
1210
|
+
/**
|
|
1211
|
+
* When `true`, a slot for custom trailing content is enabled
|
|
1212
|
+
* (projects `[trailingSlot]` content). Overrides `trailingIcon`.
|
|
1213
|
+
* @default false
|
|
1214
|
+
*/
|
|
1215
|
+
this.hasTrailingSlot = input(false);
|
|
1216
|
+
/** Marks the field as required. Adds `aria-required` and a visual asterisk. @default false */
|
|
1217
|
+
this.required = input(false);
|
|
1218
|
+
/** When `false`, hides the visual asterisk even if `required=true`. @default true */
|
|
1219
|
+
this.showRequiredIndicator = input(true);
|
|
1220
|
+
/** Human-readable error message. Takes precedence over `errors[0]`. */
|
|
1221
|
+
this.errorMessage = input('');
|
|
1222
|
+
/**
|
|
1223
|
+
* Array of error strings. The first item is displayed when `errorMessage` is empty.
|
|
1224
|
+
* Set together with `invalid=true` to trigger the error state.
|
|
1225
|
+
*/
|
|
1226
|
+
this.errors = input([]);
|
|
1227
|
+
/**
|
|
1228
|
+
* Stable `id` used to link the `<label>` with the `<input>`.
|
|
1229
|
+
* A random suffix is generated by default.
|
|
1230
|
+
*/
|
|
1231
|
+
this.inputId = input(`ku-text-input-${Math.random().toString(36).slice(2, 8)}`);
|
|
1232
|
+
/** Disables the input. @default false */
|
|
1233
|
+
this.disabled = input(false);
|
|
1234
|
+
/**
|
|
1235
|
+
* Forces the error visual state regardless of the `touched` model.
|
|
1236
|
+
* Useful for external form validation. @default false
|
|
1237
|
+
*/
|
|
1238
|
+
this.invalid = input(false);
|
|
1239
|
+
/** Current text value. Use `[(value)]` for two-way binding. */
|
|
1240
|
+
this.value = model('');
|
|
1241
|
+
/** Whether the control has been interacted with. Use `[(touched)]` for two-way binding. */
|
|
1242
|
+
this.touched = model(false);
|
|
1243
|
+
this.errorId = computed(() => `${this.inputId()}-error`);
|
|
1244
|
+
this.isPasswordType = computed(() => this.type() === 'password');
|
|
1245
|
+
this.resolvedType = computed(() => {
|
|
1246
|
+
if (this.isPasswordType()) {
|
|
1247
|
+
return this.isPasswordVisible() ? 'text' : 'password';
|
|
1248
|
+
}
|
|
1249
|
+
return this.type();
|
|
1250
|
+
});
|
|
1251
|
+
this.widthClass = computed(() => {
|
|
1252
|
+
const map = {
|
|
1253
|
+
full: 'w-full',
|
|
1254
|
+
half: 'w-1/2',
|
|
1255
|
+
auto: 'w-auto',
|
|
1256
|
+
};
|
|
1257
|
+
return map[this.width()];
|
|
1258
|
+
});
|
|
1259
|
+
this.hasLeading = computed(() => this.leadingIcon().length > 0);
|
|
1260
|
+
this.hasTrailing = computed(() => this.trailingIcon().length > 0 && !this.isPasswordType());
|
|
1261
|
+
this.showError = computed(() => this.touched() &&
|
|
1262
|
+
(this.invalid() || this.errorMessage().length > 0 || this.errors().length > 0));
|
|
1263
|
+
this.isDateEmpty = computed(() => this.type() === 'date' && !this.value());
|
|
1264
|
+
this.inputClasses = computed(() => {
|
|
1265
|
+
const textColor = this.isDateEmpty()
|
|
1266
|
+
? 'text-ku-gray-text'
|
|
1267
|
+
: 'text-ku-primary-text';
|
|
1268
|
+
const paddingLeft = this.hasLeading() ? 'pl-9' : 'pl-4';
|
|
1269
|
+
const paddingRight = this.hasTrailingSlot()
|
|
1270
|
+
? 'pr-24'
|
|
1271
|
+
: this.hasTrailing() || this.isPasswordType()
|
|
1272
|
+
? 'pr-10'
|
|
1273
|
+
: 'pr-4';
|
|
1274
|
+
const borderColor = this.showError()
|
|
1275
|
+
? 'border-ku-error-primary'
|
|
1276
|
+
: 'border-ku-secondary-border focus-visible:border-ku-action-primary';
|
|
1277
|
+
const cursor = this.type() === 'date' ? 'cursor-pointer' : '';
|
|
1278
|
+
return `${textColor} ${paddingLeft} ${paddingRight} ${borderColor} ${cursor}`;
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
onInput(event) {
|
|
1282
|
+
const target = event.target;
|
|
1283
|
+
this.value.set(target.value);
|
|
1284
|
+
}
|
|
1285
|
+
onBlur() {
|
|
1286
|
+
this.touched.set(true);
|
|
1287
|
+
}
|
|
1288
|
+
openDatePicker() {
|
|
1289
|
+
if (this.type() === 'date') {
|
|
1290
|
+
try {
|
|
1291
|
+
this.inputRef()?.nativeElement.showPicker();
|
|
1292
|
+
}
|
|
1293
|
+
catch { /* showPicker not supported in all browsers */ }
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
togglePasswordVisibility() {
|
|
1297
|
+
this.isPasswordVisible.update(v => !v);
|
|
1298
|
+
}
|
|
1299
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SignalTextInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1300
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: SignalTextInputComponent, isStandalone: true, selector: "keepui-signal-text-input", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, leadingIcon: { classPropertyName: "leadingIcon", publicName: "leadingIcon", isSignal: true, isRequired: false, transformFunction: null }, trailingIcon: { classPropertyName: "trailingIcon", publicName: "trailingIcon", isSignal: true, isRequired: false, transformFunction: null }, hasTrailingSlot: { classPropertyName: "hasTrailingSlot", publicName: "hasTrailingSlot", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showRequiredIndicator: { classPropertyName: "showRequiredIndicator", publicName: "showRequiredIndicator", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, inputId: { classPropertyName: "inputId", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { classAttribute: "block" }, providers: [{ provide: TRANSLOCO_SCOPE, useValue: 'keepui' }], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputRef"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1301
|
+
<div class="flex flex-col gap-1" [class]="widthClass()">
|
|
1302
|
+
|
|
1303
|
+
@if (label()) {
|
|
1304
|
+
<label [for]="inputId()" class="text-sm text-ku-gray-text">
|
|
1305
|
+
{{ label() }}
|
|
1306
|
+
@if (required() && showRequiredIndicator()) {
|
|
1307
|
+
<span class="text-ku-error-primary" aria-hidden="true"> *</span>
|
|
1308
|
+
}
|
|
1309
|
+
</label>
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
<div class="relative flex items-center">
|
|
1313
|
+
|
|
1314
|
+
@if (hasLeading()) {
|
|
1315
|
+
<span
|
|
1316
|
+
class="absolute left-3 flex items-center pointer-events-none text-ku-gray-text"
|
|
1317
|
+
aria-hidden="true"
|
|
1318
|
+
>
|
|
1319
|
+
<keepui-icon [name]="leadingIcon()" [size]="18" />
|
|
1320
|
+
</span>
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
<input
|
|
1324
|
+
#inputRef
|
|
1325
|
+
[id]="inputId()"
|
|
1326
|
+
[type]="resolvedType()"
|
|
1327
|
+
[value]="value()"
|
|
1328
|
+
[placeholder]="placeholder()"
|
|
1329
|
+
[disabled]="disabled()"
|
|
1330
|
+
[attr.required]="required() ? true : null"
|
|
1331
|
+
[attr.aria-required]="required() ? true : null"
|
|
1332
|
+
[attr.aria-invalid]="showError() ? true : null"
|
|
1333
|
+
[attr.aria-describedby]="showError() ? errorId() : null"
|
|
1334
|
+
class="bg-ku-primary border rounded-xl py-2.5 text-sm text-ku-primary-text
|
|
1335
|
+
placeholder:text-ku-gray-text outline-none transition-colors w-full
|
|
1336
|
+
disabled:opacity-50 disabled:cursor-not-allowed min-h-[2.75rem]
|
|
1337
|
+
dark:[color-scheme:dark]"
|
|
1338
|
+
[class]="inputClasses()"
|
|
1339
|
+
(click)="openDatePicker()"
|
|
1340
|
+
(input)="onInput($event)"
|
|
1341
|
+
(blur)="onBlur()"
|
|
1342
|
+
/>
|
|
1343
|
+
|
|
1344
|
+
@if (hasTrailingSlot()) {
|
|
1345
|
+
<div class="absolute right-1 flex items-center">
|
|
1346
|
+
<ng-content select="[trailingSlot]" />
|
|
1347
|
+
</div>
|
|
1348
|
+
} @else if (isPasswordType()) {
|
|
1349
|
+
<button
|
|
1350
|
+
type="button"
|
|
1351
|
+
(click)="togglePasswordVisibility()"
|
|
1352
|
+
class="absolute right-3 flex items-center justify-center text-ku-gray-text
|
|
1353
|
+
hover:text-ku-primary-text transition-colors cursor-pointer
|
|
1354
|
+
focus-visible:outline-none focus-visible:ring-2
|
|
1355
|
+
focus-visible:ring-ku-action-primary rounded min-h-[2.75rem] min-w-[2.75rem]"
|
|
1356
|
+
[attr.aria-label]="(isPasswordVisible()
|
|
1357
|
+
? keys.HIDE_PASSWORD
|
|
1358
|
+
: keys.SHOW_PASSWORD) | transloco"
|
|
1359
|
+
>
|
|
1360
|
+
<keepui-icon
|
|
1361
|
+
[name]="isPasswordVisible() ? 'eye-off-icon' : 'eye-icon'"
|
|
1362
|
+
[size]="18"
|
|
1363
|
+
aria-hidden="true"
|
|
1364
|
+
/>
|
|
1365
|
+
</button>
|
|
1366
|
+
} @else if (hasTrailing()) {
|
|
1367
|
+
<span
|
|
1368
|
+
class="absolute right-3 flex items-center pointer-events-none text-ku-gray-text"
|
|
1369
|
+
aria-hidden="true"
|
|
1370
|
+
>
|
|
1371
|
+
<keepui-icon [name]="trailingIcon()" [size]="18" />
|
|
1372
|
+
</span>
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
</div>
|
|
1376
|
+
|
|
1377
|
+
@if (showError()) {
|
|
1378
|
+
<span
|
|
1379
|
+
[id]="errorId()"
|
|
1380
|
+
class="text-sm text-ku-error-primary"
|
|
1381
|
+
role="alert"
|
|
1382
|
+
>
|
|
1383
|
+
@if (errorMessage()) {
|
|
1384
|
+
{{ errorMessage() }}
|
|
1385
|
+
} @else if (errors().length > 0) {
|
|
1386
|
+
{{ errors()[0] }}
|
|
1387
|
+
}
|
|
1388
|
+
</span>
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
</div>
|
|
1392
|
+
`, isInline: true, dependencies: [{ kind: "component", type: IconComponent, selector: "keepui-icon", inputs: ["name", "size", "viewBox", "ariaLabel"] }, { kind: "pipe", type: TranslocoPipe, name: "transloco" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1393
|
+
}
|
|
1394
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SignalTextInputComponent, decorators: [{
|
|
1395
|
+
type: Component,
|
|
1396
|
+
args: [{
|
|
1397
|
+
selector: 'keepui-signal-text-input',
|
|
1398
|
+
standalone: true,
|
|
1399
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1400
|
+
imports: [IconComponent, TranslocoPipe],
|
|
1401
|
+
providers: [{ provide: TRANSLOCO_SCOPE, useValue: 'keepui' }],
|
|
1402
|
+
host: { class: 'block' },
|
|
1403
|
+
template: `
|
|
1404
|
+
<div class="flex flex-col gap-1" [class]="widthClass()">
|
|
1405
|
+
|
|
1406
|
+
@if (label()) {
|
|
1407
|
+
<label [for]="inputId()" class="text-sm text-ku-gray-text">
|
|
1408
|
+
{{ label() }}
|
|
1409
|
+
@if (required() && showRequiredIndicator()) {
|
|
1410
|
+
<span class="text-ku-error-primary" aria-hidden="true"> *</span>
|
|
1411
|
+
}
|
|
1412
|
+
</label>
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
<div class="relative flex items-center">
|
|
1416
|
+
|
|
1417
|
+
@if (hasLeading()) {
|
|
1418
|
+
<span
|
|
1419
|
+
class="absolute left-3 flex items-center pointer-events-none text-ku-gray-text"
|
|
1420
|
+
aria-hidden="true"
|
|
1421
|
+
>
|
|
1422
|
+
<keepui-icon [name]="leadingIcon()" [size]="18" />
|
|
1423
|
+
</span>
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
<input
|
|
1427
|
+
#inputRef
|
|
1428
|
+
[id]="inputId()"
|
|
1429
|
+
[type]="resolvedType()"
|
|
1430
|
+
[value]="value()"
|
|
1431
|
+
[placeholder]="placeholder()"
|
|
1432
|
+
[disabled]="disabled()"
|
|
1433
|
+
[attr.required]="required() ? true : null"
|
|
1434
|
+
[attr.aria-required]="required() ? true : null"
|
|
1435
|
+
[attr.aria-invalid]="showError() ? true : null"
|
|
1436
|
+
[attr.aria-describedby]="showError() ? errorId() : null"
|
|
1437
|
+
class="bg-ku-primary border rounded-xl py-2.5 text-sm text-ku-primary-text
|
|
1438
|
+
placeholder:text-ku-gray-text outline-none transition-colors w-full
|
|
1439
|
+
disabled:opacity-50 disabled:cursor-not-allowed min-h-[2.75rem]
|
|
1440
|
+
dark:[color-scheme:dark]"
|
|
1441
|
+
[class]="inputClasses()"
|
|
1442
|
+
(click)="openDatePicker()"
|
|
1443
|
+
(input)="onInput($event)"
|
|
1444
|
+
(blur)="onBlur()"
|
|
1445
|
+
/>
|
|
1446
|
+
|
|
1447
|
+
@if (hasTrailingSlot()) {
|
|
1448
|
+
<div class="absolute right-1 flex items-center">
|
|
1449
|
+
<ng-content select="[trailingSlot]" />
|
|
1450
|
+
</div>
|
|
1451
|
+
} @else if (isPasswordType()) {
|
|
1452
|
+
<button
|
|
1453
|
+
type="button"
|
|
1454
|
+
(click)="togglePasswordVisibility()"
|
|
1455
|
+
class="absolute right-3 flex items-center justify-center text-ku-gray-text
|
|
1456
|
+
hover:text-ku-primary-text transition-colors cursor-pointer
|
|
1457
|
+
focus-visible:outline-none focus-visible:ring-2
|
|
1458
|
+
focus-visible:ring-ku-action-primary rounded min-h-[2.75rem] min-w-[2.75rem]"
|
|
1459
|
+
[attr.aria-label]="(isPasswordVisible()
|
|
1460
|
+
? keys.HIDE_PASSWORD
|
|
1461
|
+
: keys.SHOW_PASSWORD) | transloco"
|
|
1462
|
+
>
|
|
1463
|
+
<keepui-icon
|
|
1464
|
+
[name]="isPasswordVisible() ? 'eye-off-icon' : 'eye-icon'"
|
|
1465
|
+
[size]="18"
|
|
1466
|
+
aria-hidden="true"
|
|
1467
|
+
/>
|
|
1468
|
+
</button>
|
|
1469
|
+
} @else if (hasTrailing()) {
|
|
1470
|
+
<span
|
|
1471
|
+
class="absolute right-3 flex items-center pointer-events-none text-ku-gray-text"
|
|
1472
|
+
aria-hidden="true"
|
|
1473
|
+
>
|
|
1474
|
+
<keepui-icon [name]="trailingIcon()" [size]="18" />
|
|
1475
|
+
</span>
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
</div>
|
|
1479
|
+
|
|
1480
|
+
@if (showError()) {
|
|
1481
|
+
<span
|
|
1482
|
+
[id]="errorId()"
|
|
1483
|
+
class="text-sm text-ku-error-primary"
|
|
1484
|
+
role="alert"
|
|
1485
|
+
>
|
|
1486
|
+
@if (errorMessage()) {
|
|
1487
|
+
{{ errorMessage() }}
|
|
1488
|
+
} @else if (errors().length > 0) {
|
|
1489
|
+
{{ errors()[0] }}
|
|
1490
|
+
}
|
|
1491
|
+
</span>
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
</div>
|
|
1495
|
+
`,
|
|
1496
|
+
}]
|
|
1497
|
+
}] });
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Signal-based accessible textarea component with character counter,
|
|
1501
|
+
* resize control, and integrated error display.
|
|
1502
|
+
*
|
|
1503
|
+
* The `value` and `touched` properties are `model()` signals so the component
|
|
1504
|
+
* integrates seamlessly with Angular signal-based forms.
|
|
1505
|
+
*
|
|
1506
|
+
* ```html
|
|
1507
|
+
* <keepui-signal-textarea
|
|
1508
|
+
* label="Descripción"
|
|
1509
|
+
* placeholder="Escribe aquí…"
|
|
1510
|
+
* [rows]="5"
|
|
1511
|
+
* [maxLength]="500"
|
|
1512
|
+
* [(value)]="description"
|
|
1513
|
+
* />
|
|
1514
|
+
* ```
|
|
1515
|
+
*/
|
|
1516
|
+
class SignalTextareaComponent {
|
|
1517
|
+
constructor() {
|
|
1518
|
+
/** Optional label text rendered above the textarea. */
|
|
1519
|
+
this.label = input('');
|
|
1520
|
+
/** Placeholder passed to the underlying `<textarea>`. */
|
|
1521
|
+
this.placeholder = input('');
|
|
1522
|
+
/** Number of visible text rows. @default 4 */
|
|
1523
|
+
this.rows = input(4);
|
|
1524
|
+
/** Layout width of the wrapper. @default 'full' */
|
|
1525
|
+
this.width = input('full');
|
|
1526
|
+
/** CSS `resize` behaviour. @default 'none' */
|
|
1527
|
+
this.resize = input('none');
|
|
1528
|
+
/** Marks the field as required. @default false */
|
|
1529
|
+
this.required = input(false);
|
|
1530
|
+
/** Human-readable error message. Takes precedence over `errors[0]`. */
|
|
1531
|
+
this.errorMessage = input('');
|
|
1532
|
+
/**
|
|
1533
|
+
* Array of error strings. The first item is displayed when `errorMessage` is empty.
|
|
1534
|
+
* Set together with `invalid=true` to trigger the error state.
|
|
1535
|
+
*/
|
|
1536
|
+
this.errors = input([]);
|
|
1537
|
+
/**
|
|
1538
|
+
* Stable `id` used to link the `<label>` with the `<textarea>`.
|
|
1539
|
+
* A random suffix is generated by default.
|
|
1540
|
+
*/
|
|
1541
|
+
this.textareaId = input(`ku-textarea-${Math.random().toString(36).slice(2, 8)}`);
|
|
1542
|
+
/** Disables the textarea. @default false */
|
|
1543
|
+
this.disabled = input(false);
|
|
1544
|
+
/** Maximum number of characters allowed. Shows a character counter when set. */
|
|
1545
|
+
this.maxLength = input(undefined);
|
|
1546
|
+
/**
|
|
1547
|
+
* Forces the error visual state regardless of the `touched` model.
|
|
1548
|
+
* Useful for external form validation. @default false
|
|
1549
|
+
*/
|
|
1550
|
+
this.invalid = input(false);
|
|
1551
|
+
/** Current text value. Use `[(value)]` for two-way binding. */
|
|
1552
|
+
this.value = model('');
|
|
1553
|
+
/** Whether the control has been interacted with. Use `[(touched)]` for two-way binding. */
|
|
1554
|
+
this.touched = model(false);
|
|
1555
|
+
this.errorId = computed(() => `${this.textareaId()}-error`);
|
|
1556
|
+
this.charCountId = computed(() => `${this.textareaId()}-count`);
|
|
1557
|
+
this.charCount = computed(() => this.value().length);
|
|
1558
|
+
this.showError = computed(() => this.touched() &&
|
|
1559
|
+
(this.invalid() || this.errorMessage().length > 0 || this.errors().length > 0));
|
|
1560
|
+
this.widthClass = computed(() => {
|
|
1561
|
+
const map = {
|
|
1562
|
+
full: 'w-full',
|
|
1563
|
+
half: 'w-1/2',
|
|
1564
|
+
auto: 'w-auto',
|
|
1565
|
+
};
|
|
1566
|
+
return map[this.width()];
|
|
1567
|
+
});
|
|
1568
|
+
this.resizeClass = computed(() => {
|
|
1569
|
+
const map = {
|
|
1570
|
+
none: 'resize-none',
|
|
1571
|
+
vertical: 'resize-y',
|
|
1572
|
+
horizontal: 'resize-x',
|
|
1573
|
+
both: 'resize',
|
|
1574
|
+
};
|
|
1575
|
+
return map[this.resize()];
|
|
1576
|
+
});
|
|
1577
|
+
this.textareaClasses = computed(() => {
|
|
1578
|
+
const borderColor = this.showError()
|
|
1579
|
+
? 'border-ku-error-primary'
|
|
1580
|
+
: 'border-ku-secondary-border focus-visible:border-ku-action-primary';
|
|
1581
|
+
return `${this.resizeClass()} ${borderColor}`;
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
onInput(event) {
|
|
1585
|
+
const target = event.target;
|
|
1586
|
+
this.value.set(target.value);
|
|
1587
|
+
}
|
|
1588
|
+
onBlur() {
|
|
1589
|
+
this.touched.set(true);
|
|
1590
|
+
}
|
|
1591
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SignalTextareaComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1592
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: SignalTextareaComponent, isStandalone: true, selector: "keepui-signal-textarea", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, resize: { classPropertyName: "resize", publicName: "resize", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null }, textareaId: { classPropertyName: "textareaId", publicName: "textareaId", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", touched: "touchedChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
1593
|
+
<div class="flex flex-col gap-1" [class]="widthClass()">
|
|
1594
|
+
|
|
1595
|
+
@if (label()) {
|
|
1596
|
+
<label [for]="textareaId()" class="text-sm text-ku-gray-text">
|
|
1597
|
+
{{ label() }}
|
|
1598
|
+
@if (required()) {
|
|
1599
|
+
<span class="text-ku-error-primary" aria-hidden="true"> *</span>
|
|
1600
|
+
}
|
|
1601
|
+
</label>
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
<textarea
|
|
1605
|
+
[id]="textareaId()"
|
|
1606
|
+
[value]="value()"
|
|
1607
|
+
[placeholder]="placeholder()"
|
|
1608
|
+
[rows]="rows()"
|
|
1609
|
+
[disabled]="disabled()"
|
|
1610
|
+
[attr.maxlength]="maxLength() ?? null"
|
|
1611
|
+
[attr.required]="required() ? true : null"
|
|
1612
|
+
[attr.aria-required]="required() ? true : null"
|
|
1613
|
+
[attr.aria-invalid]="showError() ? true : null"
|
|
1614
|
+
[attr.aria-describedby]="showError() ? errorId() : (maxLength() ? charCountId() : null)"
|
|
1615
|
+
class="bg-ku-primary border rounded-xl p-3 text-sm text-ku-primary-text
|
|
1616
|
+
placeholder:text-ku-gray-text outline-none transition-colors w-full
|
|
1617
|
+
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1618
|
+
[class]="textareaClasses()"
|
|
1619
|
+
(input)="onInput($event)"
|
|
1620
|
+
(blur)="onBlur()"
|
|
1621
|
+
></textarea>
|
|
1622
|
+
|
|
1623
|
+
<div class="flex items-start justify-between gap-2">
|
|
1624
|
+
|
|
1625
|
+
<span
|
|
1626
|
+
[id]="errorId()"
|
|
1627
|
+
class="text-sm text-ku-error-primary"
|
|
1628
|
+
role="alert"
|
|
1629
|
+
>
|
|
1630
|
+
@if (showError()) {
|
|
1631
|
+
@if (errorMessage()) {
|
|
1632
|
+
{{ errorMessage() }}
|
|
1633
|
+
} @else if (errors().length > 0) {
|
|
1634
|
+
{{ errors()[0] }}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
</span>
|
|
1638
|
+
|
|
1639
|
+
@if (maxLength()) {
|
|
1640
|
+
<span
|
|
1641
|
+
[id]="charCountId()"
|
|
1642
|
+
class="text-xs text-ku-gray-text ml-auto shrink-0"
|
|
1643
|
+
aria-live="polite"
|
|
1644
|
+
aria-atomic="true"
|
|
1645
|
+
>
|
|
1646
|
+
{{ charCount() }} / {{ maxLength() }}
|
|
1647
|
+
</span>
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
</div>
|
|
1651
|
+
|
|
1652
|
+
</div>
|
|
1653
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1654
|
+
}
|
|
1655
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: SignalTextareaComponent, decorators: [{
|
|
1656
|
+
type: Component,
|
|
1657
|
+
args: [{
|
|
1658
|
+
selector: 'keepui-signal-textarea',
|
|
1659
|
+
standalone: true,
|
|
1660
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1661
|
+
host: { class: 'block' },
|
|
1662
|
+
template: `
|
|
1663
|
+
<div class="flex flex-col gap-1" [class]="widthClass()">
|
|
1664
|
+
|
|
1665
|
+
@if (label()) {
|
|
1666
|
+
<label [for]="textareaId()" class="text-sm text-ku-gray-text">
|
|
1667
|
+
{{ label() }}
|
|
1668
|
+
@if (required()) {
|
|
1669
|
+
<span class="text-ku-error-primary" aria-hidden="true"> *</span>
|
|
1670
|
+
}
|
|
1671
|
+
</label>
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
<textarea
|
|
1675
|
+
[id]="textareaId()"
|
|
1676
|
+
[value]="value()"
|
|
1677
|
+
[placeholder]="placeholder()"
|
|
1678
|
+
[rows]="rows()"
|
|
1679
|
+
[disabled]="disabled()"
|
|
1680
|
+
[attr.maxlength]="maxLength() ?? null"
|
|
1681
|
+
[attr.required]="required() ? true : null"
|
|
1682
|
+
[attr.aria-required]="required() ? true : null"
|
|
1683
|
+
[attr.aria-invalid]="showError() ? true : null"
|
|
1684
|
+
[attr.aria-describedby]="showError() ? errorId() : (maxLength() ? charCountId() : null)"
|
|
1685
|
+
class="bg-ku-primary border rounded-xl p-3 text-sm text-ku-primary-text
|
|
1686
|
+
placeholder:text-ku-gray-text outline-none transition-colors w-full
|
|
1687
|
+
disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1688
|
+
[class]="textareaClasses()"
|
|
1689
|
+
(input)="onInput($event)"
|
|
1690
|
+
(blur)="onBlur()"
|
|
1691
|
+
></textarea>
|
|
1692
|
+
|
|
1693
|
+
<div class="flex items-start justify-between gap-2">
|
|
1694
|
+
|
|
1695
|
+
<span
|
|
1696
|
+
[id]="errorId()"
|
|
1697
|
+
class="text-sm text-ku-error-primary"
|
|
1698
|
+
role="alert"
|
|
1699
|
+
>
|
|
1700
|
+
@if (showError()) {
|
|
1701
|
+
@if (errorMessage()) {
|
|
1702
|
+
{{ errorMessage() }}
|
|
1703
|
+
} @else if (errors().length > 0) {
|
|
1704
|
+
{{ errors()[0] }}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
</span>
|
|
1708
|
+
|
|
1709
|
+
@if (maxLength()) {
|
|
1710
|
+
<span
|
|
1711
|
+
[id]="charCountId()"
|
|
1712
|
+
class="text-xs text-ku-gray-text ml-auto shrink-0"
|
|
1713
|
+
aria-live="polite"
|
|
1714
|
+
aria-atomic="true"
|
|
1715
|
+
>
|
|
1716
|
+
{{ charCount() }} / {{ maxLength() }}
|
|
1717
|
+
</span>
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
</div>
|
|
1721
|
+
|
|
1722
|
+
</div>
|
|
1723
|
+
`,
|
|
1724
|
+
}]
|
|
1725
|
+
}] });
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* Accessible pill-style tab group with icon support and two visual variants.
|
|
1729
|
+
*
|
|
1730
|
+
* Implements the WAI-ARIA `tablist` pattern: keyboard navigation with arrow keys,
|
|
1731
|
+
* `Home`, and `End`. Each tab carries `role="tab"` and `aria-selected`.
|
|
1732
|
+
*
|
|
1733
|
+
* Works identically on **web** and **Angular + Capacitor**.
|
|
1734
|
+
*
|
|
1735
|
+
* ```html
|
|
1736
|
+
* <!-- Basic usage -->
|
|
1737
|
+
* <keepui-tab-group
|
|
1738
|
+
* [tabs]="tabs"
|
|
1739
|
+
* [selectedTabId]="activeTab"
|
|
1740
|
+
* (tabChange)="activeTab = $event"
|
|
1741
|
+
* />
|
|
1742
|
+
*
|
|
1743
|
+
* <!-- Filled variant -->
|
|
1744
|
+
* <keepui-tab-group
|
|
1745
|
+
* variant="filled"
|
|
1746
|
+
* [tabs]="tabs"
|
|
1747
|
+
* [selectedTabId]="activeTab"
|
|
1748
|
+
* (tabChange)="activeTab = $event"
|
|
1749
|
+
* />
|
|
1750
|
+
* ```
|
|
1751
|
+
*/
|
|
1752
|
+
class TabGroupComponent {
|
|
1753
|
+
constructor() {
|
|
1754
|
+
/** List of tabs to render. */
|
|
1755
|
+
this.tabs = input.required();
|
|
1756
|
+
/** ID of the currently selected tab. */
|
|
1757
|
+
this.selectedTabId = input.required();
|
|
1758
|
+
/** Visual style variant. @default 'default' */
|
|
1759
|
+
this.variant = input('default');
|
|
1760
|
+
/**
|
|
1761
|
+
* Accessible label for the tablist container.
|
|
1762
|
+
* Provide a meaningful description when multiple tab groups exist on the page.
|
|
1763
|
+
*/
|
|
1764
|
+
this.ariaLabel = input('');
|
|
1765
|
+
/** Emits the `id` of the tab the user has selected. */
|
|
1766
|
+
this.tabChange = output();
|
|
1767
|
+
this.tabButtons = viewChildren('tabButton');
|
|
1768
|
+
this.containerClass = computed(() => this.variant() === 'filled'
|
|
1769
|
+
? 'bg-ku-action-primary/10 border-ku-action-primary/20'
|
|
1770
|
+
: 'bg-ku-secondary border-ku-secondary-border');
|
|
1771
|
+
}
|
|
1772
|
+
buttonClass(tabId) {
|
|
1773
|
+
const isActive = this.selectedTabId() === tabId;
|
|
1774
|
+
if (this.variant() === 'filled') {
|
|
1775
|
+
return isActive
|
|
1776
|
+
? 'bg-ku-action-primary text-white'
|
|
1777
|
+
: 'text-ku-gray-text hover:text-ku-primary-text hover:bg-ku-action-primary/10';
|
|
1778
|
+
}
|
|
1779
|
+
return isActive
|
|
1780
|
+
? 'bg-ku-primary text-ku-primary-text shadow-sm border border-ku-primary-border'
|
|
1781
|
+
: 'text-ku-gray-text hover:text-ku-primary-text hover:bg-ku-secondary-hover';
|
|
1782
|
+
}
|
|
1783
|
+
iconClass(tabId) {
|
|
1784
|
+
const isActive = this.selectedTabId() === tabId;
|
|
1785
|
+
if (this.variant() === 'filled') {
|
|
1786
|
+
return isActive ? 'text-white' : 'text-ku-gray-text';
|
|
1787
|
+
}
|
|
1788
|
+
return isActive ? 'text-ku-action-primary' : 'text-ku-gray-text';
|
|
1789
|
+
}
|
|
1790
|
+
onTabClick(tab) {
|
|
1791
|
+
if (!tab.disabled && tab.id !== this.selectedTabId()) {
|
|
1792
|
+
this.tabChange.emit(tab.id);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
onKeydown(event) {
|
|
1796
|
+
const tabs = this.tabs().filter(t => !t.disabled);
|
|
1797
|
+
const activeIndex = tabs.findIndex(t => t.id === this.selectedTabId());
|
|
1798
|
+
let nextIndex = activeIndex;
|
|
1799
|
+
if (event.key === 'ArrowRight' || event.key === 'ArrowDown') {
|
|
1800
|
+
nextIndex = (activeIndex + 1) % tabs.length;
|
|
1801
|
+
}
|
|
1802
|
+
else if (event.key === 'ArrowLeft' || event.key === 'ArrowUp') {
|
|
1803
|
+
nextIndex = (activeIndex - 1 + tabs.length) % tabs.length;
|
|
1804
|
+
}
|
|
1805
|
+
else if (event.key === 'Home') {
|
|
1806
|
+
nextIndex = 0;
|
|
1807
|
+
}
|
|
1808
|
+
else if (event.key === 'End') {
|
|
1809
|
+
nextIndex = tabs.length - 1;
|
|
1810
|
+
}
|
|
1811
|
+
else {
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
event.preventDefault();
|
|
1815
|
+
const nextTab = tabs[nextIndex];
|
|
1816
|
+
this.tabChange.emit(nextTab.id);
|
|
1817
|
+
this.focusTab(nextTab.id);
|
|
1818
|
+
}
|
|
1819
|
+
focusTab(tabId) {
|
|
1820
|
+
const allTabs = this.tabs();
|
|
1821
|
+
const index = allTabs.findIndex(t => t.id === tabId);
|
|
1822
|
+
const buttons = this.tabButtons();
|
|
1823
|
+
buttons[index]?.nativeElement.focus();
|
|
1824
|
+
}
|
|
1825
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: TabGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1826
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: TabGroupComponent, isStandalone: true, selector: "keepui-tab-group", inputs: { tabs: { classPropertyName: "tabs", publicName: "tabs", isSignal: true, isRequired: true, transformFunction: null }, selectedTabId: { classPropertyName: "selectedTabId", publicName: "selectedTabId", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tabChange: "tabChange" }, viewQueries: [{ propertyName: "tabButtons", predicate: ["tabButton"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1827
|
+
<div
|
|
1828
|
+
role="tablist"
|
|
1829
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1830
|
+
class="flex items-center p-1 rounded-full border transition-colors duration-200"
|
|
1831
|
+
[class]="containerClass()"
|
|
1832
|
+
(keydown)="onKeydown($event)"
|
|
1833
|
+
>
|
|
1834
|
+
@for (tab of tabs(); track tab.id; let i = $index) {
|
|
1835
|
+
<button
|
|
1836
|
+
#tabButton
|
|
1837
|
+
type="button"
|
|
1838
|
+
role="tab"
|
|
1839
|
+
[id]="'keepui-tab-' + tab.id"
|
|
1840
|
+
[attr.aria-selected]="selectedTabId() === tab.id"
|
|
1841
|
+
[attr.aria-disabled]="tab.disabled ? true : null"
|
|
1842
|
+
[disabled]="tab.disabled || false"
|
|
1843
|
+
[tabindex]="selectedTabId() === tab.id ? 0 : -1"
|
|
1844
|
+
class="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full
|
|
1845
|
+
transition-colors duration-200 text-sm font-medium cursor-pointer
|
|
1846
|
+
min-h-[2.75rem]
|
|
1847
|
+
focus-visible:outline-none focus-visible:ring-2
|
|
1848
|
+
focus-visible:ring-ku-action-primary focus-visible:ring-offset-2
|
|
1849
|
+
disabled:cursor-not-allowed disabled:opacity-40"
|
|
1850
|
+
[class]="buttonClass(tab.id)"
|
|
1851
|
+
(click)="onTabClick(tab)"
|
|
1852
|
+
>
|
|
1853
|
+
@if (tab.icon) {
|
|
1854
|
+
<keepui-icon
|
|
1855
|
+
[name]="tab.icon"
|
|
1856
|
+
[size]="16"
|
|
1857
|
+
[class]="iconClass(tab.id)"
|
|
1858
|
+
aria-hidden="true"
|
|
1859
|
+
/>
|
|
1860
|
+
}
|
|
1861
|
+
<span>{{ tab.label }}</span>
|
|
1862
|
+
</button>
|
|
1863
|
+
}
|
|
1864
|
+
</div>
|
|
1865
|
+
`, isInline: true, dependencies: [{ kind: "component", type: IconComponent, selector: "keepui-icon", inputs: ["name", "size", "viewBox", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1866
|
+
}
|
|
1867
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: TabGroupComponent, decorators: [{
|
|
1868
|
+
type: Component,
|
|
1869
|
+
args: [{
|
|
1870
|
+
selector: 'keepui-tab-group',
|
|
1871
|
+
standalone: true,
|
|
1872
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1873
|
+
imports: [IconComponent],
|
|
1874
|
+
template: `
|
|
1875
|
+
<div
|
|
1876
|
+
role="tablist"
|
|
1877
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
1878
|
+
class="flex items-center p-1 rounded-full border transition-colors duration-200"
|
|
1879
|
+
[class]="containerClass()"
|
|
1880
|
+
(keydown)="onKeydown($event)"
|
|
1881
|
+
>
|
|
1882
|
+
@for (tab of tabs(); track tab.id; let i = $index) {
|
|
1883
|
+
<button
|
|
1884
|
+
#tabButton
|
|
1885
|
+
type="button"
|
|
1886
|
+
role="tab"
|
|
1887
|
+
[id]="'keepui-tab-' + tab.id"
|
|
1888
|
+
[attr.aria-selected]="selectedTabId() === tab.id"
|
|
1889
|
+
[attr.aria-disabled]="tab.disabled ? true : null"
|
|
1890
|
+
[disabled]="tab.disabled || false"
|
|
1891
|
+
[tabindex]="selectedTabId() === tab.id ? 0 : -1"
|
|
1892
|
+
class="flex-1 inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-full
|
|
1893
|
+
transition-colors duration-200 text-sm font-medium cursor-pointer
|
|
1894
|
+
min-h-[2.75rem]
|
|
1895
|
+
focus-visible:outline-none focus-visible:ring-2
|
|
1896
|
+
focus-visible:ring-ku-action-primary focus-visible:ring-offset-2
|
|
1897
|
+
disabled:cursor-not-allowed disabled:opacity-40"
|
|
1898
|
+
[class]="buttonClass(tab.id)"
|
|
1899
|
+
(click)="onTabClick(tab)"
|
|
1900
|
+
>
|
|
1901
|
+
@if (tab.icon) {
|
|
1902
|
+
<keepui-icon
|
|
1903
|
+
[name]="tab.icon"
|
|
1904
|
+
[size]="16"
|
|
1905
|
+
[class]="iconClass(tab.id)"
|
|
1906
|
+
aria-hidden="true"
|
|
1907
|
+
/>
|
|
1908
|
+
}
|
|
1909
|
+
<span>{{ tab.label }}</span>
|
|
1910
|
+
</button>
|
|
1911
|
+
}
|
|
1912
|
+
</div>
|
|
1913
|
+
`,
|
|
1914
|
+
}]
|
|
1915
|
+
}] });
|
|
1916
|
+
|
|
1917
|
+
/**
|
|
1918
|
+
* Visual step-progress indicator with optional navigation.
|
|
1919
|
+
*
|
|
1920
|
+
* Renders a sequence of numbered step circles connected by a progress bar.
|
|
1921
|
+
* Completed steps show a check-mark; the active step is highlighted; future
|
|
1922
|
+
* steps are muted. Steps with an `icon` replace the number with the given SVG symbol.
|
|
1923
|
+
*
|
|
1924
|
+
* Accessibility: uses `role="list"` / `role="listitem"` with `aria-current="step"` on
|
|
1925
|
+
* the active item. When steps are interactive (i.e. `stepChange` is listened to),
|
|
1926
|
+
* completed and current steps render as `<button>` elements; future steps remain
|
|
1927
|
+
* inert `<span>` elements.
|
|
1928
|
+
*
|
|
1929
|
+
* Works identically on **web** and **Angular + Capacitor**.
|
|
1930
|
+
*
|
|
1931
|
+
* ```html
|
|
1932
|
+
* <!-- Read-only progress indicator -->
|
|
1933
|
+
* <keepui-stepper [steps]="steps" [activeIndex]="1" />
|
|
1934
|
+
*
|
|
1935
|
+
* <!-- Interactive stepper (go back to a previous step) -->
|
|
1936
|
+
* <keepui-stepper
|
|
1937
|
+
* [steps]="steps"
|
|
1938
|
+
* [activeIndex]="currentStep"
|
|
1939
|
+
* (stepChange)="currentStep = $event"
|
|
1940
|
+
* />
|
|
1941
|
+
*
|
|
1942
|
+
* <!-- Small variant -->
|
|
1943
|
+
* <keepui-stepper [steps]="steps" [activeIndex]="0" size="sm" />
|
|
1944
|
+
* ```
|
|
1945
|
+
*/
|
|
1946
|
+
class StepperComponent {
|
|
1947
|
+
constructor() {
|
|
1948
|
+
/** Steps to render. */
|
|
1949
|
+
this.steps = input.required();
|
|
1950
|
+
/** Zero-based index of the currently active step. @default 0 */
|
|
1951
|
+
this.activeIndex = input(0);
|
|
1952
|
+
/** Visual size of the step circles and connector bars. @default 'md' */
|
|
1953
|
+
this.size = input('md');
|
|
1954
|
+
/** Layout direction. @default 'horizontal' */
|
|
1955
|
+
this.orientation = input('horizontal');
|
|
1956
|
+
/**
|
|
1957
|
+
* Accessible label for the `<nav>` element.
|
|
1958
|
+
* @default ''
|
|
1959
|
+
*/
|
|
1960
|
+
this.ariaLabel = input('');
|
|
1961
|
+
/**
|
|
1962
|
+
* Emits the zero-based index of the step the user clicked.
|
|
1963
|
+
* Only completed steps and the active step are clickable.
|
|
1964
|
+
* When no subscriber is attached, the stepper is purely visual.
|
|
1965
|
+
*/
|
|
1966
|
+
this.stepChange = output();
|
|
1967
|
+
this.iconSize = computed(() => (this.size() === 'sm' ? 12 : 16));
|
|
1968
|
+
this.wrapperClass = computed(() => 'w-full');
|
|
1969
|
+
this.listClass = computed(() => {
|
|
1970
|
+
const base = 'list-none m-0 p-0 flex';
|
|
1971
|
+
return this.orientation() === 'vertical'
|
|
1972
|
+
? `${base} flex-col gap-0`
|
|
1973
|
+
: `${base} flex-row items-start`;
|
|
1974
|
+
});
|
|
1975
|
+
this.itemClass = computed(() => {
|
|
1976
|
+
const base = 'flex';
|
|
1977
|
+
return this.orientation() === 'vertical'
|
|
1978
|
+
? `${base} flex-row items-center flex-1`
|
|
1979
|
+
: `${base} flex-col items-center flex-1`;
|
|
1980
|
+
});
|
|
1981
|
+
this.stepColumnClass = computed(() => {
|
|
1982
|
+
return this.orientation() === 'vertical'
|
|
1983
|
+
? 'flex flex-row items-center gap-3'
|
|
1984
|
+
: 'flex flex-col items-center gap-1.5';
|
|
1985
|
+
});
|
|
1986
|
+
}
|
|
1987
|
+
isCompleted(index) {
|
|
1988
|
+
return index < this.activeIndex();
|
|
1989
|
+
}
|
|
1990
|
+
isActive(index) {
|
|
1991
|
+
return index === this.activeIndex();
|
|
1992
|
+
}
|
|
1993
|
+
isClickable(index, step) {
|
|
1994
|
+
return !step.disabled && (this.isCompleted(index) || this.isActive(index));
|
|
1995
|
+
}
|
|
1996
|
+
onStepClick(index, step) {
|
|
1997
|
+
if (!step.disabled) {
|
|
1998
|
+
this.stepChange.emit(index);
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
bubbleClass(index) {
|
|
2002
|
+
const sizeClass = this.size() === 'sm'
|
|
2003
|
+
? 'w-6 h-6 text-xs'
|
|
2004
|
+
: 'w-9 h-9 text-sm';
|
|
2005
|
+
const base = [
|
|
2006
|
+
'inline-flex items-center justify-center rounded-full shrink-0',
|
|
2007
|
+
'transition-colors duration-200',
|
|
2008
|
+
'focus-visible:outline-none focus-visible:ring-2',
|
|
2009
|
+
'focus-visible:ring-ku-action-primary focus-visible:ring-offset-2',
|
|
2010
|
+
sizeClass,
|
|
2011
|
+
].join(' ');
|
|
2012
|
+
if (this.isCompleted(index)) {
|
|
2013
|
+
return `${base} bg-ku-action-primary text-white cursor-pointer`;
|
|
2014
|
+
}
|
|
2015
|
+
if (this.isActive(index)) {
|
|
2016
|
+
return `${base} bg-ku-action-primary text-white ring-2 ring-ku-action-primary ring-offset-2`;
|
|
2017
|
+
}
|
|
2018
|
+
return `${base} bg-ku-secondary border-2 border-ku-secondary-border text-ku-gray-text cursor-default`;
|
|
2019
|
+
}
|
|
2020
|
+
labelClass(index) {
|
|
2021
|
+
const base = 'text-xs font-medium text-center transition-colors duration-200';
|
|
2022
|
+
if (this.isActive(index)) {
|
|
2023
|
+
return `${base} text-ku-primary-text`;
|
|
2024
|
+
}
|
|
2025
|
+
if (this.isCompleted(index)) {
|
|
2026
|
+
return `${base} text-ku-action-primary`;
|
|
2027
|
+
}
|
|
2028
|
+
return `${base} text-ku-gray-text`;
|
|
2029
|
+
}
|
|
2030
|
+
connectorClass(index) {
|
|
2031
|
+
const filled = this.isCompleted(index) || this.isActive(index);
|
|
2032
|
+
if (this.orientation() === 'vertical') {
|
|
2033
|
+
const height = this.size() === 'sm' ? 'h-8' : 'h-10';
|
|
2034
|
+
return [
|
|
2035
|
+
`${height} w-0.5 mx-auto my-1 rounded-full transition-colors duration-300`,
|
|
2036
|
+
filled ? 'bg-ku-action-primary' : 'bg-ku-secondary-border',
|
|
2037
|
+
].join(' ');
|
|
2038
|
+
}
|
|
2039
|
+
return [
|
|
2040
|
+
'w-full h-0.5 mt-4 mb-1 rounded-full transition-colors duration-300',
|
|
2041
|
+
filled ? 'bg-ku-action-primary' : 'bg-ku-secondary-border',
|
|
2042
|
+
].join(' ');
|
|
2043
|
+
}
|
|
2044
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StepperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
2045
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.20", type: StepperComponent, isStandalone: true, selector: "keepui-stepper", inputs: { steps: { classPropertyName: "steps", publicName: "steps", isSignal: true, isRequired: true, transformFunction: null }, activeIndex: { classPropertyName: "activeIndex", publicName: "activeIndex", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { stepChange: "stepChange" }, ngImport: i0, template: `
|
|
2046
|
+
<nav
|
|
2047
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
2048
|
+
[class]="wrapperClass()"
|
|
2049
|
+
>
|
|
2050
|
+
<ol
|
|
2051
|
+
role="list"
|
|
2052
|
+
[class]="listClass()"
|
|
2053
|
+
>
|
|
2054
|
+
@for (step of steps(); track step.id; let i = $index; let last = $last) {
|
|
2055
|
+
<li
|
|
2056
|
+
role="listitem"
|
|
2057
|
+
[class]="itemClass()"
|
|
2058
|
+
[attr.aria-current]="isActive(i) ? 'step' : null"
|
|
2059
|
+
>
|
|
2060
|
+
<div [class]="stepColumnClass()">
|
|
2061
|
+
@if (isClickable(i, step)) {
|
|
2062
|
+
<button
|
|
2063
|
+
type="button"
|
|
2064
|
+
[class]="bubbleClass(i)"
|
|
2065
|
+
[attr.aria-label]="step.label"
|
|
2066
|
+
[attr.aria-disabled]="step.disabled ? true : null"
|
|
2067
|
+
[disabled]="step.disabled || false"
|
|
2068
|
+
(click)="onStepClick(i, step)"
|
|
2069
|
+
>
|
|
2070
|
+
<ng-container *ngTemplateOutlet="bubbleContent; context: { i, step }" />
|
|
2071
|
+
</button>
|
|
2072
|
+
} @else {
|
|
2073
|
+
<span [class]="bubbleClass(i)" aria-hidden="true">
|
|
2074
|
+
<ng-container *ngTemplateOutlet="bubbleContent; context: { i, step }" />
|
|
2075
|
+
</span>
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
<span [class]="labelClass(i)">{{ step.label }}</span>
|
|
2079
|
+
</div>
|
|
2080
|
+
|
|
2081
|
+
@if (!last) {
|
|
2082
|
+
<div [class]="connectorClass(i)"></div>
|
|
2083
|
+
}
|
|
2084
|
+
</li>
|
|
2085
|
+
}
|
|
2086
|
+
</ol>
|
|
2087
|
+
</nav>
|
|
2088
|
+
|
|
2089
|
+
<ng-template #bubbleContent let-i="i" let-step="step">
|
|
2090
|
+
@if (isCompleted(i)) {
|
|
2091
|
+
<svg
|
|
2092
|
+
width="14"
|
|
2093
|
+
height="14"
|
|
2094
|
+
viewBox="0 0 24 24"
|
|
2095
|
+
fill="none"
|
|
2096
|
+
stroke="currentColor"
|
|
2097
|
+
stroke-width="3"
|
|
2098
|
+
stroke-linecap="round"
|
|
2099
|
+
stroke-linejoin="round"
|
|
2100
|
+
aria-hidden="true"
|
|
2101
|
+
>
|
|
2102
|
+
<polyline points="20 6 9 17 4 12" />
|
|
2103
|
+
</svg>
|
|
2104
|
+
} @else if (step.icon) {
|
|
2105
|
+
<keepui-icon [name]="step.icon" [size]="iconSize()" aria-hidden="true" />
|
|
2106
|
+
} @else {
|
|
2107
|
+
<span class="font-semibold leading-none">{{ i + 1 }}</span>
|
|
2108
|
+
}
|
|
2109
|
+
</ng-template>
|
|
2110
|
+
`, isInline: true, dependencies: [{ kind: "component", type: IconComponent, selector: "keepui-icon", inputs: ["name", "size", "viewBox", "ariaLabel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2111
|
+
}
|
|
2112
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImport: i0, type: StepperComponent, decorators: [{
|
|
2113
|
+
type: Component,
|
|
2114
|
+
args: [{
|
|
2115
|
+
selector: 'keepui-stepper',
|
|
2116
|
+
standalone: true,
|
|
2117
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
2118
|
+
imports: [IconComponent, NgTemplateOutlet],
|
|
2119
|
+
template: `
|
|
2120
|
+
<nav
|
|
2121
|
+
[attr.aria-label]="ariaLabel() || null"
|
|
2122
|
+
[class]="wrapperClass()"
|
|
2123
|
+
>
|
|
2124
|
+
<ol
|
|
2125
|
+
role="list"
|
|
2126
|
+
[class]="listClass()"
|
|
2127
|
+
>
|
|
2128
|
+
@for (step of steps(); track step.id; let i = $index; let last = $last) {
|
|
2129
|
+
<li
|
|
2130
|
+
role="listitem"
|
|
2131
|
+
[class]="itemClass()"
|
|
2132
|
+
[attr.aria-current]="isActive(i) ? 'step' : null"
|
|
2133
|
+
>
|
|
2134
|
+
<div [class]="stepColumnClass()">
|
|
2135
|
+
@if (isClickable(i, step)) {
|
|
2136
|
+
<button
|
|
2137
|
+
type="button"
|
|
2138
|
+
[class]="bubbleClass(i)"
|
|
2139
|
+
[attr.aria-label]="step.label"
|
|
2140
|
+
[attr.aria-disabled]="step.disabled ? true : null"
|
|
2141
|
+
[disabled]="step.disabled || false"
|
|
2142
|
+
(click)="onStepClick(i, step)"
|
|
2143
|
+
>
|
|
2144
|
+
<ng-container *ngTemplateOutlet="bubbleContent; context: { i, step }" />
|
|
2145
|
+
</button>
|
|
2146
|
+
} @else {
|
|
2147
|
+
<span [class]="bubbleClass(i)" aria-hidden="true">
|
|
2148
|
+
<ng-container *ngTemplateOutlet="bubbleContent; context: { i, step }" />
|
|
2149
|
+
</span>
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
<span [class]="labelClass(i)">{{ step.label }}</span>
|
|
2153
|
+
</div>
|
|
2154
|
+
|
|
2155
|
+
@if (!last) {
|
|
2156
|
+
<div [class]="connectorClass(i)"></div>
|
|
2157
|
+
}
|
|
2158
|
+
</li>
|
|
2159
|
+
}
|
|
2160
|
+
</ol>
|
|
2161
|
+
</nav>
|
|
2162
|
+
|
|
2163
|
+
<ng-template #bubbleContent let-i="i" let-step="step">
|
|
2164
|
+
@if (isCompleted(i)) {
|
|
2165
|
+
<svg
|
|
2166
|
+
width="14"
|
|
2167
|
+
height="14"
|
|
2168
|
+
viewBox="0 0 24 24"
|
|
2169
|
+
fill="none"
|
|
2170
|
+
stroke="currentColor"
|
|
2171
|
+
stroke-width="3"
|
|
2172
|
+
stroke-linecap="round"
|
|
2173
|
+
stroke-linejoin="round"
|
|
2174
|
+
aria-hidden="true"
|
|
2175
|
+
>
|
|
2176
|
+
<polyline points="20 6 9 17 4 12" />
|
|
2177
|
+
</svg>
|
|
2178
|
+
} @else if (step.icon) {
|
|
2179
|
+
<keepui-icon [name]="step.icon" [size]="iconSize()" aria-hidden="true" />
|
|
2180
|
+
} @else {
|
|
2181
|
+
<span class="font-semibold leading-none">{{ i + 1 }}</span>
|
|
2182
|
+
}
|
|
2183
|
+
</ng-template>
|
|
2184
|
+
`,
|
|
2185
|
+
}]
|
|
2186
|
+
}] });
|
|
2187
|
+
|
|
693
2188
|
/**
|
|
694
2189
|
* Registers KeepUI core providers for a **web** Angular application.
|
|
695
2190
|
*
|
|
@@ -725,6 +2220,10 @@ const EN = {
|
|
|
725
2220
|
previewAlt: 'Selected image preview',
|
|
726
2221
|
errorUnexpected: 'An unexpected error occurred',
|
|
727
2222
|
},
|
|
2223
|
+
signalTextInput: {
|
|
2224
|
+
showPassword: 'Show password',
|
|
2225
|
+
hidePassword: 'Hide password',
|
|
2226
|
+
},
|
|
728
2227
|
};
|
|
729
2228
|
const ES = {
|
|
730
2229
|
imagePreview: {
|
|
@@ -733,6 +2232,10 @@ const ES = {
|
|
|
733
2232
|
previewAlt: 'Vista previa de imagen seleccionada',
|
|
734
2233
|
errorUnexpected: 'Ha ocurrido un error inesperado',
|
|
735
2234
|
},
|
|
2235
|
+
signalTextInput: {
|
|
2236
|
+
showPassword: 'Mostrar contraseña',
|
|
2237
|
+
hidePassword: 'Ocultar contraseña',
|
|
2238
|
+
},
|
|
736
2239
|
};
|
|
737
2240
|
const DE = {
|
|
738
2241
|
imagePreview: {
|
|
@@ -741,6 +2244,10 @@ const DE = {
|
|
|
741
2244
|
previewAlt: 'Vorschau des ausgewählten Bildes',
|
|
742
2245
|
errorUnexpected: 'Ein unerwarteter Fehler ist aufgetreten',
|
|
743
2246
|
},
|
|
2247
|
+
signalTextInput: {
|
|
2248
|
+
showPassword: 'Passwort anzeigen',
|
|
2249
|
+
hidePassword: 'Passwort verbergen',
|
|
2250
|
+
},
|
|
744
2251
|
};
|
|
745
2252
|
/** Map from locale code to its translation object. */
|
|
746
2253
|
const KEEPUI_TRANSLATIONS = {
|
|
@@ -905,5 +2412,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.20", ngImpo
|
|
|
905
2412
|
* Generated bundle index. Do not edit.
|
|
906
2413
|
*/
|
|
907
2414
|
|
|
908
|
-
export { ButtonComponent, CardComponent, FILE_PORT, IconActionButtonComponent, IconComponent, ImagePreviewComponent, KEEPUI_AVAILABLE_LANGUAGES, KEEPUI_TRANSLATIONS, KEEPUI_TRANSLATION_KEYS, KeepUiLanguageService, MockFileService, WebFileService, provideKeepUi, provideKeepUiI18n };
|
|
2415
|
+
export { ButtonComponent, CardComponent, FILE_PORT, IconActionButtonComponent, IconComponent, ImagePreviewComponent, KEEPUI_AVAILABLE_LANGUAGES, KEEPUI_TRANSLATIONS, KEEPUI_TRANSLATION_KEYS, KeepUiLanguageService, MockFileService, SignalDropdownComponent, SignalTextInputComponent, SignalTextareaComponent, StepperComponent, TabGroupComponent, WebFileService, provideKeepUi, provideKeepUiI18n };
|
|
909
2416
|
//# sourceMappingURL=keepui-ui.mjs.map
|