@sixbell-telco/sdk
Version:
A collection of reusable components designed for use in Sixbell Telco Angular projects
319 lines (314 loc) • 16.5 kB
JavaScript
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