UNPKG

@sixbell-telco/sdk

Version:

A collection of reusable components designed for use in Sixbell Telco Angular projects

319 lines (314 loc) 16.5 kB
import { CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { input, ChangeDetectionStrategy, Component, model, output, signal, effect, computed } from '@angular/core'; import { toObservable, toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop'; import * as i1 from '@angular/forms'; import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import { AutofocusDirective } from '@sixbell-telco/sdk/directives/auto-focus'; import { OnlyNumbersDirective } from '@sixbell-telco/sdk/directives/only-numbers'; import { cn } from '@sixbell-telco/sdk/utils/cn'; import { cva } from 'class-variance-authority'; import { Subject, switchMap, startWith, skip, debounceTime, distinctUntilChanged } from 'rxjs'; class InputContentComponent { alignment = input.required(); static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: InputContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.0", type: InputContentComponent, isStandalone: true, selector: "st-input-content", inputs: { alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@let alignmentVar = alignment();\n\n<!-- <div [class]=\"aligmentVar === 'start' ? 'st-input-content-left' : 'st-input-content-right'\">\n\t<ng-content></ng-content>\n</div> -->\n<!-- input-content.component.html -->\n<div class=\"inline-flex items-center align-top\" [class.justify-start]=\"alignmentVar === 'start'\" [class.justify-end]=\"alignmentVar === 'end'\">\n\t<ng-content></ng-content>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: InputContentComponent, decorators: [{ type: Component, args: [{ selector: 'st-input-content', imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let alignmentVar = alignment();\n\n<!-- <div [class]=\"aligmentVar === 'start' ? 'st-input-content-left' : 'st-input-content-right'\">\n\t<ng-content></ng-content>\n</div> -->\n<!-- input-content.component.html -->\n<div class=\"inline-flex items-center align-top\" [class.justify-start]=\"alignmentVar === 'start'\" [class.justify-end]=\"alignmentVar === 'end'\">\n\t<ng-content></ng-content>\n</div>\n" }] }] }); /* eslint-disable @typescript-eslint/no-explicit-any */ /** * @internal * Generates base input classes with style variants using class-variance-authority */ const inputComponent = cva(['font-body', 'w-full', 'input', 'leading-normal', 'grow'], { variants: { variant: { primary: ['input-primary'], secondary: ['input-secondary'], tertiary: ['input-tertiary'], accent: ['input-accent'], info: ['input-info'], success: ['input-success'], warning: ['input-warning'], error: ['input-error'], }, size: { xs: ['input-xs'], sm: ['input-sm'], md: ['input-md'], lg: ['input-lg'], xl: ['input-xl'], }, }, compoundVariants: [], defaultVariants: { variant: 'secondary', size: 'md', }, }); /** * A customizable input component with validation, debouncing, and styling variants * * @remarks * Implements ControlValueAccessor for seamless integration with Angular forms. * Supports both template-driven and reactive forms, with automatic validation styling. * * @example * ```html * <!-- Basic usage --> * <st-input label="Username" [(value)]="username"></st-input> * ``` * * @example * ```html * <!-- With validation and custom styling --> * <st-input * variant="secondary" * size="lg" * label="Email" * type="email" * [parentForm]="userForm" * formControlName="email" * ></st-input> * ``` */ class InputComponent { /** * Input style variant * @defaultValue 'secondary' */ variant = input('secondary'); /** * Input size variant * @defaultValue 'md' */ size = input('md'); /** * Label text displayed above the input */ label = input(''); /** * HTML input type * @defaultValue 'text' */ type = input('text'); /** * HTML name attribute for the input */ name = input(null); /** * Input placeholder text */ placeholder = input(''); /** * Whether the input is read-only * @defaultValue false */ readonly = input(false); focus = input(false); /** * Whether to restrict input to numeric characters only * @defaultValue false * @deprecated Use `onlyNumbers` instead. This input is maintained for backward compatibility. */ justNumbers = input(false); /** * Whether to restrict input to numeric characters only * @defaultValue false */ onlyNumbers = input(false); /** * Whether to use ghost (transparent) styling * @defaultValue false */ ghost = input(false); /** * Two-way bindable input value */ value = model(''); /** * Parent form group for reactive forms */ parentForm = input(null); /** * Form control name for reactive forms */ formControlName = input(''); // ControlValueAccessor implementation onControlChange = () => { }; onControlTouch = () => { }; /** * Whether the input is disabled * @defaultValue false */ disabled = model(false); /** * Event emitted when Enter key is pressed */ enterPressed = output(); /** * Event emitted when input loses focus */ blurred = output(); /** * Event emitted with debounced value changes */ valueDebounced = output(); /** * Debounce time in milliseconds for value changes * @defaultValue 500 */ debounceTime = input(500); // Internal implementation details blurTrigger = signal(0); debounceAction$ = new Subject(); debounceTimeEffect = effect(() => { this.debounceAction$.next(this.debounceTime()); }); value$ = toObservable(this.value); constructor() { this.setupDebounce(); } /** * @internal * Computed base input classes */ componentClass = computed(() => { return cn(inputComponent({ variant: this.variant(), size: this.size(), }), { 'input-ghost': this.ghost(), }); }); /** * @internal * Computed error state classes */ errorClass = computed(() => { return cn(inputComponent({ variant: 'error', size: this.size(), }), { 'input-ghost': this.ghost(), }); }); /** * @internal * Computed success state classes */ successClass = computed(() => { return cn(inputComponent({ variant: 'success', size: this.size(), }), { 'input-ghost': this.ghost(), }); }); // ControlValueAccessor implementation methods writeValue(obj) { this.value.set(obj); } registerOnChange(fn) { this.onControlChange = fn; } registerOnTouched(fn) { this.onControlTouch = fn; } setDisabledState(isDisabled) { this.disabled.set(isDisabled); } /** * @internal * Handles input value changes */ handleChange() { this.onControlChange(this.value()); } /** * @internal * Handles Enter key press */ handleEnter() { this.enterPressed.emit(this.value()); this.onControlChange(this.value()); } /** * @internal * Handles blur event */ handleBlur() { this.blurred.emit(this.value()); this.onControlTouch(); this.blurTrigger.update((v) => v + 1); } /** @internal */ formControl = computed(() => this.parentForm()?.get(this.formControlName())); /** @internal */ formControl$ = toObservable(this.formControl); /** @internal */ statusChanges$ = this.formControl$.pipe(switchMap((control) => control?.statusChanges.pipe(startWith(control.status)) || [])); /** @internal */ stateChanges$ = this.formControl$.pipe(switchMap((control) => control?.valueChanges.pipe(startWith(control.value)) || [])); /** @internal */ statusSignal = toSignal(this.statusChanges$); /** @internal */ stateSignal = toSignal(this.stateChanges$); /** * @internal * Reactive validation class computation */ validationClass = computed(() => { const control = this.formControl(); const trigger = this.blurTrigger(); if (!control) return this.componentClass(); const isTouched = control.touched || trigger > 0; this.statusSignal(); this.stateSignal(); if (control.dirty || isTouched) { if (control.invalid) return this.errorClass(); if (control.valid) return this.successClass(); } return this.componentClass(); }); /** * @internal * Sets up debounced value changes */ setupDebounce() { this.debounceAction$ .pipe(switchMap((time) => this.value$.pipe(skip(1), debounceTime(time), distinctUntilChanged())), takeUntilDestroyed()) .subscribe((value) => { this.valueDebounced.emit(`${value}`); }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: InputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.0", type: InputComponent, isStandalone: true, selector: "st-input", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, focus: { classPropertyName: "focus", publicName: "focus", isSignal: true, isRequired: false, transformFunction: null }, justNumbers: { classPropertyName: "justNumbers", publicName: "justNumbers", isSignal: true, isRequired: false, transformFunction: null }, onlyNumbers: { classPropertyName: "onlyNumbers", publicName: "onlyNumbers", isSignal: true, isRequired: false, transformFunction: null }, ghost: { classPropertyName: "ghost", publicName: "ghost", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, parentForm: { classPropertyName: "parentForm", publicName: "parentForm", isSignal: true, isRequired: false, transformFunction: null }, formControlName: { classPropertyName: "formControlName", publicName: "formControlName", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, debounceTime: { classPropertyName: "debounceTime", publicName: "debounceTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", enterPressed: "enterPressed", blurred: "blurred", valueDebounced: "valueDebounced" }, providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: InputComponent, multi: true, }, ], ngImport: i0, template: "@let labelVar = label();\n\n<fieldset class=\"fieldset p-0\">\n\t@if (labelVar) {\n\t\t<legend class=\"fieldset-legend font-body pt-0 text-pretty\">{{ labelVar }}</legend>\n\t}\n\t<div [class]=\"validationClass()\">\n\t\t<!-- Left content -->\n\t\t<ng-content select=\"st-input-content[alignment='start']\"></ng-content>\n\n\t\t<!-- Input field -->\n\t\t<input\n\t\t\tclass=\"placeholder-base-placeholder w-full grow\"\n\t\t\t[type]=\"type()\"\n\t\t\t[attr.name]=\"name()\"\n\t\t\t[attr.placeholder]=\"placeholder()\"\n\t\t\t[(ngModel)]=\"value\"\n\t\t\t[disabled]=\"disabled()\"\n\t\t\t[readonly]=\"readonly()\"\n\t\t\t(ngModelChange)=\"handleChange()\"\n\t\t\t(keyup.enter)=\"handleEnter()\"\n\t\t\t(blur)=\"handleBlur()\"\n\t\t\t[stOnlyNumbers]=\"onlyNumbers() || justNumbers()\"\n\t\t\t[focus]=\"focus()\"\n\t\t\tstAutofocus\n\t\t/>\n\n\t\t<!-- Right content -->\n\t\t<ng-content select=\"st-input-content[alignment='end']\"></ng-content>\n\t</div>\n\n\t<!-- Hidden label for accessibility -->\n\t<label class=\"sr-only\" [for]=\"name()\">{{ labelVar }}</label>\n\n\t<!-- Additional content -->\n\t<ng-content select=\":not(st-input-content)\"></ng-content>\n</fieldset>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: AutofocusDirective, selector: "[stAutofocus]", inputs: ["focus"] }, { kind: "directive", type: OnlyNumbersDirective, selector: "[stOnlyNumbers], [stJustNumbers]", inputs: ["stOnlyNumbers", "justNumbers"] }] }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.0", ngImport: i0, type: InputComponent, decorators: [{ type: Component, args: [{ selector: 'st-input', imports: [FormsModule, AutofocusDirective, OnlyNumbersDirective], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: InputComponent, multi: true, }, ], template: "@let labelVar = label();\n\n<fieldset class=\"fieldset p-0\">\n\t@if (labelVar) {\n\t\t<legend class=\"fieldset-legend font-body pt-0 text-pretty\">{{ labelVar }}</legend>\n\t}\n\t<div [class]=\"validationClass()\">\n\t\t<!-- Left content -->\n\t\t<ng-content select=\"st-input-content[alignment='start']\"></ng-content>\n\n\t\t<!-- Input field -->\n\t\t<input\n\t\t\tclass=\"placeholder-base-placeholder w-full grow\"\n\t\t\t[type]=\"type()\"\n\t\t\t[attr.name]=\"name()\"\n\t\t\t[attr.placeholder]=\"placeholder()\"\n\t\t\t[(ngModel)]=\"value\"\n\t\t\t[disabled]=\"disabled()\"\n\t\t\t[readonly]=\"readonly()\"\n\t\t\t(ngModelChange)=\"handleChange()\"\n\t\t\t(keyup.enter)=\"handleEnter()\"\n\t\t\t(blur)=\"handleBlur()\"\n\t\t\t[stOnlyNumbers]=\"onlyNumbers() || justNumbers()\"\n\t\t\t[focus]=\"focus()\"\n\t\t\tstAutofocus\n\t\t/>\n\n\t\t<!-- Right content -->\n\t\t<ng-content select=\"st-input-content[alignment='end']\"></ng-content>\n\t</div>\n\n\t<!-- Hidden label for accessibility -->\n\t<label class=\"sr-only\" [for]=\"name()\">{{ labelVar }}</label>\n\n\t<!-- Additional content -->\n\t<ng-content select=\":not(st-input-content)\"></ng-content>\n</fieldset>\n" }] }], ctorParameters: () => [] }); /** * Generated bundle index. Do not edit. */ export { InputComponent, InputContentComponent }; //# sourceMappingURL=sixbell-telco-sdk-components-forms-input.mjs.map