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-inputs-range-slider

v0.2.357-11

Published

> Component thanh trượt (range slider) cho phép người dùng chọn một giá trị số trong khoảng Min/Max bằng cách kéo, hỗ trợ hai chế độ hiển thị và nhãn giá trị động thông minh.

Readme

@libs-ui/components-inputs-range-slider

Component thanh trượt (range slider) cho phép người dùng chọn một giá trị số trong khoảng Min/Max bằng cách kéo, hỗ trợ hai chế độ hiển thị và nhãn giá trị động thông minh.

Giới thiệu

@libs-ui/components-inputs-range-slider là một Angular Standalone Component cung cấp thanh trượt (range input) đầy đủ tính năng. Component tự động hiển thị nhãn giá trị hiện tại theo vị trí con trỏ kéo và thông minh ẩn/hiện nhãn Min/Max khi bị chồng lấp. Tiến trình (progress) được điều khiển qua CSS Variables giúp hiệu năng rendering cao và dễ tuỳ chỉnh giao diện.

Tính năng

  • ✅ Hai chế độ: slider (có nhãn Min/Max/Value) và audio (tối giản, dùng cho media player)
  • ✅ Nhãn giá trị hiện tại di chuyển theo vị trí kéo, luôn nằm đúng giữa thumb
  • ✅ Tự động ẩn nhãn Min/Max khi bị chồng lấp bởi nhãn giá trị hiện tại
  • ✅ Two-way binding qua model() signal với cú pháp [(value)]
  • ✅ Output outChange được debounce 250ms, tránh phát quá nhiều sự kiện khi đang kéo
  • ✅ Hỗ trợ bước nhảy step tuỳ chỉnh
  • ✅ Hỗ trợ đơn vị đo unit hiển thị sau giá trị (%, px, đ, ...)
  • ✅ Định dạng số theo ngôn ngữ qua formatNumber
  • ✅ Trạng thái disable — ngăn thay đổi giá trị và giữ nguyên giá trị cũ
  • ✅ Class bổ sung qua classInclude không ghi đè class gốc container

Khi nào sử dụng

  • Điều chỉnh âm lượng, độ sáng hoặc các thông số kỹ thuật dạng số
  • Chọn khoảng giá trong bộ lọc tìm kiếm (price range)
  • Thiết lập cấu hình có giới hạn Min/Max rõ ràng
  • Cần interface trực quan, tốn ít diện tích hơn dạng Number Input
  • Điều khiển tiến trình phát nhạc/video (dùng mode audio)

Cài đặt

npm install @libs-ui/components-inputs-range-slider

Import

import { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';

@Component({
  standalone: true,
  imports: [LibsUiComponentsInputsRangeSliderComponent],
  // ...
})
export class YourComponent {}

Ví dụ sử dụng

Cơ bản — slider có nhãn đơn vị

// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';

@Component({
  standalone: true,
  imports: [LibsUiComponentsInputsRangeSliderComponent],
  templateUrl: './volume.component.html',
})
export class VolumeComponent {
  protected volume = signal<number>(50);

  protected handlerVolumeChange(value: number): void {
    // outChange đã được debounce 250ms
    console.log('Volume changed:', value);
  }
}
<!-- volume.component.html -->
<libs_ui-components-inputs-range_slider
  [(value)]="volume"
  [min]="0"
  [max]="100"
  [unit]="'%'"
  (outChange)="handlerVolumeChange($event)">
</libs_ui-components-inputs-range_slider>
<p>Âm lượng: {{ volume() }}%</p>

Chế độ audio — tối giản cho media player

// audio-player.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';

@Component({
  standalone: true,
  imports: [LibsUiComponentsInputsRangeSliderComponent],
  templateUrl: './audio-player.component.html',
})
export class AudioPlayerComponent {
  protected currentTime = signal<number>(0);
  protected duration = 180; // giây

  protected handlerSeek(value: number): void {
    // Seek đến vị trí mới trong audio
    this.audioService.seekTo(value);
  }
}
<!-- audio-player.component.html -->
<libs_ui-components-inputs-range_slider
  [(value)]="currentTime"
  [mode]="'audio'"
  [min]="0"
  [max]="duration"
  [step]="1"
  (outChange)="handlerSeek($event)">
</libs_ui-components-inputs-range_slider>

Bước nhảy (Step) — chỉ cho phép giá trị rời rạc

// config-panel.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';

@Component({
  standalone: true,
  imports: [LibsUiComponentsInputsRangeSliderComponent],
  templateUrl: './config-panel.component.html',
})
export class ConfigPanelComponent {
  protected pageSize = signal<number>(20);

  protected handlerPageSizeChange(value: number): void {
    this.loadData(value);
  }

  private loadData(size: number): void {
    // reload với pageSize mới
  }
}
<!-- config-panel.component.html -->
<libs_ui-components-inputs-range_slider
  [(value)]="pageSize"
  [min]="10"
  [max]="100"
  [step]="10"
  [unit]="'rows'"
  (outChange)="handlerPageSizeChange($event)">
</libs_ui-components-inputs-range_slider>
<p>Hiển thị {{ pageSize() }} hàng mỗi trang</p>

Định dạng số — hiển thị số lớn dễ đọc

// price-filter.component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsRangeSliderComponent } from '@libs-ui/components-inputs-range-slider';

@Component({
  standalone: true,
  imports: [LibsUiComponentsInputsRangeSliderComponent],
  templateUrl: './price-filter.component.html',
})
export class PriceFilterComponent {
  protected budget = signal<number>(5000000);

  protected handlerBudgetChange(value: number): void {
    this.filterProducts(value);
  }

  private filterProducts(maxPrice: number): void {
    // filter sản phẩm theo giá
  }
}
<!-- price-filter.component.html -->
<libs_ui-components-inputs-range_slider
  [(value)]="budget"
  [min]="0"
  [max]="10000000"
  [formatNumber]="true"
  [unit]="'đ'"
  (outChange)="handlerBudgetChange($event)">
</libs_ui-components-inputs-range_slider>
<!-- maxDisplay hiển thị "10,0" thay vì 10000000 -->

Ẩn nhãn giá trị — chỉ thanh kéo đơn thuần

<libs_ui-components-inputs-range_slider
  [(value)]="brightness"
  [min]="0"
  [max]="100"
  [hideProgressingValue]="true"
  [unit]="'%'">
</libs_ui-components-inputs-range_slider>

Disabled — vô hiệu hoá tương tác

<libs_ui-components-inputs-range_slider
  [(value)]="readonlyValue"
  [min]="0"
  [max]="100"
  [disable]="true">
</libs_ui-components-inputs-range_slider>

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | value | number (model) | 0 | Giá trị hiện tại — dùng two-way binding [(value)]. Là Angular model() signal. | [(value)]="volume" | | mode | 'audio' \| 'slider' | 'slider' | Chế độ hiển thị: slider có thanh nhãn Min/Value/Max; audio chỉ hiển thị thanh kéo tối giản (không nhãn, không padding top). | [mode]="'audio'" | | min | number | 1 | Giá trị tối thiểu của khoảng. | [min]="0" | | max | number | 100 | Giá trị tối đa của khoảng. | [max]="200" | | step | number | 0 | Bước nhảy khi kéo. 0 là kéo liên tục mượt mà. | [step]="10" | | unit | string | undefined | Đơn vị đo hiển thị sau mỗi nhãn giá trị (Min, Value, Max). | [unit]="'%'" | | disable | boolean | false | Vô hiệu hoá tương tác. Khi bằng true, người dùng không kéo được; giá trị được giữ nguyên dù có thao tác chuột. | [disable]="true" | | formatNumber | boolean | false | Bật định dạng số cho nhãn Max (maxDisplay). Ví dụ: 1000010,0. Sử dụng viewDataNumberByLanguage từ @libs-ui/utils. | [formatNumber]="true" | | hideProgressingValue | boolean | undefined | Ẩn nhãn giá trị hiện tại di chuyển theo thumb. Chỉ còn nhãn Min và Max cố định. | [hideProgressingValue]="true" | | classInclude | string | undefined | Class CSS bổ sung cho div container ngoài cùng. Class libs-ui-range-slider-container luôn được giữ nguyên, giá trị truyền vào được nối thêm sau. | [classInclude]="'mt-4 w-64'" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outChange) | number | Phát ra giá trị mới sau khi người dùng kéo. Được debounce 250ms — không phát liên tục khi đang kéo. Với mode audio, sự kiện bị skip lần emit đầu tiên (khi khởi tạo). | handlerChange(value: number): void { value; } | (outChange)="handlerChange($event)" |

Logic ngầm quan trọng

Tính toán vị trí nhãn giá trị (Tooltip Position)

Nhãn giá trị hiện tại được định vị tuyệt đối bên dưới thanh kéo. Vị trí (left) được tính dựa trên tỷ lệ % của giá trị trong khoảng [min, max], nhân với chiều rộng container, rồi trừ đi nửa chiều rộng của chính nhãn để căn giữa. Có xử lý thêm để nhãn không tràn ra ngoài phạm vi container.

Xử lý chồng lấp nhãn (Overlap Detection)

Sử dụng getBoundingClientRect() để so sánh vị trí thực tế của nhãn giá trị hiện tại với nhãn Min và Max. Khi nhãn giá trị chạm đến vùng Min hoặc Max, nhãn tương ứng sẽ bị ẩn đi (qua signal visibilityMin / visibilityMax). Khi value = 0, nhãn Min tự động ẩn.

CSS Variables Progress

Phần màu tiến trình (màu xanh đã kéo) không dùng JavaScript để cập nhật DOM trực tiếp mà thông qua CSS custom property --libs-ui-slider-value được set theo phần trăm. Hiệu ứng hover/focus đổi màu thanh trượt nhạt hơn thông qua --libs-ui-slider-border-box-shadow.

Debounce Output

outChange được pipe qua debounceTime(250). Khi người dùng kéo liên tục, chỉ sau khi dừng kéo 250ms thì giá trị mới được emit, tránh gọi API hoặc tính toán nặng liên tục trong khi kéo. Với mode audio, skip 1 lần emit đầu (khi component init) để tránh trigger seek không cần thiết.

Lưu ý quan trọng

⚠️ Two-way binding bắt buộc: value là Angular model() signal — phải dùng [(value)] để đọc giá trị từ bên ngoài và nhận cập nhật ngược lại từ component. Nếu chỉ dùng [value] (một chiều), giá trị trong template ngoài sẽ không tự cập nhật.

⚠️ Debounce 250ms: outChange không phát theo thời gian thực khi đang kéo. Nếu cần phản hồi tức thì (ví dụ: preview màu sắc), hãy đọc trực tiếp signal [(value)] thay vì lắng nghe outChange.

⚠️ mode audio không có nhãn: Khi mode="audio", component không render phần nhãn Min/Value/Max và có chiều cao nhỏ hơn (22px thay vì 44px với padding top). Các input unit, hideProgressingValue, formatNumber không có tác dụng trực quan trong mode này.

⚠️ min mặc định là 1: Giá trị mặc định của min1, không phải 0. Nếu cần khoảng bắt đầu từ 0, phải truyền rõ [min]="0".

⚠️ disable giữ nguyên giá trị: Khi disable=true, component chủ động override giá trị input về giá trị cũ ngay trong handler — người dùng không thể thay đổi bằng cách kéo dù DOM input không bị disabled attribute.

⚠️ classInclude không ghi đè: Class libs-ui-range-slider-container luôn được giữ lại trên container dù classInclude được truyền vào hay không. Các class truyền vào được nối thêm sau.