@haloduck/ui 2.0.10 → 2.0.12

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.
@@ -2851,6 +2851,170 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
2851
2851
  args: ['label']
2852
2852
  }] } });
2853
2853
 
2854
+ class TagInputComponent {
2855
+ label;
2856
+ inputEl;
2857
+ placeholder = '';
2858
+ disabled = false;
2859
+ allowDuplicates = false;
2860
+ // Two-way binding support independent of CVA
2861
+ set value(tags) {
2862
+ if (Array.isArray(tags)) {
2863
+ this.tags = tags
2864
+ .map((t) => (t ?? '').trim())
2865
+ .filter((t) => t.length > 0);
2866
+ }
2867
+ else {
2868
+ this.tags = [];
2869
+ }
2870
+ }
2871
+ valueChange = new EventEmitter();
2872
+ tags = [];
2873
+ inputValue = '';
2874
+ onChange = () => { };
2875
+ onTouched = () => { };
2876
+ writeValue(value) {
2877
+ if (Array.isArray(value)) {
2878
+ this.tags = value
2879
+ .map((t) => (t ?? '').trim())
2880
+ .filter((t) => t.length > 0);
2881
+ }
2882
+ else {
2883
+ this.tags = [];
2884
+ }
2885
+ }
2886
+ registerOnChange(fn) {
2887
+ this.onChange = fn;
2888
+ }
2889
+ registerOnTouched(fn) {
2890
+ this.onTouched = fn;
2891
+ }
2892
+ setDisabledState(isDisabled) {
2893
+ this.disabled = isDisabled;
2894
+ }
2895
+ ngAfterViewInit() {
2896
+ // hide label if no projected content
2897
+ if (this.label && this.label.nativeElement) {
2898
+ const hasContent = this.label.nativeElement.textContent?.trim();
2899
+ if (!hasContent) {
2900
+ this.label.nativeElement.style.display = 'none';
2901
+ }
2902
+ }
2903
+ }
2904
+ focus() {
2905
+ this.inputEl?.nativeElement?.focus();
2906
+ }
2907
+ onInput(event) {
2908
+ const input = event.target;
2909
+ this.inputValue = input.value;
2910
+ this.onTouched();
2911
+ }
2912
+ onBlur() {
2913
+ if (this.disabled)
2914
+ return;
2915
+ this.inputValue = '';
2916
+ this.onTouched();
2917
+ }
2918
+ onKeydown(event) {
2919
+ if (this.disabled)
2920
+ return;
2921
+ // Commit on comma or Enter
2922
+ if (event.key === ',' || event.key === 'Enter') {
2923
+ event.preventDefault();
2924
+ this.commitCurrentInput();
2925
+ return;
2926
+ }
2927
+ // Backspace behavior
2928
+ if (event.key === 'Backspace') {
2929
+ if (this.inputValue.length > 0) {
2930
+ return; // default backspace in input
2931
+ }
2932
+ if (this.tags.length > 0) {
2933
+ event.preventDefault();
2934
+ this.removeTag(this.tags.length - 1);
2935
+ return;
2936
+ }
2937
+ }
2938
+ }
2939
+ removeTag(index) {
2940
+ if (this.disabled)
2941
+ return;
2942
+ if (index < 0 || index >= this.tags.length)
2943
+ return;
2944
+ this.tags = this.tags.filter((_, i) => i !== index);
2945
+ this.emitChanges();
2946
+ }
2947
+ commitCurrentInput() {
2948
+ const raw = this.inputValue.trim();
2949
+ if (!raw) {
2950
+ this.inputValue = '';
2951
+ return;
2952
+ }
2953
+ // If user pasted multiple comma-separated values, split and add all
2954
+ const parts = raw.split(',').map((p) => p.trim()).filter((p) => p.length > 0);
2955
+ for (const part of parts) {
2956
+ this.addTag(part);
2957
+ }
2958
+ this.inputValue = '';
2959
+ }
2960
+ addTag(tag) {
2961
+ if (!this.allowDuplicates) {
2962
+ const exists = this.tags.some((t) => t.toLowerCase() === tag.toLowerCase());
2963
+ if (exists) {
2964
+ return;
2965
+ }
2966
+ }
2967
+ this.tags = [...this.tags, tag];
2968
+ this.emitChanges();
2969
+ }
2970
+ emitChanges() {
2971
+ const cleaned = this.tags
2972
+ .map((t) => (t ?? '').trim())
2973
+ .filter((t) => t.length > 0);
2974
+ if (cleaned.length !== this.tags.length || cleaned.some((t, i) => t !== this.tags[i])) {
2975
+ this.tags = cleaned;
2976
+ }
2977
+ this.onChange(this.tags);
2978
+ this.valueChange.emit(this.tags);
2979
+ }
2980
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TagInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2981
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: TagInputComponent, isStandalone: true, selector: "haloduck-tag-input", inputs: { placeholder: "placeholder", disabled: "disabled", allowDuplicates: "allowDuplicates", value: "value" }, outputs: { valueChange: "valueChange" }, providers: [
2982
+ {
2983
+ provide: NG_VALUE_ACCESSOR,
2984
+ useExisting: forwardRef(() => TagInputComponent),
2985
+ multi: true,
2986
+ },
2987
+ provideTranslocoScope('haloduck'),
2988
+ ], viewQueries: [{ propertyName: "label", first: true, predicate: ["label"], descendants: true }, { propertyName: "inputEl", first: true, predicate: ["inputEl"], descendants: true }], ngImport: i0, template: "<div class=\"flex flex-col gap-2\">\n <label #label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n\n <div class=\"tag-input-wrapper block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-2 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive sm:text-sm/6\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <ng-container *ngFor=\"let tag of tags; let i = index\">\n <span class=\"inline-flex items-center gap-1 rounded-md bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary px-2 py-0.5 text-xs\">\n {{ tag }}\n @if (!disabled) {\n <button type=\"button\" (click)=\"removeTag(i)\" class=\"text-light-on-secondary/80 hover:text-light-on-secondary dark:text-dark-on-secondary/80 dark:hover:text-dark-on-secondary hover:cursor-pointer\">\n \u00D7\n </button>\n }\n </span>\n </ng-container>\n\n <input #inputEl\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n class=\"flex-1 min-w-[8rem] bg-transparent outline-none placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [value]=\"inputValue\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n </div>\n </div>\n</div>\n", styles: [":host{display:block}.tag-input-wrapper:focus-within{outline-width:2px;outline-offset:2px;outline-color:var(--color-light-primary)!important}@media (prefers-color-scheme: dark){.tag-input-wrapper:focus-within{outline-color:var(--color-dark-primary)!important}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
2989
+ }
2990
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: TagInputComponent, decorators: [{
2991
+ type: Component,
2992
+ args: [{ selector: 'haloduck-tag-input', imports: [CommonModule], providers: [
2993
+ {
2994
+ provide: NG_VALUE_ACCESSOR,
2995
+ useExisting: forwardRef(() => TagInputComponent),
2996
+ multi: true,
2997
+ },
2998
+ provideTranslocoScope('haloduck'),
2999
+ ], template: "<div class=\"flex flex-col gap-2\">\n <label #label class=\"block text-sm/6 font-medium text-light-on-control dark:text-dark-on-control text-left\">\n <ng-content></ng-content>\n </label>\n\n <div class=\"tag-input-wrapper block w-full rounded-md bg-light-control dark:bg-dark-control disabled:bg-light-control/60 dark:disabled:bg-dark-control/80 px-2 py-1.5 text-base text-light-on-control dark:text-dark-on-control disabled:cursor-not-allowed disabled:text-light-on-control/60 dark:disabled:text-dark-on-control/80 outline -outline-offset-1 outline-light-inactive dark:outline-dark-inactive placeholder:text-light-inactive dark:placeholder:text-dark-inactive sm:text-sm/6\">\n <div class=\"flex flex-wrap items-center gap-2\">\n <ng-container *ngFor=\"let tag of tags; let i = index\">\n <span class=\"inline-flex items-center gap-1 rounded-md bg-light-secondary dark:bg-dark-secondary text-light-on-secondary dark:text-dark-on-secondary px-2 py-0.5 text-xs\">\n {{ tag }}\n @if (!disabled) {\n <button type=\"button\" (click)=\"removeTag(i)\" class=\"text-light-on-secondary/80 hover:text-light-on-secondary dark:text-dark-on-secondary/80 dark:hover:text-dark-on-secondary hover:cursor-pointer\">\n \u00D7\n </button>\n }\n </span>\n </ng-container>\n\n <input #inputEl\n [disabled]=\"disabled\"\n [placeholder]=\"placeholder\"\n class=\"flex-1 min-w-[8rem] bg-transparent outline-none placeholder:text-light-inactive dark:placeholder:text-dark-inactive\"\n [value]=\"inputValue\"\n (input)=\"onInput($event)\"\n (keydown)=\"onKeydown($event)\"\n (blur)=\"onBlur()\"\n />\n </div>\n </div>\n</div>\n", styles: [":host{display:block}.tag-input-wrapper:focus-within{outline-width:2px;outline-offset:2px;outline-color:var(--color-light-primary)!important}@media (prefers-color-scheme: dark){.tag-input-wrapper:focus-within{outline-color:var(--color-dark-primary)!important}}\n"] }]
3000
+ }], propDecorators: { label: [{
3001
+ type: ViewChild,
3002
+ args: ['label']
3003
+ }], inputEl: [{
3004
+ type: ViewChild,
3005
+ args: ['inputEl']
3006
+ }], placeholder: [{
3007
+ type: Input
3008
+ }], disabled: [{
3009
+ type: Input
3010
+ }], allowDuplicates: [{
3011
+ type: Input
3012
+ }], value: [{
3013
+ type: Input
3014
+ }], valueChange: [{
3015
+ type: Output
3016
+ }] } });
3017
+
2854
3018
  class BreadcrumbService {
2855
3019
  router;
2856
3020
  coreService = inject(CoreService);
@@ -2963,5 +3127,5 @@ const provideHaloduckTransloco = () => provideTranslocoScope({
2963
3127
  * Generated bundle index. Do not edit.
2964
3128
  */
2965
3129
 
2966
- export { AuthenticateComponent, BreadcrumbComponent, ButtonComponent, CalendarComponent, ConfirmDialogService, CopyButtonComponent, DatePickerComponent, DateRangeComponent, DialogService, DrawCanvasComponent, ERROR_NOT_ACCEPTABLE_FILE_TYPE, ERROR_OVER_COUNT, ERROR_OVER_SIZE, ERROR_UPLOAD, FileUploaderComponent, FlipComponent, ImageUploaderComponent, ImageViewerComponent, InputComponent, LanguageSelectorComponent, MapToAddressComponent, NotificationComponent, NotificationService, PictureNameComponent, SelectComponent, SelectDropdownComponent, SideMenuComponent, SideMenuItemComponent, StlViewerComponent, TableComponent, TabsComponent, ToggleComponent, dateToString, provideHaloduckTransloco };
3130
+ export { AuthenticateComponent, BreadcrumbComponent, ButtonComponent, CalendarComponent, ConfirmDialogService, CopyButtonComponent, DatePickerComponent, DateRangeComponent, DialogService, DrawCanvasComponent, ERROR_NOT_ACCEPTABLE_FILE_TYPE, ERROR_OVER_COUNT, ERROR_OVER_SIZE, ERROR_UPLOAD, FileUploaderComponent, FlipComponent, ImageUploaderComponent, ImageViewerComponent, InputComponent, LanguageSelectorComponent, MapToAddressComponent, NotificationComponent, NotificationService, PictureNameComponent, SelectComponent, SelectDropdownComponent, SideMenuComponent, SideMenuItemComponent, StlViewerComponent, TableComponent, TabsComponent, TagInputComponent, ToggleComponent, dateToString, provideHaloduckTransloco };
2967
3131
  //# sourceMappingURL=haloduck-ui.mjs.map