@edmicro/olkit 1.1.3 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -544,6 +544,21 @@ declare class OlBadge {
544
544
  * khác trong thư viện — badge từng là chỗ duy nhất gọi trục này là `theme`.
545
545
  */
546
546
  readonly color: _angular_core.InputSignal<BadgeColor>;
547
+ /**
548
+ * Có vẽ viền không. Tắt thì giữ nguyên nền và màu chữ của `color`.
549
+ *
550
+ * Viền là thuộc tính ĐỘC LẬP với màu, nên tách thành input riêng thay vì
551
+ * thêm giá trị vào `BadgeColor`: làm thành màu thì muốn "brand không viền"
552
+ * và "danger không viền" phải nhân đôi cả bảng 12 màu thành 24.
553
+ *
554
+ * Khác `color="ghost"`: ghost bỏ CẢ nền lẫn viền và ép chữ về `text-heading`
555
+ * — nó là một màu. `[bordered]="false"` chỉ bỏ viền.
556
+ *
557
+ * Class `border` VẪN giữ (đổi thành `border-transparent`) chứ không bỏ hẳn:
558
+ * badge tính chiều cao theo hộp viền, bỏ đi là badge không viền thấp hơn
559
+ * badge thường đúng 2px khi đặt cạnh nhau. Cùng lý do với `ghost`.
560
+ */
561
+ readonly bordered: _angular_core.InputSignal<boolean>;
547
562
  /**
548
563
  * @deprecated Dùng `color`.
549
564
  *
@@ -580,7 +595,7 @@ declare class OlBadge {
580
595
  readonly dotClasses: _angular_core.Signal<"bg-fg-brand-strong" | "bg-fg-danger-strong" | "bg-fg-warning" | "bg-success-strong" | "bg-heading">;
581
596
  onDismiss(event: MouseEvent): void;
582
597
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlBadge, never>;
583
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBadge, "ol-badge", never, { "color": { "alias": "color"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; "leftIcon": { "alias": "leftIcon"; "required": false; "isSignal": true; }; "rightIcon": { "alias": "rightIcon"; "required": false; "isSignal": true; }; "avatar": { "alias": "avatar"; "required": false; "isSignal": true; }; "avatarText": { "alias": "avatarText"; "required": false; "isSignal": true; }; "secondaryText": { "alias": "secondaryText"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "removable": { "alias": "removable"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; }, { "dismiss": "dismiss"; }, never, ["*"], true, never>;
598
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBadge, "ol-badge", never, { "color": { "alias": "color"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "shape": { "alias": "shape"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; "leftIcon": { "alias": "leftIcon"; "required": false; "isSignal": true; }; "rightIcon": { "alias": "rightIcon"; "required": false; "isSignal": true; }; "avatar": { "alias": "avatar"; "required": false; "isSignal": true; }; "avatarText": { "alias": "avatarText"; "required": false; "isSignal": true; }; "secondaryText": { "alias": "secondaryText"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "removable": { "alias": "removable"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; }, { "dismiss": "dismiss"; }, never, ["*"], true, never>;
584
599
  }
585
600
 
586
601
  type BannerVariant = 'brand' | 'gradient' | 'warning' | 'dark' | 'subtle';
@@ -683,6 +698,30 @@ declare class OlBreadcrumbItem {
683
698
  * còn `disabled` là chặng có tồn tại nhưng không đi tới được.
684
699
  */
685
700
  readonly disabled: _angular_core.InputSignal<boolean>;
701
+ /**
702
+ * Cho phép bấm để chuyển trang hay không. Mặc định **bật** — giữ hành vi cũ.
703
+ *
704
+ * Tắt khi muốn chặng VẪN hiện như liên kết (chữ xanh khi rê chuột, Tab tới
705
+ * được, trình đọc màn hình vẫn đọc là liên kết) nhưng KHÔNG tự điều hướng —
706
+ * ví dụ cần hỏi xác nhận trước khi rời trang đang sửa dở, hoặc nơi dùng tự
707
+ * xử lý điều hướng qua `(itemClick)`.
708
+ *
709
+ * Khác `disabled`: `disabled` bỏ HẲN thẻ `<a>`, chặng thành chữ xám không
710
+ * bấm được và không Tab tới được. `[clickable]="false"` giữ nguyên vẻ ngoài
711
+ * và trợ năng của liên kết, chỉ chặn hành vi điều hướng.
712
+ *
713
+ * Chặn bằng `preventDefault()` trong `(click)` chứ không bỏ `[routerLink]`:
714
+ * bỏ binding thì `<a>` mất `href`, trình đọc màn hình không còn coi nó là
715
+ * liên kết, và người dùng mất luôn khả năng xem đích đến ở thanh trạng thái.
716
+ */
717
+ readonly clickable: _angular_core.InputSignal<boolean>;
718
+ /**
719
+ * Phát khi người dùng bấm vào chặng — kể cả khi `[clickable]="false"`.
720
+ *
721
+ * Nhờ vậy nơi dùng chặn điều hướng mặc định rồi tự quyết: hỏi xác nhận, lưu
722
+ * nháp, hay điều hướng bằng đường khác.
723
+ */
724
+ readonly itemClick: _angular_core.OutputEmitterRef<MouseEvent>;
686
725
  private readonly parentBreadcrumb;
687
726
  readonly isCurrent: _angular_core.Signal<boolean>;
688
727
  readonly effectiveSeparator: _angular_core.Signal<BreadcrumbSeparator>;
@@ -692,8 +731,23 @@ declare class OlBreadcrumbItem {
692
731
  * người dùng tưởng nó hỏng.
693
732
  */
694
733
  readonly isLink: _angular_core.Signal<boolean>;
734
+ /**
735
+ * `href` tĩnh dựng từ `routerLink()`, dùng cho nhánh `clickable=false`.
736
+ *
737
+ * Giữ được thuộc tính `href` thật trên thẻ `<a>` nên trợ năng không đổi;
738
+ * `preventDefault()` trong `onClick` lo phần chặn đi.
739
+ */
740
+ protected readonly staticHref: _angular_core.Signal<string>;
741
+ /**
742
+ * Chặn điều hướng khi `[clickable]="false"`, nhưng VẪN phát `itemClick` để
743
+ * nơi dùng tự xử lý.
744
+ *
745
+ * `preventDefault()` chặn cả `routerLink` lẫn `href`: RouterLink bỏ qua sự
746
+ * kiện đã bị chặn, còn trình duyệt không đi theo `href` nữa.
747
+ */
748
+ protected onClick(event: MouseEvent): void;
695
749
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlBreadcrumbItem, never>;
696
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBreadcrumbItem, "ol-breadcrumb-item", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "current": { "alias": "current"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "status": { "alias": "status"; "required": false; "isSignal": true; }; "showSeparator": { "alias": "showSeparator"; "required": false; "isSignal": true; }; "separator": { "alias": "separator"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
750
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBreadcrumbItem, "ol-breadcrumb-item", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "current": { "alias": "current"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "status": { "alias": "status"; "required": false; "isSignal": true; }; "showSeparator": { "alias": "showSeparator"; "required": false; "isSignal": true; }; "separator": { "alias": "separator"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "clickable": { "alias": "clickable"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; }, never, ["*"], true, never>;
697
751
  }
698
752
 
699
753
  type AppDevice = 'appstore' | 'googleplay' | 'google-play' | 'apple' | 'google';
@@ -1073,8 +1127,35 @@ declare class OlDatepicker implements ControlValueAccessor {
1073
1127
  onInputChange(val: string): void;
1074
1128
  clearInput(event: MouseEvent): void;
1075
1129
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
1076
- private donDepPanel;
1077
- onDocumentClick(event: MouseEvent): void;
1130
+ private detachPanelNow;
1131
+ /**
1132
+ * Phần tử được bấm có thuộc datepicker này không.
1133
+ *
1134
+ * Ngăn xếp `OlOverlayContainer` gọi hàm này để biết một cú click là "trong"
1135
+ * hay "ngoài". Trước đây mỗi component tự nghe `document:click`, nên một cú
1136
+ * bấm ra ngoài đóng luôn MỌI lớp — mở datepicker trong modal rồi bấm nền là
1137
+ * mất cả hộp thoại đang điền dở. Đo /modal ngày 2026-09-24 xác nhận.
1138
+ *
1139
+ * ## Phải kiểm CẢ PANEL, không chỉ host
1140
+ *
1141
+ * Với `appendTo="body"` panel đã bị `overlayContainer.attach()` chuyển ra
1142
+ * `.ol-overlay-container` — không còn là con của host, nên click vào chính
1143
+ * popup bị coi là click ra ngoài. Đo /modal: bật `appendTo` rồi bấm một
1144
+ * ngày → popup ĐÓNG NGAY, range picker cần hai ngày nên người dùng không
1145
+ * chọn nổi ngày kết thúc.
1146
+ *
1147
+ * Tắt `appendTo` thì popup ở lại đúng — lỗi chỉ xuất hiện khi treo ra ngoài,
1148
+ * tức đúng ca sinh ra để dùng trong modal.
1149
+ */
1150
+ private ownsTarget;
1151
+ /**
1152
+ * Đóng popup khi component bị huỷ.
1153
+ *
1154
+ * Bắt buộc với `appendTo="body"`: panel treo vào `<body>` chứ không phải cây
1155
+ * DOM của component, nên Angular huỷ component mà panel VẪN Ở LẠI. Chuyển
1156
+ * trang khi đang mở lịch là để lại một popup mồ côi không ai đóng được.
1157
+ */
1158
+ ngOnDestroy(): void;
1078
1159
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDatepicker, never>;
1079
1160
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDatepicker, "ol-datepicker", never, { "id": { "alias": "id"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "minDate": { "alias": "minDate"; "required": false; "isSignal": true; }; "maxDate": { "alias": "maxDate"; "required": false; "isSignal": true; }; "daysDisabled": { "alias": "daysDisabled"; "required": false; "isSignal": true; }; "datesDisabled": { "alias": "datesDisabled"; "required": false; "isSignal": true; }; "datesEnabled": { "alias": "datesEnabled"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "showFooter": { "alias": "showFooter"; "required": false; "isSignal": true; }; "inline": { "alias": "inline"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "dateChange": "dateChange"; "timestampChange": "timestampChange"; "isoChange": "isoChange"; "formattedChange": "formattedChange"; }, never, never, true, never>;
1080
1161
  }
@@ -1192,8 +1273,26 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1192
1273
  onEndInputChange(val: string): void;
1193
1274
  clearRange(event: MouseEvent): void;
1194
1275
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
1195
- private donDepPanel;
1196
- onDocumentClick(event: MouseEvent): void;
1276
+ private detachPanelNow;
1277
+ /**
1278
+ * Phần tử được bấm có thuộc bộ chọn khoảng này không.
1279
+ *
1280
+ * Xem chú thích cùng tên ở `ol-datepicker` — hai component dùng chung cơ
1281
+ * chế. Range picker còn nhạy hơn: nó cần HAI cú bấm (ngày đầu, ngày cuối),
1282
+ * nên popup đóng sớm là người dùng không chọn nổi khoảng.
1283
+ *
1284
+ * Phải kiểm cả panel vì với `appendTo="body"` nó đã bị chuyển ra
1285
+ * `.ol-overlay-container`, không còn là con của host.
1286
+ */
1287
+ private ownsTarget;
1288
+ /**
1289
+ * Đóng popup khi component bị huỷ.
1290
+ *
1291
+ * Bắt buộc với `appendTo="body"`: panel treo vào `<body>` nên Angular huỷ
1292
+ * component mà panel VẪN Ở LẠI. Chuyển trang khi đang mở lịch là để lại một
1293
+ * popup mồ côi không ai đóng được.
1294
+ */
1295
+ ngOnDestroy(): void;
1197
1296
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDateRangePicker, never>;
1198
1297
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDateRangePicker, "ol-date-range-picker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "startPlaceholder": { "alias": "startPlaceholder"; "required": false; "isSignal": true; }; "endPlaceholder": { "alias": "endPlaceholder"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "fromLabel": { "alias": "fromLabel"; "required": false; "isSignal": true; }; "toLabel": { "alias": "toLabel"; "required": false; "isSignal": true; }; "showInfoIcon": { "alias": "showInfoIcon"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "dualMonth": { "alias": "dualMonth"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "minDate": { "alias": "minDate"; "required": false; "isSignal": true; }; "maxDate": { "alias": "maxDate"; "required": false; "isSignal": true; }; "daysDisabled": { "alias": "daysDisabled"; "required": false; "isSignal": true; }; "datesDisabled": { "alias": "datesDisabled"; "required": false; "isSignal": true; }; "datesEnabled": { "alias": "datesEnabled"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "showFooter": { "alias": "showFooter"; "required": false; "isSignal": true; }; "presets": { "alias": "presets"; "required": false; "isSignal": true; }; "inline": { "alias": "inline"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "rangeChange": "rangeChange"; }, never, never, true, never>;
1199
1298
  }
@@ -1329,9 +1428,13 @@ declare class OlDatepickerPopup {
1329
1428
  /**
1330
1429
  * Trần chiều cao + cuộn CHỈ cho popup nổi.
1331
1430
  *
1332
- * Dùng `[class]` trả chuỗi chứ KHÔNG `[class.max-h-…]` với giá trị tuỳ ý: tên class
1333
- * chứa dấu chấm và ngoặc vuông nên Angular cắt hỏng tên khi dùng class
1431
+ * Dùng `[class]` trả chuỗi chứ KHÔNG `[class.max-h-…]` với giá trị tuỳ ý: tên
1432
+ * class chứa dấu chấm và ngoặc vuông nên Angular cắt hỏng tên khi dùng class
1334
1433
  * binding từng cái (`[class.h-1.5]` ra `h-1`).
1434
+ *
1435
+ * Dấu `…` ở trên là cố ý, không phải ba chấm ASCII: Tailwind quét file thô,
1436
+ * không phân biệt chú thích với mã, nên tên class đầy đủ viết trong chú thích
1437
+ * cũng bị sinh thành luật CSS và làm build cảnh báo `Unexpected token Delim`.
1335
1438
  */
1336
1439
  readonly wrapperClasses: _angular_core.Signal<"" | "max-h-[var(--ol-dp-max-height,85svh)] overflow-y-auto overscroll-contain">;
1337
1440
  /**
@@ -1630,6 +1733,112 @@ declare class OlFooter {
1630
1733
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFooter, "ol-footer", never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1631
1734
  }
1632
1735
 
1736
+ /**
1737
+ * Bảng chuẩn kích thước và trạng thái cho MỌI ô nhập của thư viện.
1738
+ *
1739
+ * ## Vì sao cần
1740
+ *
1741
+ * Chín component họ form từng tự khai kích thước riêng, và chúng trôi khỏi
1742
+ * nhau. Đo ngày 2026-09-23 trên chín component:
1743
+ *
1744
+ * thiếu cỡ `xl` 3 component (select · autocomplete · input-counter)
1745
+ * nền sai 3 component (autocomplete · timepicker · datepicker)
1746
+ * viền sai 5 component
1747
+ * đệm ngang lệch 2px ở cả ba cỡ đầu (datepicker so với input)
1748
+ * cỡ chữ `sm` lệch 2px (14px vs 12px)
1749
+ *
1750
+ * Nền và viền lệch **chỉ lộ ở chế độ tối**: sáng thì `--bg-secondary` và
1751
+ * `--bg-secondary-medium` cùng ra `#f9fafb`, tối thì `gray-950` vs `gray-800`.
1752
+ * Nên lỗi sống rất lâu — người dựng nhìn chế độ sáng, thấy giống hệt nhau.
1753
+ *
1754
+ * Đọc từ đây thì thêm ô nhập mới cũng tự khớp, và đổi thang chỉ sửa một chỗ.
1755
+ *
1756
+ * ## Không gộp chiều cao với đệm ngang
1757
+ *
1758
+ * Hai thứ tách riêng vì component có icon cần `ps-*` thay `px-*`. Gộp lại thì
1759
+ * mỗi component phải cắt chuỗi ra để bỏ phần đệm — dễ sai hơn là ghép vào.
1760
+ */
1761
+ /** Bốn cỡ của họ form. Trùng `InputSize`, `SelectSize`, `DatepickerSize`. */
1762
+ type FieldSize = 'sm' | 'base' | 'lg' | 'xl';
1763
+ /** Ba trạng thái hiển thị. */
1764
+ type FieldState = 'default' | 'error' | 'success';
1765
+ /**
1766
+ * Chiều cao theo cỡ — **cố định bằng `h-*`**, không suy từ `py-*`.
1767
+ *
1768
+ * Suy từ đệm dọc thì chiều cao phụ thuộc `line-height` của cỡ chữ và không
1769
+ * bao giờ khớp thang chung. Đo trước khi chuẩn hoá: datepicker ra 34/42/54
1770
+ * trong khi input ra 36/40/48 — đặt cạnh nhau là so le ngay, nặng nhất ở `lg`.
1771
+ *
1772
+ * `xl` (52px) là cỡ mới nhất; ba component còn thiếu nó sẽ so le 12px khi đặt
1773
+ * cạnh một `ol-input size="xl"`.
1774
+ */
1775
+ declare const FIELD_HEIGHT: Record<FieldSize, string>;
1776
+ /**
1777
+ * Chiều cao TỐI THIỂU — cho component cao theo nội dung.
1778
+ *
1779
+ * `ol-select` nhiều lựa chọn, `ol-tag-input` nhiều thẻ: chúng phải giãn ra khi
1780
+ * nội dung xuống dòng, nên `h-*` cố định sẽ cắt mất. Giá trị bằng đúng
1781
+ * `FIELD_HEIGHT` để một dòng vẫn cao bằng các ô khác.
1782
+ */
1783
+ declare const FIELD_MIN_HEIGHT: Record<FieldSize, string>;
1784
+ /**
1785
+ * Đệm ngang theo Figma `ol-input`: sm 10px · base 12px · lg 14px · xl 16px.
1786
+ *
1787
+ * KHÔNG dùng `[class.px-2.5]` trên template: Angular cắt phần sau dấu chấm
1788
+ * nên nó render ra `px-2`, lệch 2px so với thiết kế. Phải gom vào một chuỗi.
1789
+ */
1790
+ declare const FIELD_PADDING_X: Record<FieldSize, string>;
1791
+ /**
1792
+ * Cỡ chữ theo cỡ ô.
1793
+ *
1794
+ * `sm` là **14px** (`text-sm`), không phải 12px. Datepicker từng dùng
1795
+ * `text-xs` nên chữ trong ô nhỏ hơn hẳn các ô cùng hàng.
1796
+ */
1797
+ declare const FIELD_TEXT: Record<FieldSize, string>;
1798
+ /**
1799
+ * Nền · viền · vòng focus theo trạng thái.
1800
+ *
1801
+ * Dùng `-medium` cho cả nền lẫn viền ở trạng thái thường. Hai tên gần giống
1802
+ * nhau (`bg-neutral-secondary` vs `bg-neutral-secondary-medium`) nhưng **khác
1803
+ * hẳn ở chế độ tối**, nên nhầm một chữ là ô đó lệch màu mà chế độ sáng không
1804
+ * lộ ra.
1805
+ *
1806
+ * Dùng `focus-within:` chứ không `focus:`: viền nằm trên thẻ bọc, còn focus
1807
+ * rơi vào `<input>` bên trong. `focus:` trên thẻ bọc không bao giờ khớp.
1808
+ */
1809
+ declare const FIELD_STATE: Record<FieldState, string>;
1810
+ /** Nền và con trỏ khi ô bị khoá. Áp đè lên `FIELD_STATE`. */
1811
+ declare const FIELD_DISABLED = "bg-disabled opacity-60 cursor-not-allowed";
1812
+ /** Bo góc — 12px cho mọi cỡ, theo Figma `bieu-mau.md`. */
1813
+ declare const FIELD_RADIUS = "rounded-md";
1814
+ /**
1815
+ * Lớp của nhãn phía trên ô.
1816
+ *
1817
+ * `text-heading` chứ không `text-body`: nhãn là thứ người dùng đọc đầu tiên để
1818
+ * biết ô này hỏi gì, nó phải đậm hơn chữ phụ.
1819
+ */
1820
+ declare const FIELD_LABEL = "inline-flex items-center gap-1 text-sm font-medium text-heading";
1821
+ /** Khoảng cách nhãn → ô. */
1822
+ declare const FIELD_LABEL_GAP = "mb-1.5";
1823
+ /** Dấu sao bắt buộc. */
1824
+ declare const FIELD_ASTERISK = "text-fg-danger font-bold";
1825
+ /**
1826
+ * Chữ trợ giúp dưới ô, đổi màu theo trạng thái.
1827
+ *
1828
+ * Trạng thái lỗi mà chữ vẫn màu phụ thì người dùng không biết dòng đó là lời
1829
+ * giải thích lỗi hay chú thích thường.
1830
+ */
1831
+ declare const FIELD_HELPER: Record<FieldState, string>;
1832
+ /** Khoảng cách ô → chữ trợ giúp. */
1833
+ declare const FIELD_HELPER_GAP = "mt-1.5";
1834
+ /**
1835
+ * Gộp chiều cao · đệm ngang · cỡ chữ · bo góc cho một cỡ.
1836
+ *
1837
+ * Tiện cho component không có icon. Component có icon tự ghép từ các bảng
1838
+ * riêng để thay `px-*` bằng `ps-*`.
1839
+ */
1840
+ declare function fieldSizeClasses(size: FieldSize): string;
1841
+
1633
1842
  /**
1634
1843
  * Nhãn cho ô nhập — khớp Figma `ol-label` (#1149:94727), trục
1635
1844
  * `Type=[Default | Required & icon | Two texts]`.
@@ -2030,7 +2239,7 @@ declare class OlInput implements ControlValueAccessor {
2030
2239
  registerOnTouched(fn: () => void): void;
2031
2240
  setDisabledState(isDisabled: boolean): void;
2032
2241
  /** Gọi từ template mỗi khi giá trị đổi, để form biết. */
2033
- protected phatThayDoi(val: string): void;
2242
+ protected emitChange(val: string): void;
2034
2243
  /**
2035
2244
  * Xoá nội dung ô.
2036
2245
  *
@@ -2092,41 +2301,13 @@ declare class OlTextarea implements ControlValueAccessor {
2092
2301
  registerOnTouched(fn: () => void): void;
2093
2302
  setDisabledState(isDisabled: boolean): void;
2094
2303
  /** Gọi từ template mỗi khi giá trị đổi, để form biết. */
2095
- protected phatThayDoi(val: string): void;
2304
+ protected emitChange(val: string): void;
2096
2305
  /** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
2097
2306
  protected markTouched(): void;
2098
2307
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTextarea, never>;
2099
2308
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTextarea, "ol-textarea", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "showCharCount": { "alias": "showCharCount"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "showAsterisk": { "alias": "showAsterisk"; "required": false; "isSignal": true; }; "commentBox": { "alias": "commentBox"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "submitComment": "submitComment"; }, never, never, true, never>;
2100
2309
  }
2101
2310
 
2102
- /**
2103
- * Bảng chuẩn kích thước và trạng thái cho MỌI ô nhập của thư viện.
2104
- *
2105
- * ## Vì sao cần
2106
- *
2107
- * Chín component họ form từng tự khai kích thước riêng, và chúng trôi khỏi
2108
- * nhau. Đo ngày 2026-09-23 trên chín component:
2109
- *
2110
- * thiếu cỡ `xl` 3 component (select · autocomplete · input-counter)
2111
- * nền sai 3 component (autocomplete · timepicker · datepicker)
2112
- * viền sai 5 component
2113
- * đệm ngang lệch 2px ở cả ba cỡ đầu (datepicker so với input)
2114
- * cỡ chữ `sm` lệch 2px (14px vs 12px)
2115
- *
2116
- * Nền và viền lệch **chỉ lộ ở chế độ tối**: sáng thì `--bg-secondary` và
2117
- * `--bg-secondary-medium` cùng ra `#f9fafb`, tối thì `gray-950` vs `gray-800`.
2118
- * Nên lỗi sống rất lâu — người dựng nhìn chế độ sáng, thấy giống hệt nhau.
2119
- *
2120
- * Đọc từ đây thì thêm ô nhập mới cũng tự khớp, và đổi thang chỉ sửa một chỗ.
2121
- *
2122
- * ## Không gộp chiều cao với đệm ngang
2123
- *
2124
- * Hai thứ tách riêng vì component có icon cần `ps-*` thay `px-*`. Gộp lại thì
2125
- * mỗi component phải cắt chuỗi ra để bỏ phần đệm — dễ sai hơn là ghép vào.
2126
- */
2127
- /** Bốn cỡ của họ form. Trùng `InputSize`, `SelectSize`, `DatepickerSize`. */
2128
- type FieldSize = 'sm' | 'base' | 'lg' | 'xl';
2129
-
2130
2311
  /**
2131
2312
  * Thang cỡ theo Figma: `sm | base | lg`.
2132
2313
  *
@@ -2452,7 +2633,7 @@ declare class OlSelect implements ControlValueAccessor {
2452
2633
  private hengio;
2453
2634
  constructor();
2454
2635
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
2455
- private donDepPanel;
2636
+ private detachPanelNow;
2456
2637
  /**
2457
2638
  * `[minTermLength]="undefined"` (bind vào khoá state chưa có) đè mất giá
2458
2639
  * trị mặc định, và `x < undefined` ra `NaN` — luôn `false`. Cùng lớp lỗi
@@ -2601,7 +2782,7 @@ declare class OlSelect implements ControlValueAccessor {
2601
2782
  * Phát `search` có GỠ NẢY. Gõ "nguyễn văn a" là 12 phím — không gỡ nảy thì
2602
2783
  * 12 lượt gọi API, mà chỉ lượt cuối có ích.
2603
2784
  */
2604
- private phatSearch;
2785
+ private emitSearch;
2605
2786
  /** Đơn chọn đóng sau khi chọn, đa chọn thì không — trừ khi nơi gọi chốt khác. */
2606
2787
  private shouldClose;
2607
2788
  selectItem(item: any): void;
@@ -3298,6 +3479,20 @@ declare class OlTimepicker implements ControlValueAccessor {
3298
3479
  readonly placeholder: _angular_core.InputSignal<string>;
3299
3480
  readonly format: _angular_core.InputSignal<TimepickerFormat>;
3300
3481
  readonly size: _angular_core.InputSignal<TimepickerSize>;
3482
+ /**
3483
+ * Đệm ngang và cỡ chữ theo BẢNG CHUẨN họ form.
3484
+ *
3485
+ * Trước đây template ghim cứng `px-3.5` (14px cho mọi cỡ) và không đặt cỡ
3486
+ * chữ nào, nên nó thừa kế 16px mặc định. Đo trên /datepicker mục 8 ngày
3487
+ * 2026-09-24, đặt cạnh ba ô kia:
3488
+ *
3489
+ * sm/base timepicker chữ 16px · ba ô kia 14px
3490
+ * mọi cỡ timepicker đệm 14px · input 10/12/14/16px theo cỡ
3491
+ *
3492
+ * Gom vào MỘT chuỗi thay vì nhiều `[class.px-*]`: Angular cắt tên class ở
3493
+ * dấu chấm nên `[class.px-2.5]` render ra `px-2`, lệch 2px so với thiết kế.
3494
+ */
3495
+ protected readonly fieldTextClasses: _angular_core.Signal<string>;
3301
3496
  readonly state: _angular_core.InputSignal<TimepickerState>;
3302
3497
  /**
3303
3498
  * Khai bằng `model()` thay vì `input()` để Reactive Forms khoá được component:
@@ -3373,7 +3568,7 @@ declare class OlTimepicker implements ControlValueAccessor {
3373
3568
  private emitAllChanges;
3374
3569
  constructor(elementRef: ElementRef);
3375
3570
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
3376
- private donDepPanel;
3571
+ private detachPanelNow;
3377
3572
  onDocumentClick(event: MouseEvent): void;
3378
3573
  toggleDropdown(): void;
3379
3574
  private scrollToSelected;
@@ -3430,7 +3625,24 @@ declare class OlAutocomplete implements ControlValueAccessor {
3430
3625
  readonly loading: _angular_core.InputSignal<boolean>;
3431
3626
  readonly size: _angular_core.InputSignal<AutocompleteSize>;
3432
3627
  /** Cỡ đã quy bí danh: `'md'` -> `'base'`. Template so với cái này. */
3433
- protected readonly resolvedSize: _angular_core.Signal<AutocompleteSize>;
3628
+ protected readonly resolvedSize: _angular_core.Signal<FieldSize>;
3629
+ /**
3630
+ * Đệm ngang theo BẢNG CHUẨN họ form. Chỉ dùng khi KHÔNG `multiple` —
3631
+ * chế độ nhiều thẻ có đệm riêng (`px-1.5 py-1`) vì các thẻ tự mang đệm.
3632
+ *
3633
+ * Ba lỗi cũ gộp làm một chỗ này (đo /autocomplete 2026-09-24):
3634
+ * sm `[class.px-2.5]` -> Angular CẮT ở dấu chấm, render `px-2` = 8px
3635
+ * thay vì 10px. Đây là lý do phải gom vào chuỗi, không bind lẻ.
3636
+ * base dùng chung `px-3.5` với `lg` = 14px, bảng chuẩn quy định 12px.
3637
+ * xl có `px-4` nhưng nằm ở `[class.px-4]` rời, dễ lệch khỏi ba cỡ kia.
3638
+ */
3639
+ protected readonly fieldPaddingClass: _angular_core.Signal<string>;
3640
+ /**
3641
+ * Cỡ chữ theo BẢNG CHUẨN. Trước đây `[class.text-base]` chỉ khai cho `lg`,
3642
+ * `xl` không có nhánh nào nên rơi về 16px MẶC ĐỊNH của trình duyệt — trùng
3643
+ * giá trị đúng một cách tình cờ, hỏng ngay khi cỡ gốc đổi.
3644
+ */
3645
+ protected readonly fieldTextClass: _angular_core.Signal<string>;
3434
3646
  readonly state: _angular_core.InputSignal<AutocompleteState>;
3435
3647
  readonly required: _angular_core.InputSignal<boolean>;
3436
3648
  readonly showAsterisk: _angular_core.InputSignal<boolean>;
@@ -3537,7 +3749,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3537
3749
  private hengio;
3538
3750
  constructor(elementRef: ElementRef);
3539
3751
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
3540
- private donDepPanel;
3752
+ private detachPanelNow;
3541
3753
  onDocumentClick(event: MouseEvent): void;
3542
3754
  /**
3543
3755
  * Chưa gõ đủ `minTermLength` ở chế độ từ xa — template hiện
@@ -3592,7 +3804,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3592
3804
  *
3593
3805
  * Dưới `minTermLength` thì KHÔNG phát: nơi gọi không phải tự kiểm lại.
3594
3806
  */
3595
- private phatSearch;
3807
+ private emitSearch;
3596
3808
  selectOption(item: any): void;
3597
3809
  removeTag(valToRemove: any, event: MouseEvent): void;
3598
3810
  clearAll(event: MouseEvent): void;
@@ -3694,8 +3906,74 @@ declare class OlModal {
3694
3906
  readonly headingLevel: _angular_core.InputSignal<1 | 2 | 3 | 4 | 5 | 6>;
3695
3907
  readonly isOpen: _angular_core.ModelSignal<boolean>;
3696
3908
  readonly title: _angular_core.InputSignal<string>;
3909
+ /**
3910
+ * Icon đứng trước tiêu đề ở phần đầu mặc định.
3911
+ *
3912
+ * Thêm 2026-09-24 cùng `description`. Trước đó `ol-modal` chỉ có `title`,
3913
+ * nên muốn có icon hay dòng mô tả là phải lắp `ol-modal-header` thủ công —
3914
+ * và lắp rồi thì `title` bị bỏ qua, phải khai lại qua `heading`. Đo trên
3915
+ * /modal: 13 chỗ dùng modal, chỗ nào cần icon đều lặp lại đúng khuôn đó.
3916
+ *
3917
+ * Vẫn dùng `ol-modal-header` được khi cần thứ phần đầu mặc định không có
3918
+ * (`supportingText`, `helperText`, nút tuỳ biến); lắp nó vào thì phần đầu
3919
+ * mặc định tự tắt, xem `showDefaultHeader`.
3920
+ */
3921
+ readonly icon: _angular_core.InputSignal<string>;
3922
+ /** Dòng mô tả dưới tiêu đề ở phần đầu mặc định. Xem chú thích ở `icon`. */
3923
+ readonly description: _angular_core.InputSignal<string>;
3697
3924
  readonly size: _angular_core.InputSignal<ModalSize>;
3925
+ /**
3926
+ * Cho phép đóng hộp thoại bằng Escape hoặc bấm ra nền mờ.
3927
+ *
3928
+ * Tắt khi người dùng BUỘC phải chọn một phương án — xác nhận xoá, đồng ý
3929
+ * điều khoản. Không còn ẩn nút X nữa: xem `showClose`.
3930
+ */
3698
3931
  readonly dismissible: _angular_core.InputSignal<boolean>;
3932
+ /**
3933
+ * Có vẽ nút X ở góc phải phần đầu không. Mặc định **theo `dismissible`**.
3934
+ *
3935
+ * TÁCH KHỎI `dismissible` (2026-09-24). Trước đây một input gánh BỐN việc —
3936
+ * hiện nút X, đóng bằng Escape, đóng khi bấm nền, và quyết định có dựng
3937
+ * phần đầu — nên `[dismissible]="false"` (chỉ định chặn bấm-ra-ngoài) kéo
3938
+ * theo MẤT nút X. Người dùng phải tự lắp `ol-modal-header` để lấy lại nút,
3939
+ * tức phải khai lại cả tiêu đề vốn đã truyền qua `title`.
3940
+ *
3941
+ * Nay ba trường hợp tách bạch:
3942
+ *
3943
+ * `[dismissible]="false"` chặn Escape + nền mờ, VẪN có nút X
3944
+ * `[dismissible]="false" [showClose]="false"` không lối thoát nào ngoài nút trong nội dung
3945
+ * `[showClose]="false"` giấu nút X, vẫn đóng được bằng Escape/nền
3946
+ *
3947
+ * Mặc định `true` — nút X LUÔN hiện trừ khi tắt tường minh.
3948
+ *
3949
+ * ĐÂY LÀ THAY ĐỔI PHÁ VỠ so với bản trước: `[dismissible]="false"` vốn ẩn
3950
+ * nút X, nay vẫn giữ. Nơi nào thật sự muốn không có lối thoát nào phải thêm
3951
+ * `[showClose]="false"`. Đổi vì hành vi cũ là LỖI chứ không phải lựa chọn:
3952
+ * người dùng chặn bấm-ra-ngoài rồi mất nút đóng, phải tự lắp
3953
+ * `ol-modal-header` để lấy lại — tức khai lại tiêu đề đã truyền qua `title`.
3954
+ */
3955
+ readonly showClose: _angular_core.InputSignal<boolean>;
3956
+ /** Nút X có hiện không. Tách hẳn khỏi `dismissible` từ 2026-09-24. */
3957
+ protected readonly closeVisible: _angular_core.Signal<boolean>;
3958
+ /**
3959
+ * Có `<ol-modal-header>` lồng bên trong không.
3960
+ *
3961
+ * Dùng để KHÔNG dựng phần đầu mặc định khi người dùng đã tự lắp header
3962
+ * riêng. Trước đây phải tắt tay bằng `title=""` kèm `[dismissible]="false"`
3963
+ * — quên một trong hai là lồng HAI khung header chồng nhau, mà build vẫn
3964
+ * xanh nên chỉ nhìn mới thấy.
3965
+ *
3966
+ * `descendants: false` để chỉ bắt header là CON TRỰC TIẾP. Một `ol-modal`
3967
+ * lồng trong nội dung của modal này thì header của nó không tính.
3968
+ */
3969
+ private readonly projectedHeader;
3970
+ /**
3971
+ * Có dựng phần đầu mặc định không.
3972
+ *
3973
+ * Người dùng lắp `ol-modal-header` riêng thì thôi — nếu không sẽ có hai
3974
+ * khung `p-3` chồng nhau, nút đóng hiện hai lần.
3975
+ */
3976
+ protected readonly showDefaultHeader: _angular_core.Signal<boolean>;
3699
3977
  readonly backdrop: _angular_core.InputSignal<OlModalBackdrop>;
3700
3978
  readonly placement: _angular_core.InputSignal<ModalPlacement>;
3701
3979
  /**
@@ -3715,7 +3993,7 @@ declare class OlModal {
3715
3993
  toggle(): void;
3716
3994
  isVisible(): boolean;
3717
3995
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlModal, never>;
3718
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlModal, "ol-modal", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "isOpen": { "alias": "isOpen"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "backdrop": { "alias": "backdrop"; "required": false; "isSignal": true; }; "placement": { "alias": "placement"; "required": false; "isSignal": true; }; "role": { "alias": "role"; "required": false; "isSignal": true; }; }, { "isOpen": "isOpenChange"; "opened": "opened"; "closed": "closed"; }, never, ["*", "[footer]"], true, never>;
3996
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlModal, "ol-modal", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "isOpen": { "alias": "isOpen"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "showClose": { "alias": "showClose"; "required": false; "isSignal": true; }; "backdrop": { "alias": "backdrop"; "required": false; "isSignal": true; }; "placement": { "alias": "placement"; "required": false; "isSignal": true; }; "role": { "alias": "role"; "required": false; "isSignal": true; }; }, { "isOpen": "isOpenChange"; "opened": "opened"; "closed": "closed"; }, ["projectedHeader"], ["*", "[footer]"], true, never>;
3719
3997
  }
3720
3998
 
3721
3999
  /**
@@ -5557,6 +5835,16 @@ declare class OlTreeNodeItem {
5557
5835
  selected: boolean;
5558
5836
  expanded: boolean;
5559
5837
  }>;
5838
+ /**
5839
+ * Chỉ `id` của nút, tách khỏi cả object.
5840
+ *
5841
+ * `effect` bên dưới đọc tín hiệu này thay vì `this.node()`: đọc cả object
5842
+ * thì effect chạy lại mỗi lần nơi dùng thay MẢNG gốc — kiểu tải lại danh
5843
+ * sách từ máy chủ — dù chính nút này không đổi gì. `computed` chỉ phát tín
5844
+ * hiệu khi GIÁ TRỊ đổi, mà `id` là chuỗi nên so sánh bằng `===`: tải lại
5845
+ * 100 nút với cùng id thì effect không chạy lần nào.
5846
+ */
5847
+ private readonly nodeId;
5560
5848
  constructor();
5561
5849
  /**
5562
5850
  * Nút cha có con chọn một phần thì ô của nó phải là dấu gạch.
@@ -6308,6 +6596,26 @@ declare class OlPositioningService {
6308
6596
  * không xử lý được ca modal chồng modal, mỗi lớp mở sau phải nằm trên lớp
6309
6597
  * trước.
6310
6598
  */
6599
+ /**
6600
+ * Một lớp nổi đang mở trong ngăn xếp.
6601
+ *
6602
+ * `contains` cho phép ngăn xếp biết một cú click thuộc về lớp nào. Thiếu nó
6603
+ * thì mỗi component phải tự nghe `document:click` và tự đoán — mà chúng không
6604
+ * biết nhau, nên MỘT cú bấm ra ngoài đóng luôn mọi lớp: mở datepicker trong
6605
+ * modal rồi bấm nền là mất cả hộp thoại đang điền dở. Đo /modal ngày
6606
+ * 2026-09-24 xác nhận đúng như vậy.
6607
+ */
6608
+ interface OverlayLayer {
6609
+ /** Đóng lớp này. */
6610
+ close: () => void;
6611
+ /**
6612
+ * Phần tử được bấm có thuộc lớp này không (kể cả phần đã treo ra `<body>`).
6613
+ *
6614
+ * Trả `undefined` nghĩa là lớp không tham gia cơ chế click-ngoài — nó tự lo,
6615
+ * hoặc chỉ đóng bằng Escape và nút đóng.
6616
+ */
6617
+ contains?: (target: EventTarget | null) => boolean;
6618
+ }
6311
6619
  declare class OlOverlayContainer {
6312
6620
  private readonly doc;
6313
6621
  private host;
@@ -6356,6 +6664,12 @@ declare class OlOverlayContainer {
6356
6664
  private readonly openLayers;
6357
6665
  /** Trình nghe Escape duy nhất, gắn khi có lớp đầu tiên mở. */
6358
6666
  private escapeListener;
6667
+ /**
6668
+ * Trình nghe click duy nhất — cùng nguyên tắc với Escape.
6669
+ *
6670
+ * Bấm ra ngoài chỉ đóng lớp TRÊN CÙNG, lớp dưới chờ cú bấm sau.
6671
+ */
6672
+ private clickListener;
6359
6673
  /**
6360
6674
  * Đăng ký một lớp nổi vừa mở. Trả hàm gỡ — gọi khi lớp đóng.
6361
6675
  *
@@ -6387,7 +6701,7 @@ declare class OlOverlayContainer {
6387
6701
  * Hàm gỡ **an toàn khi gọi nhiều lần** — `close()` thường chạy cả từ phím
6388
6702
  * Escape lẫn từ nút đóng, và cả hai đường đều gọi nó.
6389
6703
  */
6390
- register(close: () => void): () => void;
6704
+ register(close: () => void, contains?: (target: EventTarget | null) => boolean): () => void;
6391
6705
  /**
6392
6706
  * Gắn trình nghe khi có lớp đầu tiên, không gắn sẵn từ lúc dựng service.
6393
6707
  *
@@ -6395,8 +6709,32 @@ declare class OlOverlayContainer {
6395
6709
  * ứng dụng — kể cả khi người dùng đang gõ trong ô nhập bình thường.
6396
6710
  */
6397
6711
  private ensureEscapeListener;
6398
- /** Gỡ trình nghe khi lớp cuối cùng đóng — không để nó nghe vô ích. */
6399
- private teardownEscapeListenerIfIdle;
6712
+ /**
6713
+ * Trình nghe click duy nhất cho cả ngăn xếp.
6714
+ *
6715
+ * ## Vì sao không để mỗi component tự nghe
6716
+ *
6717
+ * Trước đây `ol-datepicker`, `ol-select`, `ol-dropdown`… mỗi cái một
6718
+ * `@HostListener('document:click')`. Chúng không biết nhau, nên một cú bấm
6719
+ * ra ngoài kích hoạt TẤT CẢ cùng lúc: mở datepicker trong modal rồi bấm nền
6720
+ * thì cả datepicker lẫn modal cùng đóng, người dùng mất hộp thoại đang điền
6721
+ * dở. Đo /modal ngày 2026-09-24: `dp=1, modal=true` -> sau MỘT cú bấm còn
6722
+ * `dp=0, modal=false`.
6723
+ *
6724
+ * Escape đã giải đúng từ trước (xem `ensureEscapeListener`); đây là áp cùng
6725
+ * nguyên tắc cho chuột.
6726
+ *
6727
+ * ## Chỉ lớp TRÊN CÙNG đóng
6728
+ *
6729
+ * Click nằm trong lớp trên cùng -> không đóng gì. Nằm ngoài -> đóng đúng lớp
6730
+ * đó, và `stopPropagation` chặn lớp dưới nhận cùng cú bấm ấy.
6731
+ *
6732
+ * Lớp không khai `contains` thì bị bỏ qua hoàn toàn — nó tự lo phần click
6733
+ * của mình, ngăn xếp không đoán thay.
6734
+ */
6735
+ private ensureClickListener;
6736
+ /** Gỡ hai trình nghe khi lớp cuối cùng đóng — không để chúng nghe vô ích. */
6737
+ private teardownListenersIfIdle;
6400
6738
  /**
6401
6739
  * @deprecated Dùng `attach()`. Giữ lại vì đã publish trong
6402
6740
  * `@edmicro/olkit@1.1.1` — bỏ phương thức công khai là breaking change.
@@ -6406,6 +6744,6 @@ declare class OlOverlayContainer {
6406
6744
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
6407
6745
  }
6408
6746
 
6409
- export { CHAT_BUBBLE_STATUS, MONTHS_FULL_EN, MONTHS_FULL_VI, MONTHS_SHORT_VI, OlAccordion, OlAccordionItem, OlAlert, OlAlertDialog, OlAppNav, OlAspectRatio, OlAutocomplete, OlAvatar, OlAvatarGroup, OlBackdrop, OlBadge, OlBanner, OlBottomNav, OlBottomNavItem, OlBreadcrumb, OlBreadcrumbItem, OlButton, OlButtonGroup, OlCard, OlCarousel, OlCarouselSlide, OlChart, OlChatBubble, OlChatContainer, OlCheckbox, OlCheckboxRadioForm, OlCheckboxRadioGroup, OlClipboard, OlCollapse, OlCommand, OlDateRangePicker, OlDatepicker, OlDatepickerDirective, OlDatepickerFooter, OlDatepickerHeader, OlDatepickerPopup, OlDatepickerStateService, OlDayView, OlDeviceMockup, OlDownloadAppButton, OlDrawer, OlDrawerFooter, OlDrawerHeader, OlDropdown, OlDropdownHeader, OlDropdownItem, OlEmpty, OlFileInput, OlFloatingLabel, OlFocusTrapService, OlFooter, OlGallery, OlGalleryItem, OlHelper, OlIcon, OlIconService, OlIconShape, OlInput, OlInputCounter, OlJumbotron, OlKbd, OlLabel, OlListGroup, OlListGroupItem, OlLoadingOverlay, OlModal, OlModalFooter, OlModalHeader, OlMonthView, OlNavDropdown, OlNavbar, OlNavbarItem, OlOtpInput, OlOverlayContainer, OlPagination, OlPopover, OlPopoverContent, OlPositioningService, OlProgress, OlProgressBarService, OlProgressBarTop, OlRadio, OlRange, OlRating, OlRichTextEditor, OlScrollArea, OlSelect, OlSeparator, OlSidebar, OlSidebarGroup, OlSidebarItem, OlSidebarSubmenu, OlSkeleton, OlSpeedDial, OlSpeedDialItem, OlSpinner, OlStepItem, OlStepper, OlTabItem, OlTable, OlTableLoading, OlTableRow, OlTabs, OlTagInput, OlTextarea, OlTimeline, OlTimelineItem, OlTimepicker, OlToast, OlToastContainer, OlToastService, OlToggle, OlToggleGroup, OlToggleInput, OlTooltip, OlTooltipDirective, OlTree, OlTreeNodeItem, OlYearView, WEEKDAYS_SHORT_EN, WEEKDAYS_SHORT_VI, boDau, cloneDate, endOfDay, formatDate, generateMonthMatrix, generateMonthsMatrix, generateYearsMatrix, isBetween, isDateDisabled, isSameDay, isSameMonth, isSameYear, isToday, parseDate, startOfDay, stripDiacritics };
6410
- export type { AccordionVariant, AlertColor, AlertDialogTone, AlertLayout, AlertRounded, AlertType, AlertVariant, AppDevice, AppNavConfig, AppNavGroup, AppNavItem, AppNavUser, AppTheme, AutocompleteOption, AutocompleteSize, AutocompleteState, AvatarShape, AvatarSize, AvatarStatus, BadgeColor, BadgeShape, BadgeSize, BadgeTheme, BadgeType, BannerPosition, BannerVariant, BoxplotDataPoint, BreadcrumbItemStatus, BreadcrumbSeparator, BreadcrumbVariant, ButtonColor, ButtonGroupColor, ButtonGroupOrientation, ButtonGroupSize, ButtonSize, CardPadding, CardRadius, CardShadow, CardVariant, ChartDataPoint, ChartSeries, ChartType, ChatBubbleStatus, ChatSender, CheckboxSize, ChoiceControl, ChoiceControlPosition, ChoiceGroupLayout, ChoiceLayout, ChoiceShape, CommandGroup, CommandItem, DateCustomClass, DateRange, DatepickerPreset, DatepickerSelectionMode, DatepickerSize, DatepickerViewMode, DayCell, DeviceType, DrawerFooterLayout, DrawerPlacement, DrawerSize, DropdownAutoClose, DropdownPlacement, DropdownTrigger, EmptySize, FileInputSize, FloatingLabelSize, FloatingLabelState, FloatingLabelVariant, HeatmapData, HelperFeature, HelperRule, HelperSize, HelperState, IconColor, IconShapeColor, IconShapeSize, IconShapeType, IconSize, IconVariant, InputCounterSize, InputSize, InputState, JumbotronVariant, ListGroupType, ModalFooterLayout, ModalPlacement, ModalSize, MonthCell, OlAvatarStatusPosition, OlBottomNavPosition, OlButtonType, OlCheckboxRadioFormAlign, OlCheckboxRadioGroupRole, OlDateRangePickerVariant, OlGalleryGap, OlGalleryRatio, OlHelperType, OlModalBackdrop, OlModalRole, OlSkeletonAnimation, OlSkeletonAspectRatio, OlSkeletonRounded, OlSkeletonType, OlSpeedDialTooltipPosition, OlTimepickerAddonType, OlToggleInputAlign, OlTreeNode, OlTreeNodeSelectionMode, OlTreeNodeToggleIcon, OlTreeSelectionMode, OlTreeToggleIcon, OtpInputSize, PaginationType, PaginationVariant, PopoverPlacement, PopoverTrigger, PositionedCoordinates, PositioningOptions, ProgressColor, ProgressLabelPosition, ProgressSize, RadarIndicator, RadioSize, RatingType, ScatterDataPoint, ScatterSeries, ScrollAreaAxis, ScrollAreaScrollbar, SelectOption, SelectSize, SelectState, SeparatorOrientation, SpeedDialAction, SpeedDialColor, SpeedDialDirection, SpeedDialTrigger, SpeedDialType, StepState, StepperBreakpoint, StepperOrientation, StepperVariant, TabsSize, TabsVariant, TagInputSize, TextareaState, TimeObject, TimepickerFormat, TimepickerSize, TimepickerState, Toast, ToastConfig, ToastPosition, ToggleColor, TogglePosition, ToggleSize, YearCell };
6747
+ export { CHAT_BUBBLE_STATUS, FIELD_ASTERISK, FIELD_DISABLED, FIELD_HEIGHT, FIELD_HELPER, FIELD_HELPER_GAP, FIELD_LABEL, FIELD_LABEL_GAP, FIELD_MIN_HEIGHT, FIELD_PADDING_X, FIELD_RADIUS, FIELD_STATE, FIELD_TEXT, MONTHS_FULL_EN, MONTHS_FULL_VI, MONTHS_SHORT_VI, OlAccordion, OlAccordionItem, OlAlert, OlAlertDialog, OlAppNav, OlAspectRatio, OlAutocomplete, OlAvatar, OlAvatarGroup, OlBackdrop, OlBadge, OlBanner, OlBottomNav, OlBottomNavItem, OlBreadcrumb, OlBreadcrumbItem, OlButton, OlButtonGroup, OlCard, OlCarousel, OlCarouselSlide, OlChart, OlChatBubble, OlChatContainer, OlCheckbox, OlCheckboxRadioForm, OlCheckboxRadioGroup, OlClipboard, OlCollapse, OlCommand, OlDateRangePicker, OlDatepicker, OlDatepickerDirective, OlDatepickerFooter, OlDatepickerHeader, OlDatepickerPopup, OlDatepickerStateService, OlDayView, OlDeviceMockup, OlDownloadAppButton, OlDrawer, OlDrawerFooter, OlDrawerHeader, OlDropdown, OlDropdownHeader, OlDropdownItem, OlEmpty, OlFileInput, OlFloatingLabel, OlFocusTrapService, OlFooter, OlGallery, OlGalleryItem, OlHelper, OlIcon, OlIconService, OlIconShape, OlInput, OlInputCounter, OlJumbotron, OlKbd, OlLabel, OlListGroup, OlListGroupItem, OlLoadingOverlay, OlModal, OlModalFooter, OlModalHeader, OlMonthView, OlNavDropdown, OlNavbar, OlNavbarItem, OlOtpInput, OlOverlayContainer, OlPagination, OlPopover, OlPopoverContent, OlPositioningService, OlProgress, OlProgressBarService, OlProgressBarTop, OlRadio, OlRange, OlRating, OlRichTextEditor, OlScrollArea, OlSelect, OlSeparator, OlSidebar, OlSidebarGroup, OlSidebarItem, OlSidebarSubmenu, OlSkeleton, OlSpeedDial, OlSpeedDialItem, OlSpinner, OlStepItem, OlStepper, OlTabItem, OlTable, OlTableLoading, OlTableRow, OlTabs, OlTagInput, OlTextarea, OlTimeline, OlTimelineItem, OlTimepicker, OlToast, OlToastContainer, OlToastService, OlToggle, OlToggleGroup, OlToggleInput, OlTooltip, OlTooltipDirective, OlTree, OlTreeNodeItem, OlYearView, WEEKDAYS_SHORT_EN, WEEKDAYS_SHORT_VI, boDau, cloneDate, endOfDay, fieldSizeClasses, formatDate, generateMonthMatrix, generateMonthsMatrix, generateYearsMatrix, isBetween, isDateDisabled, isSameDay, isSameMonth, isSameYear, isToday, parseDate, startOfDay, stripDiacritics };
6748
+ export type { AccordionVariant, AlertColor, AlertDialogTone, AlertLayout, AlertRounded, AlertType, AlertVariant, AppDevice, AppNavConfig, AppNavGroup, AppNavItem, AppNavUser, AppTheme, AutocompleteOption, AutocompleteSize, AutocompleteState, AvatarShape, AvatarSize, AvatarStatus, BadgeColor, BadgeShape, BadgeSize, BadgeTheme, BadgeType, BannerPosition, BannerVariant, BoxplotDataPoint, BreadcrumbItemStatus, BreadcrumbSeparator, BreadcrumbVariant, ButtonColor, ButtonGroupColor, ButtonGroupOrientation, ButtonGroupSize, ButtonSize, CardPadding, CardRadius, CardShadow, CardVariant, ChartDataPoint, ChartSeries, ChartType, ChatBubbleStatus, ChatSender, CheckboxSize, ChoiceControl, ChoiceControlPosition, ChoiceGroupLayout, ChoiceLayout, ChoiceShape, CommandGroup, CommandItem, DateCustomClass, DateRange, DatepickerPreset, DatepickerSelectionMode, DatepickerSize, DatepickerViewMode, DayCell, DeviceType, DrawerFooterLayout, DrawerPlacement, DrawerSize, DropdownAutoClose, DropdownPlacement, DropdownTrigger, EmptySize, FieldSize, FieldState, FileInputSize, FloatingLabelSize, FloatingLabelState, FloatingLabelVariant, HeatmapData, HelperFeature, HelperRule, HelperSize, HelperState, IconColor, IconShapeColor, IconShapeSize, IconShapeType, IconSize, IconVariant, InputCounterSize, InputSize, InputState, JumbotronVariant, ListGroupType, ModalFooterLayout, ModalPlacement, ModalSize, MonthCell, OlAvatarStatusPosition, OlBottomNavPosition, OlButtonType, OlCheckboxRadioFormAlign, OlCheckboxRadioGroupRole, OlDateRangePickerVariant, OlGalleryGap, OlGalleryRatio, OlHelperType, OlModalBackdrop, OlModalRole, OlSkeletonAnimation, OlSkeletonAspectRatio, OlSkeletonRounded, OlSkeletonType, OlSpeedDialTooltipPosition, OlTimepickerAddonType, OlToggleInputAlign, OlTreeNode, OlTreeNodeSelectionMode, OlTreeNodeToggleIcon, OlTreeSelectionMode, OlTreeToggleIcon, OtpInputSize, OverlayLayer, PaginationType, PaginationVariant, PopoverPlacement, PopoverTrigger, PositionedCoordinates, PositioningOptions, ProgressColor, ProgressLabelPosition, ProgressSize, RadarIndicator, RadioSize, RatingType, ScatterDataPoint, ScatterSeries, ScrollAreaAxis, ScrollAreaScrollbar, SelectOption, SelectSize, SelectState, SeparatorOrientation, SpeedDialAction, SpeedDialColor, SpeedDialDirection, SpeedDialTrigger, SpeedDialType, StepState, StepperBreakpoint, StepperOrientation, StepperVariant, TabsSize, TabsVariant, TagInputSize, TextareaState, TimeObject, TimepickerFormat, TimepickerSize, TimepickerState, Toast, ToastConfig, ToastPosition, ToggleColor, TogglePosition, ToggleSize, YearCell };
6411
6749
  //# sourceMappingURL=edmicro-olkit.d.ts.map