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-quill

v0.2.357-11

Published

> Trình soạn thảo văn bản giàu tính năng (Rich Text Editor) dựa trên Quill.js, tích hợp sẵn toolbar thích ứng, hỗ trợ mention, upload ảnh, validation và bảo mật XSS.

Readme

@libs-ui/components-inputs-quill

Trình soạn thảo văn bản giàu tính năng (Rich Text Editor) dựa trên Quill.js, tích hợp sẵn toolbar thích ứng, hỗ trợ mention, upload ảnh, validation và bảo mật XSS.

Giới thiệu

@libs-ui/components-inputs-quill là một Angular component bọc xung quanh thư viện Quill.js, cung cấp trải nghiệm soạn thảo văn bản chuyên nghiệp với toolbar tự động tính toán độ rộng và ẩn/hiện các nút thừa vào menu "Xem thêm". Component hỗ trợ đầy đủ các tính năng định dạng (Bold, Italic, Heading, Font, Color…), tích hợp mention (@user), upload hình ảnh, link, emoji và validation nội dung. Mọi nội dung đầu ra đều được lọc qua bộ lọc XSS tích hợp để đảm bảo an toàn.

Tính năng

  • ✅ Toolbar đầy đủ: Undo/Redo, Font family, Font size, Color, Background, Bold, Italic, Underline, Strike-through, Align, Indent, List, Blockquote, Link, Emoji, Image
  • ✅ Toolbar thích ứng: tự động tính toán độ rộng container và ẩn nút thừa vào "Xem thêm"
  • ✅ 3 chế độ toolbar: default (đầy đủ), basic (cơ bản), custom (tự cấu hình)
  • ✅ Hỗ trợ Mention (@user) thông qua dataConfigMention
  • ✅ Upload hình ảnh từ file hoặc qua clipboard paste
  • ✅ Validation: bắt buộc nhập, độ dài tối thiểu/tối đa
  • ✅ Bảo mật XSS: tất cả nội dung input/output đều qua xssFilter
  • ✅ Hỗ trợ chế độ readonly
  • ✅ Custom blots (đăng ký định dạng Quill tùy chỉnh)
  • ✅ Lấy API điều khiển qua outFunctionsControl (setContent, insertText, insertLink, insertImage, setFontSize, v.v.)
  • ✅ Hỗ trợ heightAuto — tự động thay đổi chiều cao theo nội dung
  • ✅ Two-way binding qua [(item)]
  • ✅ Standalone component, Angular Signals, OnPush

Khi nào sử dụng

  • Khi cần trình soạn thảo văn bản có định dạng trong form (mô tả, nội dung bài viết, ghi chú)
  • Khi cần hỗ trợ đề cập người dùng (@mention) trong editor
  • Khi cần cho phép người dùng upload và chèn hình ảnh vào nội dung
  • Khi xây dựng hệ thống CMS, chat, hoặc quản lý nội dung phức tạp
  • Khi cần kiểm soát validation nội dung nhập vào (bắt buộc, min/max length)

Cài đặt

npm install @libs-ui/components-inputs-quill

Các package phụ thuộc ([email protected], parchment, quill-delta...) đã khai báo peerDependencies trong package.json của lib → trình quản lý gói tự cài kèm, không cần cài thủ công.

⚠️ Nạp CSS của Quill (BẮT BUỘC — nếu thiếu, editor/toolbar sẽ vỡ giao diện)

Quill ship style riêng, KHÔNG đi kèm trong bundle của component. Consumer PHẢI tự nạp CSS theme. Khai báo trong angular.json / project.json:

// architect.build.options.styles  (Angular CLI)  HOẶC  targets.build.options.styles (Nx)
"styles": [
  "src/styles.scss",
  "./node_modules/quill/dist/quill.snow.css",
  "./node_modules/quill/dist/quill.bubble.css"
]

🔴 quill.snow.css là theme mặc định (BẮT BUỘC). Chỉ thêm quill.bubble.css nếu dùng theme bubble. Khai báo allowedCommonJsDependencies: ["quill"] trong cấu hình build để tắt cảnh báo CommonJS.

Import

import { LibsUiComponentsInputsQuillComponent } from '@libs-ui/components-inputs-quill';

// Các interface và types
import {
  IQuillFunctionControlEvent,
  IQuillUploadImageConfig,
  IQuillBlotRegister,
  IQuillLink,
  IQuillSelectionChange,
  IQuillTextChange,
  IQuillToolbarConfig,
  TYPE_MODE_BAR_CONFIG_OPTION,
} from '@libs-ui/components-inputs-quill';

// Các utility functions
import {
  getHTMLFromDeltaOfQuill,
  convertHtmlToDivBlocks,
  getDeltaOfQuillFromHTML,
  processPasteData,
  insertContentWithRange,
} from '@libs-ui/components-inputs-quill';

Ví dụ sử dụng

1. Cơ bản — Two-way binding

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

@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsQuillComponent],
  template: `
    <libs_ui-components-inputs-quill
      [(item)]="formData"
      [fieldNameBind]="'content'"
      [placeholder]="'Nhập nội dung tại đây...'"
    ></libs_ui-components-inputs-quill>
  `,
})
export class MyComponent {
  protected formData = signal<Record<string, string>>({ content: '' });
}

2. Với Validation và Label

// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsQuillComponent } from '@libs-ui/components-inputs-quill';
import { IValidRequired, IValidLength } from '@libs-ui/components-inputs-valid';

@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsQuillComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected formData = signal<Record<string, string>>({ description: '' });

  protected validRequired: IValidRequired = {
    isRequired: true,
    message: 'Vui lòng nhập mô tả',
  };

  protected validMinLength: IValidLength = {
    length: 10,
    message: 'Mô tả phải có ít nhất 10 ký tự',
  };

  protected validMaxLength: IValidLength = {
    length: 5000,
    message: 'Mô tả không được vượt quá 5000 ký tự',
  };
}
<!-- my.component.html -->
<libs_ui-components-inputs-quill
  [(item)]="formData"
  [fieldNameBind]="'description'"
  [label]="{ title: 'Mô tả chi tiết', isRequired: true }"
  [validRequired]="validRequired"
  [validMinLength]="validMinLength"
  [validMaxLength]="validMaxLength"
  [showErrorLabel]="true"
  [showErrorBorder]="true"
></libs_ui-components-inputs-quill>

3. Với Mention và Upload hình ảnh

// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsQuillComponent, IQuillUploadImageConfig } from '@libs-ui/components-inputs-quill';
import { IMentionConfig } from '@libs-ui/components-inputs-mention';

@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsQuillComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected formData = signal<Record<string, string>>({ content: '' });

  protected mentionConfig: IMentionConfig = {
    items: [
      { id: '1', name: 'Nguyễn Văn An', username: '[email protected]' },
      { id: '2', name: 'Trần Thị Bình', username: '[email protected]' },
      { id: '3', name: 'Lê Văn Cường', username: '[email protected]' },
    ],
    triggerChar: '@',
    labelKey: 'name',
    mentionFilter: (search, items) =>
      items?.filter((item) => item.name.toLowerCase().includes(search.toLowerCase())),
    mentionSelect: (item, triggerChar) => `${triggerChar || '@'}${item.name}`,
  };

  protected uploadImageConfig: IQuillUploadImageConfig = {
    modeCustom: false,
    showIcon: true,
    maxImageSize: 5 * 1024 * 1024, // 5MB
    onlyAcceptImageHttpsLink: false,
    functionUploadImage: async (files: File[]) => {
      // Gọi API upload file và trả về array URL
      const urls: string[] = [];
      for (const file of files) {
        const url = await this.uploadToServer(file);
        urls.push(url);
      }
      return urls;
    },
  };

  private async uploadToServer(file: File): Promise<string> {
    // Implement API call thực tế
    return 'https://example.com/images/uploaded.jpg';
  }
}
<!-- my.component.html -->
<libs_ui-components-inputs-quill
  [(item)]="formData"
  [fieldNameBind]="'content'"
  [dataConfigMention]="mentionConfig"
  [uploadImageConfig]="uploadImageConfig"
  [placeholder]="'Gõ @ để mention người dùng...'"
></libs_ui-components-inputs-quill>

4. Lấy API điều khiển qua outFunctionsControl

// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsQuillComponent, IQuillFunctionControlEvent } from '@libs-ui/components-inputs-quill';

@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsQuillComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected formData = signal<Record<string, string>>({ content: '' });

  private quillControl: IQuillFunctionControlEvent | undefined;

  protected handlerFunctionsControl(control: IQuillFunctionControlEvent): void {
    this.quillControl = control;
  }

  protected async handlerSetContent(event: Event): Promise<void> {
    event.stopPropagation();
    await this.quillControl?.setContent('<p>Nội dung mới được đặt từ bên ngoài</p>');
  }

  protected async handlerInsertText(event: Event): Promise<void> {
    event.stopPropagation();
    await this.quillControl?.insertText('Văn bản được chèn');
  }

  protected async handlerValidate(event: Event): Promise<void> {
    event.stopPropagation();
    const isValid = await this.quillControl?.checkIsValid();
    console.log('Is valid:', isValid);
  }

  protected async handlerInsertLink(event: Event): Promise<void> {
    event.stopPropagation();
    await this.quillControl?.insertLink('Xem chi tiết', 'https://example.com');
  }
}
<!-- my.component.html -->
<libs_ui-components-inputs-quill
  [(item)]="formData"
  [fieldNameBind]="'content'"
  (outFunctionsControl)="handlerFunctionsControl($event)"
></libs_ui-components-inputs-quill>

<button (click)="handlerSetContent($event)">Đặt nội dung</button>
<button (click)="handlerInsertText($event)">Chèn văn bản</button>
<button (click)="handlerInsertLink($event)">Chèn link</button>
<button (click)="handlerValidate($event)">Kiểm tra hợp lệ</button>

5. Chế độ toolbar tùy chỉnh

// component.ts
import { Component, signal } from '@angular/core';
import { LibsUiComponentsInputsQuillComponent, IQuillToolbarConfig, TYPE_MODE_BAR_CONFIG_OPTION } from '@libs-ui/components-inputs-quill';

@Component({
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [LibsUiComponentsInputsQuillComponent],
  templateUrl: './my.component.html',
})
export class MyComponent {
  protected formData = signal<Record<string, string>>({ content: '' });

  protected toolbarConfig: { type: TYPE_MODE_BAR_CONFIG_OPTION; config?: IQuillToolbarConfig[] } = {
    type: 'basic',
  };
}
<!-- my.component.html -->
<libs_ui-components-inputs-quill
  [(item)]="formData"
  [fieldNameBind]="'content'"
  [toolbarConfig]="toolbarConfig"
  [isShowToolBar]="true"
></libs_ui-components-inputs-quill>

6. Readonly mode

<libs_ui-components-inputs-quill
  [(item)]="formData"
  [fieldNameBind]="'content'"
  [readonly]="true"
  [isShowToolBar]="false"
></libs_ui-components-inputs-quill>

7. Auto focus và heightAuto

<libs_ui-components-inputs-quill
  [(item)]="formData"
  [fieldNameBind]="'content'"
  [autoFocus]="true"
  [focusBottom]="true"
  [heightAuto]="true"
></libs_ui-components-inputs-quill>

@Input()

| Input | Type | Default | Mô tả | Ví dụ | |---|---|---|---|---| | item | TYPE_OBJECT (model) | undefined | Object chứa dữ liệu two-way binding. Dùng [(item)] để binding hai chiều. | [(item)]="formData" | | fieldNameBind | string | 'value' | Tên trường trong item dùng để đọc/ghi nội dung HTML của editor. | [fieldNameBind]="'content'" | | isShowToolBar | boolean | true | Hiển thị hoặc ẩn thanh toolbar. | [isShowToolBar]="false" | | isToolbarPositionFixed | boolean | false | Đặt toolbar ở vị trí cố định (fixed) thay vì inline. | [isToolbarPositionFixed]="true" | | classIncludeToolbar | string | '' | CSS class bổ sung cho container toolbar. | [classIncludeToolbar]="'border-b'" | | stylesIncludeToolbar | TYPE_OBJECT | undefined | Style object bổ sung cho toolbar. | [stylesIncludeToolbar]="{ background: '#f5f5f5' }" | | toolbarConfig | { type: TYPE_MODE_BAR_CONFIG_OPTION; config?: IQuillToolbarConfig[] } | undefined | Cấu hình chế độ toolbar. type có thể là 'default', 'basic' hoặc 'custom'. | [toolbarConfig]="{ type: 'basic' }" | | placeholder | string | 'i18n_import_content' | Placeholder text khi editor trống. | [placeholder]="'Nhập nội dung...'" | | label | ILabel | undefined | Cấu hình label hiển thị bên trên editor. | [label]="{ title: 'Nội dung', isRequired: true }" | | autoUpdateValueWhenTextChange | boolean | true | Tự động cập nhật item[fieldNameBind] mỗi khi nội dung thay đổi. Đặt false để tối ưu hiệu năng và chỉ lấy giá trị khi cần (gọi refreshItemValue()). | [autoUpdateValueWhenTextChange]="false" | | readonly | boolean | undefined | Chế độ chỉ đọc, không cho phép chỉnh sửa nội dung. | [readonly]="true" | | showErrorLabel | boolean | true | Hiển thị thông báo lỗi validation dạng text bên dưới editor. | [showErrorLabel]="true" | | showErrorBorder | boolean | false | Hiển thị viền màu đỏ khi có lỗi validation. | [showErrorBorder]="true" | | onlyShowErrorBorderInContent | boolean | false | Chỉ hiển thị viền đỏ ở vùng nội dung editor, không áp dụng cho cả container. | [onlyShowErrorBorderInContent]="true" | | classInclude | string | undefined | CSS class bổ sung cho vùng nội dung .ql-editor. | [classInclude]="'min-h-[200px]'" | | classIncludeTemplate | string | undefined | CSS class bổ sung cho container tổng thể của component. | [classIncludeTemplate]="'border rounded'" | | handlersExpand | Array<{ title: string; action: () => void }> | undefined | Mảng các handler mở rộng cho toolbar. title là tên nút toolbar, action là hàm xử lý. | [handlersExpand]="[{ title: 'link', action: myLinkHandler }]" | | validRequired | IValidRequired | undefined | Cấu hình validation bắt buộc nhập. | [validRequired]="{ isRequired: true, message: 'Bắt buộc nhập' }" | | validMinLength | IValidLength | undefined | Cấu hình validation độ dài tối thiểu. | [validMinLength]="{ length: 10, message: 'Tối thiểu 10 ký tự' }" | | validMaxLength | IValidLength | undefined | Cấu hình validation độ dài tối đa. | [validMaxLength]="{ length: 5000, message: 'Tối đa 5000 ký tự' }" | | zIndex | number | 1250 | Z-index cho các overlay/popup (link editor, upload image). | [zIndex]="2000" | | dataConfigMention | IMentionConfig | undefined | Cấu hình tính năng mention (@user). | [dataConfigMention]="mentionConfig" | | blotsRegister | Array<IQuillBlotRegister> | undefined | Mảng các custom Quill blot để đăng ký thêm vào editor. | [blotsRegister]="customBlots" | | autoFocus | boolean | undefined | Tự động focus vào editor khi component khởi tạo. | [autoFocus]="true" | | focusBottom | boolean | undefined | Khi focus, đặt con trỏ ở cuối nội dung. | [focusBottom]="true" | | blockUndoRedoKeyboard | boolean | undefined | Chặn phím tắt Ctrl+Z (Undo) và Ctrl+Shift+Z / Ctrl+Y (Redo). | [blockUndoRedoKeyboard]="true" | | templateToolBarPersonalize | TemplateRef | undefined | Template tùy chỉnh hiển thị trong toolbar (slot "personalize"). | [templateToolBarPersonalize]="myToolbarTpl" | | template | TemplateRef | undefined | Template tùy chỉnh hiển thị trong container của component. | [template]="myCustomTpl" | | uploadImageConfig | IQuillUploadImageConfig | uploadImageConfigDefault() | Cấu hình tính năng upload hình ảnh. | [uploadImageConfig]="uploadConfig" | | heightAuto | boolean | undefined | Cho phép editor tự động tăng chiều cao theo nội dung (không cố định scroll). | [heightAuto]="true" | | elementScrollHeightAuto | HTMLElement | undefined | Element container dùng để scroll khi heightAuto được bật. | [elementScrollHeightAuto]="scrollContainer" | | ignoreShowPopupEditLink | boolean | undefined | Khi true, không hiện popup mặc định khi edit link — thay vào đó emit outShowPopupEditLink. | [ignoreShowPopupEditLink]="true" | | ignoreCommunicateMicroEventPopup | boolean | undefined | Bỏ qua việc giao tiếp sự kiện popup qua cơ chế micro-frontend. | [ignoreCommunicateMicroEventPopup]="true" |

@Output()

| Output | Type | Mô tả | Handler TS | Binding HTML | |---|---|---|---|---| | (outChange) | string | Emits chuỗi HTML khi nội dung editor thay đổi (sau xssFilter). | handlerChange(html: string): void { this.content.set(html); } | (outChange)="handlerChange($event)" | | (outFocus) | void | Emits khi người dùng focus vào editor. | handlerFocus(): void { this.isFocused.set(true); } | (outFocus)="handlerFocus()" | | (outBlur) | void | Emits khi editor mất focus. | handlerBlur(): void { this.isFocused.set(false); } | (outBlur)="handlerBlur()" | | (outFunctionsControl) | IQuillFunctionControlEvent | Emits object chứa các hàm điều khiển editor từ bên ngoài. Emits ngay khi component khởi tạo. | handlerFunctionsControl(ctrl: IQuillFunctionControlEvent): void { this.quillCtrl = ctrl; } | (outFunctionsControl)="handlerFunctionsControl($event)" | | (outSelectionChange) | IQuillSelectionChange | Emits khi vị trí con trỏ hoặc vùng chọn văn bản thay đổi. | handlerSelectionChange(e: IQuillSelectionChange): void { console.log(e.range); } | (outSelectionChange)="handlerSelectionChange($event)" | | (outTextChange) | IQuillTextChange | Emits khi nội dung thay đổi, kèm delta diff chi tiết từ Quill. | handlerTextChange(e: IQuillTextChange): void { console.log(e.delta); } | (outTextChange)="handlerTextChange($event)" | | (outMessageError) | string | Emits thông báo lỗi validation. Emits chuỗi rỗng khi hợp lệ. | handlerMessageError(msg: string): void { this.errorMsg.set(msg); } | (outMessageError)="handlerMessageError($event)" | | (outContextMenu) | MouseEvent | Emits khi người dùng click chuột phải vào vùng editor. | handlerContextMenu(e: MouseEvent): void { e.stopPropagation(); this.showContextMenu(e); } | (outContextMenu)="handlerContextMenu($event)" | | (outShowPopupEditLink) | { dataLink: IQuillLink; callback: (linkEdit: { title: string; link: string }) => Promise<void> } | Emits khi cần hiện popup edit link tùy chỉnh. Chỉ emit khi ignoreShowPopupEditLinktrue. | handlerShowPopupEditLink(e: { dataLink: IQuillLink; callback: Function }): void { this.openCustomLinkDialog(e); } | (outShowPopupEditLink)="handlerShowPopupEditLink($event)" |

FunctionsControl API (qua outFunctionsControl)

Khi nhận được object IQuillFunctionControlEvent từ outFunctionsControl, bạn có thể gọi các hàm sau:

| Method | Signature | Mô tả | |---|---|---| | checkIsValid | () => Promise<boolean> | Kiểm tra validation (validRequired, validMinLength, validMaxLength). Trả về true nếu hợp lệ. | | refreshItemValue | () => void | Cập nhật thủ công item[fieldNameBind] từ nội dung hiện tại của editor. Dùng khi autoUpdateValueWhenTextChange = false. | | setContent | (content: string) => Promise<void> | Đặt nội dung HTML cho editor (qua xssFilter). | | insertText | (value: string, index?: number, focusLast?: boolean) => Promise<void> | Chèn văn bản vào vị trí con trỏ hiện tại hoặc tại index. | | insertLink | (value: string, url: string, index?: number) => Promise<void> | Chèn hyperlink vào editor. | | insertImage | (content: string, index?: number) => Promise<void> | Chèn hình ảnh (URL) vào editor. | | setFontSize | (size: number) => Promise<void> | Đặt font size (px) tại vị trí con trỏ. | | setColor | (color: string) => Promise<void> | Đặt màu chữ tại vị trí con trỏ (CSS color string). | | setBackground | (color: string) => Promise<void> | Đặt màu nền tại vị trí con trỏ. | | quill | () => Quill | Trả về instance Quill.js để truy cập trực tiếp các API của Quill. | | scrollToSelectionWithElementScrollHeightAuto | (index?: number) => void | Scroll đến vị trí con trỏ khi dùng heightAuto. | | insertEmbed | (range: { index: number; indexSelect: number; length?: number }, type: string, data: unknown, sources?: Sources) => Promise<void> | Chèn một embed blot tùy chỉnh. | | reCalculatorToolbar | () => Promise<void> | Tính toán lại độ rộng toolbar (dùng khi container thay đổi kích thước). |

Types & Interfaces

import {
  IQuillToolbarConfig,
  IQuillUploadImageConfig,
  IQuillFunctionControlEvent,
  IQuillLink,
  IQuillBlotRegister,
  IQuillSelectionChange,
  IQuillTextChange,
  TYPE_MODE_BAR_CONFIG_OPTION,
} from '@libs-ui/components-inputs-quill';

// Chế độ toolbar
export type TYPE_MODE_BAR_CONFIG_OPTION = 'default' | 'basic' | 'custom';

// Cấu hình một item trong toolbar
export interface IQuillToolbarConfig {
  type: string;                              // Loại nút (undo, redo, bold, italic, color, ...)
  mode?: Array<TYPE_MODE_BAR_CONFIG_OPTION>; // Toolbar mode nào hiển thị nút này
  width: number;                             // Độ rộng nút (px) dùng để tính layout toolbar
  display?: boolean;                         // Hiện/ẩn nút (quản lý bởi toolbar calculator)
  classInclude?: string;                     // CSS class bổ sung cho nút
}

// Cấu hình upload hình ảnh
export interface IQuillUploadImageConfig {
  modeCustom: boolean;                                                   // true: hiển thị dialog tùy chỉnh; false: mở file picker mặc định
  showIcon?: boolean;                                                    // Hiện icon upload trong toolbar
  zIndex?: number;                                                       // Z-index của dialog upload
  maxImageSize?: number;                                                 // Kích thước file tối đa (bytes), mặc định 5MB
  label?: ILabel;                                                        // Label cho dialog upload
  onlyAcceptImageHttpsLink?: boolean;                                    // Chỉ chấp nhận link https (không chấp nhận base64)
  functionUploadImage?: (files: File[]) => Promise<Array<string | ArrayBuffer>>; // Hàm gọi API upload, trả về array URL
}

// Interface điều khiển editor từ bên ngoài (nhận qua outFunctionsControl)
export interface IQuillFunctionControlEvent {
  checkIsValid: () => Promise<boolean>;
  refreshItemValue: () => void;
  setContent: (content: string) => Promise<void>;
  insertText: (value: string, index?: number, focusLast?: boolean) => Promise<void>;
  insertLink: (value: string, url: string, index?: number) => Promise<void>;
  insertImage: (content: string, index?: number) => Promise<void>;
  setFontSize: (size: number) => Promise<void>;
  setColor: (color: string) => Promise<void>;
  setBackground: (color: string) => Promise<void>;
  quill: () => Quill;
  scrollToSelectionWithElementScrollHeightAuto: (index?: number) => void;
  insertEmbed: (range: { index: number; indexSelect: number; length?: number }, type: string, data: unknown, sources?: Sources) => Promise<void>;
  reCalculatorToolbar: () => Promise<void>;
}

// Thông tin một link trong editor
export interface IQuillLink {
  title: string;
  url: string;
  range: {
    index: number;
    length: number;
  };
}

// Đăng ký custom blot
export interface IQuillBlotRegister {
  component: any;        // Class blot kế thừa từ Quill Blot
  className: string;     // CSS class name của blot trong HTML output
  style: string;         // CSS style inline áp dụng cho blot
  ignoreDelete?: boolean; // Khi true, ngăn không cho xóa blot bằng phím Backspace/Delete
}

// Dữ liệu sự kiện khi vùng chọn thay đổi
export interface IQuillSelectionChange {
  quill: Quill;
  range: RangeStatic;
  oldRange: RangeStatic;
  source: Sources;
}

// Dữ liệu sự kiện khi nội dung thay đổi
export interface IQuillTextChange {
  quill: Quill;
  delta: Delta;
}

Utility Functions

import {
  getHTMLFromDeltaOfQuill,
  convertHtmlToDivBlocks,
  getDeltaOfQuillFromHTML,
  processPasteData,
  insertContentWithRange,
} from '@libs-ui/components-inputs-quill';

// Chuyển đổi Quill Delta object sang chuỗi HTML
const html = getHTMLFromDeltaOfQuill(delta);

// Chuyển đổi HTML sang cấu trúc div blocks
const blocks = convertHtmlToDivBlocks(html);

// Chuyển đổi HTML sang Quill Delta object
const delta = getDeltaOfQuillFromHTML(html);

// Xử lý dữ liệu khi paste vào editor
const processedData = processPasteData(pasteEvent);

// Chèn nội dung vào editor tại vị trí range chỉ định
insertContentWithRange(quillInstance, content, range);

Lưu ý quan trọng

⚠️ autoUpdateValueWhenTextChange = false để tối ưu hiệu năng: Khi editor có nội dung lớn hoặc nhiều hình ảnh, việc update item mỗi lần gõ phím có thể ảnh hưởng hiệu năng. Đặt [autoUpdateValueWhenTextChange]="false" và chủ động gọi functionsControl.refreshItemValue() khi cần lấy giá trị (ví dụ: trước khi submit form).

⚠️ Toolbar tự động ẩn nút thừa: Toolbar sử dụng cơ chế tính toán độ rộng để ẩn các nút không vừa vào container vào menu "Xem thêm". Nếu cần trigger lại sau khi container thay đổi kích thước, gọi functionsControl.reCalculatorToolbar().

⚠️ XSS Security: Tất cả nội dung đặt vào editor (qua setContent) đều được xử lý qua xssFilter trước khi render. Nội dung đầu ra (outChange) cũng đã được lọc. Không cần sanitize thêm ở phía consumer.

⚠️ Custom blots và ignoreDelete: Khi đăng ký custom blot với ignoreDelete: true, phím Backspace/Delete sẽ không xóa blot đó. Dùng khi blot là nội dung không thể xóa trực tiếp (ví dụ: mention node, reflection node).

⚠️ Upload hình ảnh: functionUploadImage trong IQuillUploadImageConfig phải trả về Promise<Array<string | ArrayBuffer>>. Khi onlyAcceptImageHttpsLink = true, các link không phải https:// hoặc base64 sẽ bị bỏ qua. Kích thước file vượt quá maxImageSize sẽ hiện thông báo cảnh báo.

⚠️ Peer dependencies: Component phụ thuộc vào quill@^1.x và các thư viện nội bộ @libs-ui. Đảm bảo đã cài đặt đầy đủ peer dependencies trước khi sử dụng.

⚠️ outFunctionsControl emits ngay khi khởi tạo: Output outFunctionsControl sẽ emit object IQuillFunctionControlEvent ngay trong ngOnInit. Hàm setContent và các API khác chỉ hoạt động sau khi ngAfterViewInit hoàn tất (có delay ~20ms). Nếu cần gọi setContent ngay sau khi nhận control, có thể dùng setTimeout ngắn.