Tài liệu API

Forge Dialog là thư viện hộp thoại TypeScript không phụ thuộc, xây trên phần tử <dialog> của trình duyệt. Mọi thứ trên trang này đều nằm trong gói đã phát hành — không có plugin phải cài và không có peer dependency phải thoả mãn.

Cài đặt

Cài từ npm. Tên gói viết liền một từ; tên thư viện là Forge Dialog.

npm install forgedialog

Import API và một tệp CSS:

import { alert, confirm, prompt, open } from 'forgedialog';
import 'forgedialog/style.css';

Hoặc nạp thẳng bản dựng cho trình duyệt. Nó tạo biến toàn cục ForgeDialog — đây là định danh JavaScript nên viết liền một từ.

<link rel="stylesheet" href="https://unpkg.com/forgedialog/dist/index.css" />
<script src="https://unpkg.com/forgedialog/dist/index.global.js"></script>
<script>
  ForgeDialog.alert('Hello!');
</script>

Yêu cầu Chrome/Edge 88+, Firefox 78+, Safari 15.4+, hoặc Node.js 20+ cho công cụ đóng gói và import phía máy chủ.

Điểm vào

Mọi điểm vào đều tree-shake được và có hạn mức gzip riêng, kiểm tra tự động trong CI. Hãy import cái hẹp nhất đủ dùng.

Import Bao gồm
forgedialog Tất cả, đã bật sẵn kéo thả, hiệu ứng, và bộ áp giao diện đầy đủ.
forgedialog/core open(), alert, confirm, prompt, chủ đề, nhãn, plugin, và bộ áp giao diện đầy đủ.
forgedialog/alert Chỉ alert().
forgedialog/confirm Chỉ confirm().
forgedialog/prompt Chỉ prompt().
forgedialog/presentation drawer, bottomSheet, lightbox, loading, toast, commandPalette, notificationCenter.
forgedialog/workflows form, formWizard, wizard.
forgedialog/interactions Bộ điều khiển kéo thả. Có tác dụng phụ: chỉ cần import là kéo thả được bật.
forgedialog/animations Các hiệu ứng dựng sẵn. Có tác dụng phụ: chỉ cần import là hiệu ứng được bật.
forgedialog/appearance Bộ áp giao diện đầy đủ. Có tác dụng phụ: import nó là nâng cấp các điểm vào chuyên biệt.
forgedialog/react useForgeDialog() cho React.
forgedialog/vue useForgeDialog() cho Vue.
forgedialog/svelte Action dialogTrigger.
forgedialog/web-component defineForgeDialog()ForgeDialogElement.

Import theo năng lực

Các điểm vào chuyên biệt (/alert, /confirm, /prompt) đi kèm bộ áp giao diện nhẹ, chỉ gồm độ mờ nền, lớp phủ, viền, và các bóng dựng sẵn. Thêm năng lực bằng cách import chúng để lấy tác dụng phụ:

import 'forgedialog/appearance';   // per-component colour, radius, hover
import 'forgedialog/interactions'; // dragging
import 'forgedialog/animations';   // animation presets
import { alert } from 'forgedialog/alert';

forgedialogforgedialog/core đã có sẵn bộ áp giao diện đầy đủ, nên chỉ cần import thêm khi bạn dùng điểm vào chuyên biệt.

Tệp CSS

forgedialog/style.css là tệp gộp tất cả. Muốn gửi ít CSS hơn thì chỉ ghép các lớp mà trang thật sự dùng — core.css thì luôn bắt buộc.

import 'forgedialog/style/core.css';      // required
import 'forgedialog/style/forms.css';     // form fields and file dropzones
import 'forgedialog/style/workflows.css'; // wizard stepper
import 'forgedialog/style/toast.css';
import 'forgedialog/style/lightbox.css';
import 'forgedialog/style/command.css';
import 'forgedialog/style/draggable.css';

Mọi quy tắc đều nằm trong cascade layer forgedialog, nên CSS không đặt trong layer của bạn sẽ thắng mà không cần !important.

alert, confirm, prompt

Bản thay thế trả về promise cho các hàm dựng sẵn của trình duyệt.

Chữ ký hàm Trả về
alert(message, options?) Promise<void>
confirm(message, options?) Promise<boolean>false khi đóng bằng Escape hoặc click nền
prompt(message, options?) Promise<string | null>null khi bị huỷ
await alert('Saved successfully.', { title: 'Success' });

const ok = await confirm('Delete this item?', { title: 'Please confirm' });

const name = await prompt('What is your name?', {
  defaultValue: 'Ada Lovelace',
  inputType: 'text',
  inputLabel: 'Full name',
  placeholder: 'Type a name',
  validate: (value) => (value.trim() ? true : 'Name is required'),
});

alertconfirm tự động nhận role="alertdialog". prompt nhận thêm các tuỳ chọn ở trên, bên cạnh mọi trường của DialogOptions.

open()

API tầng thấp. Nó trả về DialogInstance ngay lập tức chứ không phải promise, nên hộp thoại có thể được cập nhật, di chuyển, hoặc đóng từ bất cứ đâu.

import { open } from 'forgedialog';

const dialog = open({
  title: 'Custom dialog',
  content: (container) => container.append(renderForm()),
  buttons: [
    { text: 'Cancel', onClick: (i) => i.close(null) },
    { text: 'Save', role: 'primary', autoFocus: true, onClick: (i) => i.close(read()) },
  ],
});

const result = await dialog.whenClosed();          // the result, or undefined
const { result: r, reason } = await dialog.whenSettled(); // result plus why it closed

Nội dung và HTML không tin cậy

content nhận một chuỗi (hiển thị dưới dạng văn bản), một phần tử, hoặc một hàm dựng. Còn HTML thì phải quyết định rõ ràng:

// Untrusted markup: pass a sanitizer.
open({ html: userContent, sanitizeHtml: (h) => DOMPurify.sanitize(h) });

// Markup you already trust.
open({ unsafeHtml: '<p>Generated by us</p>' });

Lý do đóng

Mỗi lần đóng đều kèm một lý do: 'button', 'escape', 'backdrop', 'api', 'abort', hoặc 'destroy'. Đọc nó bằng whenSettled().

DialogOptions

Được nhận bởi open() và mọi hàm tiện ích dựng trên nó.

Tuỳ chọn Kiểu Mô tả
title string Chữ ở thanh tiêu đề. Nếu bỏ trống, hộp thoại vẫn có nhãn ẩn về mặt thị giác để công nghệ hỗ trợ gọi được tên nó.
message string | HTMLElement Văn bản hoặc phần tử cho phần thân.
content string | HTMLElement | (container) => void Nội dung phần thân. Chuỗi được hiển thị dưới dạng văn bản.
html string HTML, chỉ được hiển thị khi có kèm sanitizeHtml.
unsafeHtml string HTML chèn nguyên trạng. Chỉ dùng cho nội dung bạn kiểm soát được.
sanitizeHtml (html: string) => string Hàm làm sạch áp cho html.
buttons ButtonConfig[] Các nút ở chân hộp thoại. Xem bên dưới.
type 'alert' | 'confirm' | 'prompt' | 'form' | 'wizard' | 'custom' Loại ngữ nghĩa. alertconfirm mặc định dùng role="alertdialog".
role 'dialog' | 'alertdialog' Ghi đè role suy ra từ type.
size 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen' Bề rộng dựng sẵn.
presentation 'modal' | 'drawer-left' | 'drawer-right' | 'bottom-sheet' | 'lightbox' Cách hộp thoại xuất hiện và vị trí nó nằm.
animation 'fade' | 'scale' | 'slide' | 'spring' | 'bounce' | 'blur' | 'none' Cần năng lực animations. Tôn trọng prefers-reduced-motion.
appearance DialogAppearance Tạo kiểu riêng cho từng hộp thoại. Xem Giao diện.
draggable boolean | DraggableOptions Xem Kéo thả.
closable boolean Hiện nút đóng ở thanh tiêu đề. Mặc định true.
closeOnEscape boolean Mặc định true.
closeOnOverlayClick boolean Mặc định true.
initialFocus string | HTMLElement | (root) => HTMLElement | null Phần tử nhận tiêu điểm khi mở. Mặc định là phần tử tab được đầu tiên.
restoreFocus boolean Trả tiêu điểm về phần tử đã mở hộp thoại khi đóng. Mặc định true.
portalTarget HTMLElement Nơi hộp thoại được gắn vào. Mặc định là document.body.
className string Các class thêm vào phần tử hộp thoại.
labels Partial<DialogLabels> Ghi đè nhãn cho riêng hộp thoại này.
signal AbortSignal Huỷ bỏ sẽ đóng hộp thoại với lý do 'abort'.
data unknown Dữ liệu tuỳ ý, đọc được từ các hook.
onOpen (instance) => void | Promise<void> Sau khi hộp thoại mở.
onBeforeClose (instance, result) => boolean | void | Promise<…> Trả về false để giữ nó mở.
onClose (instance, result) => void | Promise<void> Sau khi hộp thoại đóng.
onError (error, instance) => void Theo dõi lỗi trong vòng đời, những lỗi này được dọn dẹp tự động.

ButtonConfig

Trường Kiểu Mô tả
text string Nhãn. Bắt buộc.
role 'primary' | 'secondary' | 'danger' Mức nhấn thị giác.
onClick (instance) => void | Promise<void> Hàm xử lý khi bấm.
closesDialog boolean Đóng hộp thoại sau khi chạy onClick.
result TResult Giá trị hộp thoại trả về khi nút này đóng nó.
autoFocus boolean Nhận tiêu điểm khi mở.
disabled boolean Hiển thị nút ở trạng thái vô hiệu.
id string Id của phần tử, tiện cho việc kiểm thử.

DialogInstance

Thành phần Trả về Mô tả
open() Promise<void> Mở một hộp thoại chưa được mở.
close(result?, reason?) Promise<void> Đóng kèm một kết quả.
cancel(reason?) Promise<void> Đóng mà không có kết quả.
destroy() Promise<void> Đóng rồi gỡ bỏ phần tử cùng các listener của nó.
update(partialOptions) void Đổi kiểu hoặc vẽ lại hộp thoại tại chỗ, ngay khi nó đang mở.
whenClosed() Promise<TResult | undefined> Trả về kết quả.
whenSettled() Promise<{ result, reason }> Trả về kết quả kèm lý do đóng.
isOpen() boolean Hộp thoại có đang mở hay không.
getState() DialogState 'idle' | 'opening' | 'open' | 'closing' | 'closed' | 'destroyed'.
getPosition() { x, y } Độ dịch chuyển kéo hiện tại.
setPosition({ x, y }) { x, y } Di chuyển hộp thoại, giới hạn trong vùng cho phép.
resetPosition() void Đưa nó về giữa.
element HTMLElement Phần tử hộp thoại.
id string Id duy nhất.

Giao diện

Các tuỳ chỉnh giao diện chỉ áp cho một hộp thoại và đổi được lúc chạy bằng update(). Giá trị độ mờ bị kẹp trong khoảng 0..1; số trần cho độ dày, bo góc, và độ nhoè được hiểu là pixel.

Trường Kiểu Áp dụng cho
opacity number Toàn bộ nền hộp thoại.
surfaceColor string Nền hộp thoại.
overlayOpacity number Lớp phủ phía sau nó.
backdropBlur number | string Bán kính làm mờ lớp phủ.
titleColor string Chữ ở thanh tiêu đề.
titleBackground string Nền thanh tiêu đề. Mặc định trong suốt và bị cắt theo góc bo của hộp thoại.
titleOpacity number Thanh tiêu đề.
contentColor string Chữ ở phần thân.
contentOpacity number Phần thân.
borderColor string Viền.
borderOpacity number Viền.
borderWidth number | string Viền.
borderStyle 'none' | 'solid' | 'dashed' | 'dotted' | 'double' Viền.
radius number | string | DialogCornerRadius Các góc. Xem Bo góc.
shadow ShadowPreset | string | DialogShadowConfig Xem Đổ bóng.
hover DialogHoverAppearance Xem Hover.
open({
  title: 'Styled',
  appearance: {
    opacity: 0.96,
    surfaceColor: '#12141a',
    overlayOpacity: 0.55,
    backdropBlur: 12,
    titleColor: '#ffd166',
    titleBackground: '#1b1740',
    contentColor: '#c8ccd4',
    borderColor: '#7c5cff',
    borderWidth: 2,
    radius: 20,
    shadow: { angle: 135, distance: 24, blur: 60, opacity: 0.45 },
  },
});

Bộ áp đầy đủ đi kèm forgedialogforgedialog/core. Với điểm vào chuyên biệt, hãy thêm import 'forgedialog/appearance' — không có nó thì các trường nâng cao bị bỏ qua chứ không báo lỗi.

Đổ bóng

shadow nhận ba dạng.

Dạng Ví dụ
Bóng dựng sẵn shadow: 'xl' — một trong none, sm, md, lg, xl
Bất kỳ giá trị box-shadow nào của CSS shadow: '0 10px 40px rgb(0 0 0 / 30%)'
Ghép từ các thành phần shadow: { angle: 90, distance: 18, blur: 48, opacity: 0.3 }
Thành phần Kiểu Ý nghĩa
angle number Hướng bóng đổ, tính bằng độ: 0 lên trên, 90 sang phải, 180 xuống dưới (mặc định), 270 sang trái.
distance number | string Bóng bị hắt đi xa bao nhiêu.
blur number | string Độ mềm.
spread number | string Độ nở ra trước khi làm mờ.
color string Màu bóng.
opacity number Độ đậm, 0..1.
inset boolean Đổ bóng vào phía trong.

Bo góc

radius nhận một giá trị dùng chung cho mọi góc, bất kỳ chuỗi border-radius nào của CSS, hoặc một object khai theo từng góc. Góc không có trong object đó vẫn giữ bo góc của chủ đề chứ không tụt về 0.

appearance: { radius: 20 }
appearance: { radius: '20px 20px 4px 4px' }
appearance: { radius: { topLeft: 24, topRight: 24, bottomRight: 4, bottomLeft: 4 } }

Drawer giữ góc vuông và bottom sheet chỉ bo hai góc trên, trừ khi radius nói khác.

Hover

hover chỉ đổi kiểu hộp thoại khi con trỏ đang ở trên nó, và chỉ với hộp thoại có yêu cầu — mọi hộp thoại khác đứng yên hoàn toàn. Nó dùng lại các trường màu, bo góc, và đổ bóng, cộng thêm ba trường riêng.

Trường Kiểu Mô tả
lift number | string Hộp thoại nhấc lên bao nhiêu.
scale number Hệ số phóng, ví dụ 1.02.
duration number | string Thời lượng chuyển động.

Biến đổi khi hover tôn trọng prefers-reduced-motion và bị tạm ngưng trong lúc kéo hộp thoại, nên không thể đẩy nó ra ngoài vùng giới hạn.

Kéo thả

draggable: true là đủ để kéo hộp thoại bằng thanh tiêu đề. Dạng object thêm ràng buộc, hỗ trợ bàn phím, và ghi nhớ vị trí. Kéo thả cần năng lực interactions, thứ mà forgedialog đã bật sẵn cho bạn.

Tuỳ chọn Kiểu Mô tả
handle 'header' | string | HTMLElement Thứ khởi động thao tác kéo. Mặc định là thanh tiêu đề.
axis 'both' | 'x' | 'y' Khoá chuyển động theo một trục.
bounds 'viewport' | HTMLElement | DOMRect Vùng mà hộp thoại bị giới hạn trong đó.
initialPosition { x, y } Vị trí lệch ban đầu.
keyboard boolean Di chuyển bằng phím mũi tên khi tay cầm đang có tiêu điểm.
keyboardStep number Số pixel mỗi lần nhấn phím.
persistKey string Ghi nhớ vị trí qua các phiên dưới khoá này.
onDragStart / onDrag / onDragEnd (event) => void Mỗi hàm nhận { position, originalEvent }.

Bottom sheet giữ cử chỉ vuốt để đóng của riêng nó và bỏ qua kéo thả thông thường.

Drawer, sheet, lightbox, trạng thái tải

import { drawer, bottomSheet, lightbox, loading } from 'forgedialog';

drawer({ title: 'Settings', side: 'right', content: renderSettings });

bottomSheet({ title: 'Share', content: renderShare });

lightbox('/photo.jpg', { alt: 'A photo', caption: 'Taken in 2026' });

const task = loading('Uploading…');
task.update('Almost there…');
await task.close();

Đây chỉ là open() kèm một giá trị presentation dựng sẵn, nên mọi trường của DialogOptions vẫn dùng được.

Biểu mẫu

form() dựng một hộp thoại có kiểm tra từ danh sách trường và trả về các giá trị, hoặc null nếu bị huỷ. Tên trường được suy ra vào kiểu kết quả, nên values.email đã có kiểu mà không cần ép.

import { form } from 'forgedialog';

const values = await form(
  [
    { name: 'email', type: 'email', label: 'Email', required: true },
    { name: 'plan', type: 'select', label: 'Plan', options: [
      { value: 'free', label: 'Free' },
      { value: 'pro', label: 'Pro' },
    ] },
    { name: 'notes', type: 'textarea', label: 'Notes', rows: 4 },
  ],
  { title: 'Sign up', submitText: 'Create account' },
);

Các kiểu trường: text, password, email, number, date, textarea, select, checkbox, radio, file. Mỗi trường nhận label, defaultValue, placeholder, helpText, required, và một hàm validate; kiểu số có thêm min, max, step, còn kiểu tệp có thêm accept, multiple, maxFiles, và maxSizeBytes.

formWizard(steps, options?) trải đúng các trường đó ra nhiều bước, kèm nextText, backText, finishText, và callback onStepChange.

Wizard

wizard() là dạng tổng quát: mỗi bước tự vẽ nội dung tuỳ ý, có thể kiểm tra dữ liệu, và có thể chọn bước kế tiếp dựa trên dữ liệu đã thu thập.

import { wizard } from 'forgedialog';

const flow = wizard({
  initialData: { email: '', plan: 'free' },
  persistKey: 'signup',      // resumes where the user left off
  confirmUnsaved: true,      // asks before discarding progress
  steps: [
    { id: 'account', title: 'Account', render: renderAccount, validate: validateAccount },
    { id: 'plan', title: 'Plan', render: renderPlan,
      next: (data) => (data.plan === 'pro' ? 'billing' : 'review') },
    { id: 'billing', title: 'Billing', render: renderBilling },
    { id: 'review', title: 'Review', render: renderReview },
  ],
});

const data = await flow.result; // the data, or null if cancelled

Bộ điều khiển còn cung cấp next(), back(), goTo(stepId), getData(), và instance bên dưới.

Toast và thông báo

import { toast, notificationCenter, getNotificationHistory } from 'forgedialog';

toast('Saved', { tone: 'success' });

// A duration of 0 or Infinity keeps it up until dismiss() is called.
const upload = toast('Uploading…', { duration: 0 });
upload.dismiss();

toast('Message sent', { action: { text: 'Undo', onClick: undoSend } });

notificationCenter();          // a dialog listing past toasts
getNotificationHistory();      // the same list as data

Các sắc thái gồm info, success, warning, và danger. clearNotificationHistory() xoá sạch nhật ký.

Bảng lệnh

import { commandPalette } from 'forgedialog';

commandPalette([
  { id: 'new', label: 'New document', shortcut: '⌘N', run: createDocument },
  { id: 'search', label: 'Search', keywords: ['find', 'filter'], run: openSearch },
]);

Gõ để lọc theo nhãn và theo keywords; bảng lệnh trả về id của lệnh đã chạy.

Chủ đề

import { setTheme, getTheme, setThemePreset } from 'forgedialog';

setTheme('dark');    // 'light' | 'dark' | 'system'
getTheme();

setThemePreset('glass'); // 'default' | 'minimal' | 'glass' | 'material'

setTheme() ghi data-fd-theme lên <html>. CSS của bạn có thể đọc chính thuộc tính đó để trang và hộp thoại đổi chủ đề cùng lúc — đây đúng là cách site này làm.

Biến CSS

Mọi thứ thuộc về hình thức đều là một biến trên :root, nên viết được cả một chủ đề mà không đụng tới JavaScript.

Biến Mặc định
--fd-color-surface #ffffff
--fd-color-text #1a1d23
--fd-color-text-muted #5b6270
--fd-color-border #e2e4e9
--fd-color-overlay rgba(15, 17, 21, 0.5)
--fd-color-primary / --fd-color-primary-text #315fce / #ffffff
--fd-color-secondary / --fd-color-secondary-text #eceef2 / #1a1d23
--fd-color-danger / --fd-color-danger-text #e5484d / #ffffff
--fd-radius 10px
--fd-shadow 0 20px 60px rgba(15, 17, 21, 0.25)
--fd-spacing-sm / md / lg 8px / 16px / 24px
--fd-font-family bộ phông hệ thống
--fd-duration-fast / --fd-duration-normal 120ms / 200ms
--fd-easing cubic-bezier(0.16, 1, 0.3, 1)
--fd-z-index-base 1000

Các biến riêng từng hộp thoại mà appearance API ghi ra (--fd-dialog-radius, --fd-dialog-shadow, --fd-dialog-title-background, và những cái còn lại) cũng đặt tay được, nếu bạn thích tạo kiểu bằng CSS hơn bằng JavaScript.

Nhãn và đa ngôn ngữ

import { setLabels, getLabels } from 'forgedialog';

setLabels({
  ok: 'Đồng ý',
  cancel: 'Huỷ',
  close: 'Đóng',
  submit: 'Gửi',
  fieldRequired: 'Bắt buộc',
  promptPlaceholder: 'Nhập nội dung',
  notifications: 'Thông báo',
});

Nhãn đặt theo cách này áp dụng ở mọi nơi; từng hộp thoại vẫn ghi đè được bằng tuỳ chọn labels của riêng nó.

Plugin và hook

Năm hook vòng đời chạy cho mọi hộp thoại: beforeOpen, afterOpen, beforeClose, afterClose, và beforeDestroy.

import { on, off, registerPlugin } from 'forgedialog';

// A single hook.
on('afterOpen', ({ instance, options }) => track('dialog', options.title));

// Or a plugin bundling several.
registerPlugin({
  name: 'analytics',
  install: (api) => api.on('afterClose', ({ reason }) => track('close', reason)),
  hooks: {
    beforeClose: ({ preventClose, result }) => {
      if (result === undefined) preventClose?.();
    },
  },
});

Mỗi hook nhận { instance, options, result, reason, preventClose }. Gọi preventClose() trong beforeClose sẽ giữ hộp thoại mở.

Adapter cho framework

Mỗi adapter là một lớp bọc mỏng quanh open(), gắn vòng đời hộp thoại vào vòng đời component, nên không còn gì sót lại sau khi component bị gỡ.

// React and Vue share the same hook shape.
import { useForgeDialog } from 'forgedialog/react';
const { open } = useForgeDialog();
await open({ title: 'Hi' }).whenClosed();

// Svelte: an action on the triggering element.
import { dialogTrigger } from 'forgedialog/svelte';
// <button use:dialogTrigger={{ title: 'Hi' }}>Open</button>

// Web component.
import { defineForgeDialog } from 'forgedialog/web-component';
defineForgeDialog(); // <forge-dialog open title="Hi" message="…" size="md">

TypeScript, SSR, và CSP

  • Có kiểu cho cả ESM lẫn CJS. open<T>() luồn kiểu kết quả của nó qua buttons, whenClosed(), và các hook.
  • Import gói này trên máy chủ không đụng tới DOM, nên an toàn với bản dựng SSR; hộp thoại chỉ được tạo khi bạn gọi API trong trình duyệt.
  • Bản build không chứa evalnew Function, điều này được kiểm tra trong CI, nên nó chạy được dưới Content Security Policy nghiêm ngặt. Chính site này cũng đang chạy dưới một CSP như vậy.
  • Mọi điểm vào đều có hạn mức gzip bắt buộc, kiểm tra ở từng pull request.