npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@libs-ui/components-datetime-input

v0.2.357-9

Published

> Component nhập giờ phút (HH:mm) hỗ trợ single time và time range, tự động format, auto-focus và validation tích hợp.

Readme

@libs-ui/components-datetime-input

Component nhập giờ phút (HH:mm) hỗ trợ single time và time range, tự động format, auto-focus và validation tích hợp.

Giới thiệu

LibsUiComponentsDatetimeInputComponent là component nhập liệu thời gian dạng giờ:phút (HH:mm). Component hỗ trợ hai chế độ: nhập giờ đơn lẻ (single time) và nhập khoảng thời gian (time range với From - To). Giá trị tự động được format thành định dạng chuẩn khi blur và tự động chuyển focus giữa các ô input giúp tăng tốc độ nhập liệu.

Tính năng

  • Single Time Input: Nhập giờ phút đơn giản (HH:mm)
  • Time Range Input: Nhập khoảng thời gian (From HH:mm - To HH:mm) với [multiTime]="true"
  • Auto Format: Tự động format thành HH:mm khi blur (nhập "9" → hiển thị "09")
  • Auto Focus: Tự động chuyển focus hours → minutes → toHours → toMinutes khi nhập đủ ký tự
  • Backspace Navigation: Nhấn Backspace ở ô trống để quay lại ô trước
  • Validation Required: Cấu hình validation bắt buộc nhập giá trị
  • Validation Compare: Kiểm tra giờ kết thúc phải lớn hơn giờ bắt đầu
  • Real-time Emit: Emit data ngay khi nhập từng ký tự với [isEmitRealTime]="true"
  • FunctionsControl: Điều khiển validate, set error, reset error từ component cha
  • Label tích hợp: Hỗ trợ cấu hình label đầy đủ qua [labelConfig]
  • Disable / Readonly: Hỗ trợ trạng thái disable và readonly

Khi nào sử dụng

  • Khi cần nhập giờ phút đơn giản (HH:mm) trong form
  • Khi cần nhập khoảng thời gian (From - To) như giờ làm việc, giờ đặt lịch
  • Khi cần validation bắt buộc hoặc so sánh thời gian bắt đầu/kết thúc
  • Khi cần emit data real-time trong quá trình nhập để cập nhật UI phụ thuộc
  • Khi cần điều khiển validate từ bên ngoài (submit form)

Cài đặt

npm install @libs-ui/components-datetime-input

Import

import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedTimeInput,
  ISelectedMultiTime,
  IDateTimeInputFunctionControlEvent,
  IValidCompare,
} from '@libs-ui/components-datetime-input';

Ví dụ sử dụng

1. Single Time — Nhập giờ đơn

import { Component } from '@angular/core';
import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedTimeInput,
} from '@libs-ui/components-datetime-input';

@Component({
  selector: 'app-single-time',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsDatetimeInputComponent],
  template: `
    <libs_ui-components-datetime-input
      [labelConfig]="{ labelLeft: 'Giờ bắt đầu', required: true }"
      (outEmitSingleTime)="handlerSingleTimeSelected($event)"
    />
  `,
})
export class SingleTimeComponent {
  protected handlerSingleTimeSelected(event: ISelectedTimeInput): void {
    event.stopPropagation?.();
    console.log('Giờ đã chọn:', event);
    // { hours: 9, minute: 30 }
  }
}

2. Time Range — Nhập khoảng thời gian

import { Component, ChangeDetectionStrategy } from '@angular/core';
import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedMultiTime,
} from '@libs-ui/components-datetime-input';

@Component({
  selector: 'app-time-range',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsDatetimeInputComponent],
  template: `
    <libs_ui-components-datetime-input
      [multiTime]="true"
      [labelConfig]="{ labelLeft: 'Khoảng giờ làm việc', required: true }"
      (outEmitMultiTime)="handlerTimeRangeSelected($event)"
    />
  `,
})
export class TimeRangeComponent {
  protected handlerTimeRangeSelected(event: ISelectedMultiTime): void {
    event.from?.(); // { hours: 8, minute: 0 }
    event.to?.();   // { hours: 17, minute: 30 }
  }
}

3. Time Range với Giá Trị Mặc Định (Preset)

import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedMultiTime,
} from '@libs-ui/components-datetime-input';

@Component({
  selector: 'app-time-preset',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsDatetimeInputComponent],
  template: `
    <libs_ui-components-datetime-input
      [multiTime]="true"
      [selectedTime]="selectedTime()"
      [labelConfig]="{ labelLeft: 'Giờ ca làm việc' }"
      (outEmitMultiTime)="handlerTimeRangeSelected($event)"
    />
  `,
})
export class TimePresetComponent {
  // QUAN TRỌNG: from và to PHẢI là WritableSignal
  readonly selectedTime = signal<ISelectedMultiTime>({
    from: signal({ hours: 8, minute: 0 }),
    to: signal({ hours: 17, minute: 30 }),
  });

  protected handlerTimeRangeSelected(event: ISelectedMultiTime): void {
    console.log('Khoảng giờ:', event.from?.(), event.to?.());
  }
}

4. Validation — Required và So Sánh Thời Gian

import { Component, ChangeDetectionStrategy } from '@angular/core';
import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedMultiTime,
} from '@libs-ui/components-datetime-input';

@Component({
  selector: 'app-time-validation',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsDatetimeInputComponent],
  template: `
    <libs_ui-components-datetime-input
      [multiTime]="true"
      [labelConfig]="{ labelLeft: 'Giờ làm thêm', required: true }"
      [validRequired]="{ message: 'Vui lòng nhập giờ' }"
      [validCompareTime]="{
        message: 'Giờ kết thúc phải lớn hơn giờ bắt đầu',
        isCheckErrorTimeEndGreaterTimeStart: true
      }"
      (outEmitMultiTime)="handlerTimeRangeSelected($event)"
      (outEmitValid)="handlerValidChange($event)"
    />
  `,
})
export class TimeValidationComponent {
  protected handlerTimeRangeSelected(event: ISelectedMultiTime): void {
    console.log('Time range:', event);
  }

  protected handlerValidChange(valid: { validRequired: boolean; validCompare: boolean }): void {
    console.log('validRequired:', valid.validRequired);
    console.log('validCompare:', valid.validCompare);
  }
}

5. FunctionsControl — Điều Khiển Từ Bên Ngoài

import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedMultiTime,
  IDateTimeInputFunctionControlEvent,
} from '@libs-ui/components-datetime-input';

@Component({
  selector: 'app-functions-control',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsDatetimeInputComponent],
  template: `
    <libs_ui-components-datetime-input
      [multiTime]="true"
      [labelConfig]="{ labelLeft: 'Giờ làm việc' }"
      [validRequired]="{ message: 'Vui lòng nhập giờ' }"
      (outFunctionsControl)="datetimeControls.set($event)"
      (outEmitMultiTime)="handlerTimeRangeSelected($event)"
    />
    <button (click)="handlerSubmit($event)">Lưu</button>
  `,
})
export class FunctionsControlComponent {
  protected readonly datetimeControls = signal<IDateTimeInputFunctionControlEvent | null>(null);

  protected handlerTimeRangeSelected(event: ISelectedMultiTime): void {
    console.log('Time range:', event);
  }

  protected async handlerSubmit(event: Event): Promise<void> {
    event.stopPropagation();
    const isValid = await this.datetimeControls()?.checkIsValid();
    if (!isValid) {
      return;
    }
    console.log('Form valid, submit...');
  }

  protected async handlerSetError(event: Event): Promise<void> {
    event.stopPropagation();
    await this.datetimeControls()?.setMessageError('Thời gian không hợp lệ với lịch làm việc');
  }

  protected async handlerResetError(event: Event): Promise<void> {
    event.stopPropagation();
    await this.datetimeControls()?.resetError();
  }
}

6. Disable / Readonly

import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import {
  LibsUiComponentsDatetimeInputComponent,
  ISelectedMultiTime,
} from '@libs-ui/components-datetime-input';

@Component({
  selector: 'app-disable-readonly',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsDatetimeInputComponent],
  template: `
    <!-- Readonly — hiển thị giá trị, không cho nhập -->
    <libs_ui-components-datetime-input
      [multiTime]="true"
      [readonly]="true"
      [selectedTime]="workingHours()"
      [labelConfig]="{ labelLeft: 'Giờ làm việc (chỉ xem)' }"
    />

    <!-- Disable — xám toàn bộ -->
    <libs_ui-components-datetime-input
      [disable]="true"
      [labelConfig]="{ labelLeft: 'Giờ nghỉ (không áp dụng)' }"
    />
  `,
})
export class DisableReadonlyComponent {
  readonly workingHours = signal<ISelectedMultiTime>({
    from: signal({ hours: 8, minute: 0 }),
    to: signal({ hours: 17, minute: 0 }),
  });
}

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | [classDatePickerInput] | string | undefined | CSS class bổ sung cho wrapper input | classDatePickerInput="my-time-input" | | [classIncludeBetweenTime] | string \| undefined | undefined | CSS class cho ký tự "-" phân cách giữa From và To | classIncludeBetweenTime="mx-2" | | [classIncludeInput] | string | 'libs-ui-font-h5r' | CSS class cho từng input hours/minutes | classIncludeInput="libs-ui-font-h4r" | | [defaultHeight] | number | 28 | Chiều cao mặc định (px) của ô input | [defaultHeight]="36" | | [disable] | boolean | false | Vô hiệu hóa toàn bộ input | [disable]="true" | | [emitDataSingleDataWhenUseMultiTime] | boolean | false | Khi dùng multiTime, emit từng phần (from/to) riêng biệt dù chưa nhập đủ cả hai | [emitDataSingleDataWhenUseMultiTime]="true" | | [fromAndToDateLabel] | { from?: string; to?: string } | undefined | Label cho phần From và To của time range | [fromAndToDateLabel]="{ from: 'Từ', to: 'Đến' }" | | [ignoreAllowEqualTime] | boolean | true | Khi true, From = To bị coi là lỗi khi dùng isCheckErrorTimeEndGreaterTimeStart. Đặt false để cho phép bằng nhau | [ignoreAllowEqualTime]="false" | | [ignoreBorder] | boolean | false | Ẩn border của wrapper input | [ignoreBorder]="true" | | [ignoreCheckValidWhenInput] | boolean | false | Bỏ qua validate ngay khi nhập từ bàn phím; chỉ validate khi gọi checkIsValid() | [ignoreCheckValidWhenInput]="true" | | [ignoreMultiIcon] | boolean | false | Ẩn icon đồng hồ giữa phần From và To | [ignoreMultiIcon]="true" | | [ignoreShowValid] | boolean | false | Ẩn thông báo lỗi validation phía dưới input | [ignoreShowValid]="true" | | [ignoreWithDefault] | boolean | false | Bỏ qua width mặc định của wrapper, tự do tùy chỉnh kích thước | [ignoreWithDefault]="true" | | [isEmitRealTime] | boolean | false | Emit outEmitRealTime ngay khi người dùng nhập từng ký tự | [isEmitRealTime]="true" | | [labelConfig] | ILabel \| undefined | undefined | Cấu hình label phía trên input (labelLeft, required, description, buttons...) | [labelConfig]="{ labelLeft: 'Giờ vào', required: true }" | | [multiTime] | boolean | false | Bật chế độ nhập khoảng thời gian From - To | [multiTime]="true" | | [readonly] | boolean | false | Chỉ hiển thị giá trị, không cho phép chỉnh sửa | [readonly]="true" | | [resetInput] | boolean | true | Hiển thị nút xóa (X) khi có giá trị | [resetInput]="false" | | [selectedTime] | ISelectedMultiTime \| undefined | undefined | Giá trị được set sẵn từ bên ngoài. fromto BẮT BUỘC là WritableSignal | [selectedTime]="selectedTime()" | | [showBorderError] | boolean | false | Hiển thị border màu đỏ (error state) mà không cần có lỗi validation thực sự | [showBorderError]="true" | | [validCompareTime] | IValidCompare \| undefined | undefined | Cấu hình validation so sánh From và To | [validCompareTime]="{ message: 'Giờ kết thúc phải sau giờ bắt đầu', isCheckErrorTimeEndGreaterTimeStart: true }" | | [validRequired] | IMessageTranslate \| undefined | undefined | Cấu hình validation bắt buộc nhập giá trị | [validRequired]="{ message: 'Vui lòng nhập giờ' }" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outClickButtonLabel) | IButton | Emit khi click một button được cấu hình trên label | handlerClickButtonLabel(e: IButton): void { e.stopPropagation?.(); } | (outClickButtonLabel)="handlerClickButtonLabel($event)" | | (outEmitMultiTime) | ISelectedMultiTime | Emit khi giá trị time range thay đổi và hợp lệ (cả from và to đã nhập đủ) | handlerEmitMultiTime(e: ISelectedMultiTime): void { } | (outEmitMultiTime)="handlerEmitMultiTime($event)" | | (outEmitRealTime) | ISelectedMultiTime | Emit real-time ngay khi nhập từng ký tự (cần bật [isEmitRealTime]="true") | handlerEmitRealTime(e: ISelectedMultiTime): void { } | (outEmitRealTime)="handlerEmitRealTime($event)" | | (outEmitSingleTime) | ISelectedTimeInput | Emit khi giá trị single time thay đổi và hợp lệ | handlerEmitSingleTime(e: ISelectedTimeInput): void { } | (outEmitSingleTime)="handlerEmitSingleTime($event)" | | (outEmitValid) | { validRequired: boolean; validCompare: boolean } | Emit trạng thái validation sau mỗi lần kiểm tra | handlerEmitValid(e: { validRequired: boolean; validCompare: boolean }): void { } | (outEmitValid)="handlerEmitValid($event)" | | (outFunctionsControl) | IDateTimeInputFunctionControlEvent | Emit object chứa các hàm điều khiển component từ bên ngoài | handlerFunctionsControl(e: IDateTimeInputFunctionControlEvent): void { this.controls.set(e); } | (outFunctionsControl)="handlerFunctionsControl($event)" | | (outLabelRightClick) | boolean | Emit true khi click vào phần label bên phải | handlerLabelRightClick(e: boolean): void { e.stopPropagation?.(); } | (outLabelRightClick)="handlerLabelRightClick($event)" | | (outResetTime) | ISelectedMultiTime \| undefined | Emit undefined khi người dùng nhấn nút xóa (X) | handlerResetTime(e: ISelectedMultiTime \| undefined): void { } | (outResetTime)="handlerResetTime($event)" | | (outSwitchEventLabel) | ISwitchEvent | Emit khi toggle switch trên label thay đổi | handlerSwitchEventLabel(e: ISwitchEvent): void { e.stopPropagation?.(); } | (outSwitchEventLabel)="handlerSwitchEventLabel($event)" |

FunctionsControl API

Nhận object IDateTimeInputFunctionControlEvent qua (outFunctionsControl) để điều khiển component từ bên ngoài.

| Method | Signature | Mô tả | |---|---|---| | checkIsValid | () => Promise<boolean> | Kích hoạt kiểm tra validation và trả về true nếu hợp lệ, false nếu có lỗi | | resetError | (resetAll?: boolean) => Promise<void> | Xóa toàn bộ trạng thái lỗi hiện tại | | setMessageError | (message: string \| undefined) => Promise<void> | Hiển thị message lỗi tùy chỉnh; truyền undefined để xóa lỗi |

// Cách sử dụng FunctionsControl
protected readonly controls = signal<IDateTimeInputFunctionControlEvent | null>(null);

protected async handlerSubmit(event: Event): Promise<void> {
  event.stopPropagation();
  const isValid = await this.controls()?.checkIsValid();
  if (!isValid) {
    return;
  }
  // submit logic...
}

protected async handlerSetError(event: Event): Promise<void> {
  event.stopPropagation();
  await this.controls()?.setMessageError('Trùng với ca làm việc khác');
}

protected async handlerResetError(event: Event): Promise<void> {
  event.stopPropagation();
  await this.controls()?.resetError();
}

Types & Interfaces

import {
  ISelectedTimeInput,
  ISelectedMultiTime,
  IValidCompare,
  IDateTimeInputFunctionControlEvent,
} from '@libs-ui/components-datetime-input';

ISelectedTimeInput

Dữ liệu của một mốc thời gian đơn (hours:minutes).

export interface ISelectedTimeInput {
  hours?: string | number;   // Giờ (0-23)
  minute?: string | number;  // Phút (0-59)
}

ISelectedMultiTime

Dữ liệu khoảng thời gian (From - To). fromto bắt buộc là WritableSignal.

import { WritableSignal } from '@angular/core';

export interface ISelectedMultiTime {
  from?: WritableSignal<ISelectedTimeInput>;  // Thời gian bắt đầu
  to?: WritableSignal<ISelectedTimeInput>;    // Thời gian kết thúc
}

// Cách khởi tạo đúng
const myTime = signal<ISelectedMultiTime>({
  from: signal({ hours: 8, minute: 0 }),
  to: signal({ hours: 17, minute: 30 }),
});

// Cách đọc giá trị
myTime().from?.()  // { hours: 8, minute: 0 }
myTime().to?.()    // { hours: 17, minute: 30 }

IValidCompare

Cấu hình validation so sánh giờ bắt đầu và kết thúc. Extends IMessageTranslate.

import { IMessageTranslate } from '@libs-ui/interfaces-types';

export interface IValidCompare extends IMessageTranslate {
  isCheckErrorTimeEndGreaterTimeStart?: boolean;  // true: báo lỗi nếu From >= To
  isCheckErrorTimeDuplicate?: boolean;             // true: báo lỗi nếu From === To
}

// IMessageTranslate
// interface IMessageTranslate {
//   message?: string;
//   interpolateParams?: object;
// }

IDateTimeInputFunctionControlEvent

Object chứa các hàm điều khiển component từ bên ngoài.

export interface IDateTimeInputFunctionControlEvent {
  checkIsValid: () => Promise<boolean>;
  resetError: (resetAll?: boolean) => Promise<void>;
  setMessageError: (message: string | undefined) => Promise<void>;
}

Lưu ý quan trọng

⚠️ WritableSignal bắt buộc cho selectedTime: Các field fromto trong ISelectedMultiTime PHẢI được tạo bằng signal(). Truyền plain object sẽ gây lỗi runtime vì component gọi .() để unwrap.

// ĐÚNG
selectedTime = signal<ISelectedMultiTime>({
  from: signal({ hours: 9, minute: 30 }),
  to: signal({ hours: 17, minute: 0 }),
});

// SAI — sẽ lỗi runtime
selectedTime = signal<ISelectedMultiTime>({
  from: { hours: 9, minute: 30 },  // plain object, không phải signal
  to: { hours: 17, minute: 0 },
});

⚠️ ignoreAllowEqualTime mặc định true: Khi dùng isCheckErrorTimeEndGreaterTimeStart: true, mặc định From bằng To cũng bị coi là lỗi. Để cho phép From = To, thêm [ignoreAllowEqualTime]="false".

⚠️ Backspace navigation cần nhấn 2 lần: Khi ô input đang trống và nhấn Backspace, lần 1 xử lý xóa giá trị (không có gì để xóa), lần 2 mới chuyển focus về ô trước. Đây là hành vi thiết kế có chủ đích.

⚠️ Validation không chạy khi disable hoặc readonly: checkIsValid() trả về true ngay lập tức khi component đang ở trạng thái [disable]="true" hoặc [readonly]="true".

⚠️ outEmitMultiTime chỉ emit khi đủ dữ liệu: Theo mặc định (emitDataSingleDataWhenUseMultiTime = false), outEmitMultiTime chỉ emit khi cả From và To đã nhập đủ giờ và phút. Dùng [emitDataSingleDataWhenUseMultiTime]="true" để emit từng phần.