@edmicro/olkit 1.1.3 → 1.1.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edmicro/olkit",
3
- "version": "1.1.3",
3
+ "version": "1.1.4",
4
4
  "description": "Ôn Luyện Design System UI Component Library for Angular",
5
5
  "author": "EDMICRO",
6
6
  "license": "MIT",
@@ -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
  }
@@ -1630,6 +1729,112 @@ declare class OlFooter {
1630
1729
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFooter, "ol-footer", never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1631
1730
  }
1632
1731
 
1732
+ /**
1733
+ * Bảng chuẩn kích thước và trạng thái cho MỌI ô nhập của thư viện.
1734
+ *
1735
+ * ## Vì sao cần
1736
+ *
1737
+ * Chín component họ form từng tự khai kích thước riêng, và chúng trôi khỏi
1738
+ * nhau. Đo ngày 2026-09-23 trên chín component:
1739
+ *
1740
+ * thiếu cỡ `xl` 3 component (select · autocomplete · input-counter)
1741
+ * nền sai 3 component (autocomplete · timepicker · datepicker)
1742
+ * viền sai 5 component
1743
+ * đệm ngang lệch 2px ở cả ba cỡ đầu (datepicker so với input)
1744
+ * cỡ chữ `sm` lệch 2px (14px vs 12px)
1745
+ *
1746
+ * Nền và viền lệch **chỉ lộ ở chế độ tối**: sáng thì `--bg-secondary` và
1747
+ * `--bg-secondary-medium` cùng ra `#f9fafb`, tối thì `gray-950` vs `gray-800`.
1748
+ * 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.
1749
+ *
1750
+ * Đọc từ đây thì thêm ô nhập mới cũng tự khớp, và đổi thang chỉ sửa một chỗ.
1751
+ *
1752
+ * ## Không gộp chiều cao với đệm ngang
1753
+ *
1754
+ * Hai thứ tách riêng vì component có icon cần `ps-*` thay `px-*`. Gộp lại thì
1755
+ * mỗi component phải cắt chuỗi ra để bỏ phần đệm — dễ sai hơn là ghép vào.
1756
+ */
1757
+ /** Bốn cỡ của họ form. Trùng `InputSize`, `SelectSize`, `DatepickerSize`. */
1758
+ type FieldSize = 'sm' | 'base' | 'lg' | 'xl';
1759
+ /** Ba trạng thái hiển thị. */
1760
+ type FieldState = 'default' | 'error' | 'success';
1761
+ /**
1762
+ * Chiều cao theo cỡ — **cố định bằng `h-*`**, không suy từ `py-*`.
1763
+ *
1764
+ * Suy từ đệm dọc thì chiều cao phụ thuộc `line-height` của cỡ chữ và không
1765
+ * bao giờ khớp thang chung. Đo trước khi chuẩn hoá: datepicker ra 34/42/54
1766
+ * trong khi input ra 36/40/48 — đặt cạnh nhau là so le ngay, nặng nhất ở `lg`.
1767
+ *
1768
+ * `xl` (52px) là cỡ mới nhất; ba component còn thiếu nó sẽ so le 12px khi đặt
1769
+ * cạnh một `ol-input size="xl"`.
1770
+ */
1771
+ declare const FIELD_HEIGHT: Record<FieldSize, string>;
1772
+ /**
1773
+ * Chiều cao TỐI THIỂU — cho component cao theo nội dung.
1774
+ *
1775
+ * `ol-select` nhiều lựa chọn, `ol-tag-input` nhiều thẻ: chúng phải giãn ra khi
1776
+ * nội dung xuống dòng, nên `h-*` cố định sẽ cắt mất. Giá trị bằng đúng
1777
+ * `FIELD_HEIGHT` để một dòng vẫn cao bằng các ô khác.
1778
+ */
1779
+ declare const FIELD_MIN_HEIGHT: Record<FieldSize, string>;
1780
+ /**
1781
+ * Đệm ngang theo Figma `ol-input`: sm 10px · base 12px · lg 14px · xl 16px.
1782
+ *
1783
+ * KHÔNG dùng `[class.px-2.5]` trên template: Angular cắt phần sau dấu chấm
1784
+ * 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.
1785
+ */
1786
+ declare const FIELD_PADDING_X: Record<FieldSize, string>;
1787
+ /**
1788
+ * Cỡ chữ theo cỡ ô.
1789
+ *
1790
+ * `sm` là **14px** (`text-sm`), không phải 12px. Datepicker từng dùng
1791
+ * `text-xs` nên chữ trong ô nhỏ hơn hẳn các ô cùng hàng.
1792
+ */
1793
+ declare const FIELD_TEXT: Record<FieldSize, string>;
1794
+ /**
1795
+ * Nền · viền · vòng focus theo trạng thái.
1796
+ *
1797
+ * Dùng `-medium` cho cả nền lẫn viền ở trạng thái thường. Hai tên gần giống
1798
+ * nhau (`bg-neutral-secondary` vs `bg-neutral-secondary-medium`) nhưng **khác
1799
+ * hẳn ở chế độ tối**, nên nhầm một chữ là ô đó lệch màu mà chế độ sáng không
1800
+ * lộ ra.
1801
+ *
1802
+ * Dùng `focus-within:` chứ không `focus:`: viền nằm trên thẻ bọc, còn focus
1803
+ * rơi vào `<input>` bên trong. `focus:` trên thẻ bọc không bao giờ khớp.
1804
+ */
1805
+ declare const FIELD_STATE: Record<FieldState, string>;
1806
+ /** Nền và con trỏ khi ô bị khoá. Áp đè lên `FIELD_STATE`. */
1807
+ declare const FIELD_DISABLED = "bg-disabled opacity-60 cursor-not-allowed";
1808
+ /** Bo góc — 12px cho mọi cỡ, theo Figma `bieu-mau.md`. */
1809
+ declare const FIELD_RADIUS = "rounded-md";
1810
+ /**
1811
+ * Lớp của nhãn phía trên ô.
1812
+ *
1813
+ * `text-heading` chứ không `text-body`: nhãn là thứ người dùng đọc đầu tiên để
1814
+ * biết ô này hỏi gì, nó phải đậm hơn chữ phụ.
1815
+ */
1816
+ declare const FIELD_LABEL = "inline-flex items-center gap-1 text-sm font-medium text-heading";
1817
+ /** Khoảng cách nhãn → ô. */
1818
+ declare const FIELD_LABEL_GAP = "mb-1.5";
1819
+ /** Dấu sao bắt buộc. */
1820
+ declare const FIELD_ASTERISK = "text-fg-danger font-bold";
1821
+ /**
1822
+ * Chữ trợ giúp dưới ô, đổi màu theo trạng thái.
1823
+ *
1824
+ * 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
1825
+ * giải thích lỗi hay chú thích thường.
1826
+ */
1827
+ declare const FIELD_HELPER: Record<FieldState, string>;
1828
+ /** Khoảng cách ô → chữ trợ giúp. */
1829
+ declare const FIELD_HELPER_GAP = "mt-1.5";
1830
+ /**
1831
+ * Gộp chiều cao · đệm ngang · cỡ chữ · bo góc cho một cỡ.
1832
+ *
1833
+ * Tiện cho component không có icon. Component có icon tự ghép từ các bảng
1834
+ * riêng để thay `px-*` bằng `ps-*`.
1835
+ */
1836
+ declare function fieldSizeClasses(size: FieldSize): string;
1837
+
1633
1838
  /**
1634
1839
  * Nhãn cho ô nhập — khớp Figma `ol-label` (#1149:94727), trục
1635
1840
  * `Type=[Default | Required & icon | Two texts]`.
@@ -2030,7 +2235,7 @@ declare class OlInput implements ControlValueAccessor {
2030
2235
  registerOnTouched(fn: () => void): void;
2031
2236
  setDisabledState(isDisabled: boolean): void;
2032
2237
  /** Gọi từ template mỗi khi giá trị đổi, để form biết. */
2033
- protected phatThayDoi(val: string): void;
2238
+ protected emitChange(val: string): void;
2034
2239
  /**
2035
2240
  * Xoá nội dung ô.
2036
2241
  *
@@ -2092,41 +2297,13 @@ declare class OlTextarea implements ControlValueAccessor {
2092
2297
  registerOnTouched(fn: () => void): void;
2093
2298
  setDisabledState(isDisabled: boolean): void;
2094
2299
  /** Gọi từ template mỗi khi giá trị đổi, để form biết. */
2095
- protected phatThayDoi(val: string): void;
2300
+ protected emitChange(val: string): void;
2096
2301
  /** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
2097
2302
  protected markTouched(): void;
2098
2303
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTextarea, never>;
2099
2304
  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
2305
  }
2101
2306
 
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
2307
  /**
2131
2308
  * Thang cỡ theo Figma: `sm | base | lg`.
2132
2309
  *
@@ -2452,7 +2629,7 @@ declare class OlSelect implements ControlValueAccessor {
2452
2629
  private hengio;
2453
2630
  constructor();
2454
2631
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
2455
- private donDepPanel;
2632
+ private detachPanelNow;
2456
2633
  /**
2457
2634
  * `[minTermLength]="undefined"` (bind vào khoá state chưa có) đè mất giá
2458
2635
  * trị mặc định, và `x < undefined` ra `NaN` — luôn `false`. Cùng lớp lỗi
@@ -2601,7 +2778,7 @@ declare class OlSelect implements ControlValueAccessor {
2601
2778
  * Phát `search` có GỠ NẢY. Gõ "nguyễn văn a" là 12 phím — không gỡ nảy thì
2602
2779
  * 12 lượt gọi API, mà chỉ lượt cuối có ích.
2603
2780
  */
2604
- private phatSearch;
2781
+ private emitSearch;
2605
2782
  /** Đơ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
2783
  private shouldClose;
2607
2784
  selectItem(item: any): void;
@@ -3298,6 +3475,20 @@ declare class OlTimepicker implements ControlValueAccessor {
3298
3475
  readonly placeholder: _angular_core.InputSignal<string>;
3299
3476
  readonly format: _angular_core.InputSignal<TimepickerFormat>;
3300
3477
  readonly size: _angular_core.InputSignal<TimepickerSize>;
3478
+ /**
3479
+ * Đệm ngang và cỡ chữ theo BẢNG CHUẨN họ form.
3480
+ *
3481
+ * Trước đây template ghim cứng `px-3.5` (14px cho mọi cỡ) và không đặt cỡ
3482
+ * chữ nào, nên nó thừa kế 16px mặc định. Đo trên /datepicker mục 8 ngày
3483
+ * 2026-09-24, đặt cạnh ba ô kia:
3484
+ *
3485
+ * sm/base timepicker chữ 16px · ba ô kia 14px
3486
+ * mọi cỡ timepicker đệm 14px · input 10/12/14/16px theo cỡ
3487
+ *
3488
+ * Gom vào MỘT chuỗi thay vì nhiều `[class.px-*]`: Angular cắt tên class ở
3489
+ * dấu chấm nên `[class.px-2.5]` render ra `px-2`, lệch 2px so với thiết kế.
3490
+ */
3491
+ protected readonly fieldTextClasses: _angular_core.Signal<string>;
3301
3492
  readonly state: _angular_core.InputSignal<TimepickerState>;
3302
3493
  /**
3303
3494
  * Khai bằng `model()` thay vì `input()` để Reactive Forms khoá được component:
@@ -3373,7 +3564,7 @@ declare class OlTimepicker implements ControlValueAccessor {
3373
3564
  private emitAllChanges;
3374
3565
  constructor(elementRef: ElementRef);
3375
3566
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
3376
- private donDepPanel;
3567
+ private detachPanelNow;
3377
3568
  onDocumentClick(event: MouseEvent): void;
3378
3569
  toggleDropdown(): void;
3379
3570
  private scrollToSelected;
@@ -3430,7 +3621,24 @@ declare class OlAutocomplete implements ControlValueAccessor {
3430
3621
  readonly loading: _angular_core.InputSignal<boolean>;
3431
3622
  readonly size: _angular_core.InputSignal<AutocompleteSize>;
3432
3623
  /** Cỡ đã quy bí danh: `'md'` -> `'base'`. Template so với cái này. */
3433
- protected readonly resolvedSize: _angular_core.Signal<AutocompleteSize>;
3624
+ protected readonly resolvedSize: _angular_core.Signal<FieldSize>;
3625
+ /**
3626
+ * Đệm ngang theo BẢNG CHUẨN họ form. Chỉ dùng khi KHÔNG `multiple` —
3627
+ * chế độ nhiều thẻ có đệm riêng (`px-1.5 py-1`) vì các thẻ tự mang đệm.
3628
+ *
3629
+ * Ba lỗi cũ gộp làm một chỗ này (đo /autocomplete 2026-09-24):
3630
+ * sm `[class.px-2.5]` -> Angular CẮT ở dấu chấm, render `px-2` = 8px
3631
+ * thay vì 10px. Đây là lý do phải gom vào chuỗi, không bind lẻ.
3632
+ * base dùng chung `px-3.5` với `lg` = 14px, bảng chuẩn quy định 12px.
3633
+ * xl có `px-4` nhưng nằm ở `[class.px-4]` rời, dễ lệch khỏi ba cỡ kia.
3634
+ */
3635
+ protected readonly fieldPaddingClass: _angular_core.Signal<string>;
3636
+ /**
3637
+ * Cỡ chữ theo BẢNG CHUẨN. Trước đây `[class.text-base]` chỉ khai cho `lg`,
3638
+ * `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
3639
+ * giá trị đúng một cách tình cờ, hỏng ngay khi cỡ gốc đổi.
3640
+ */
3641
+ protected readonly fieldTextClass: _angular_core.Signal<string>;
3434
3642
  readonly state: _angular_core.InputSignal<AutocompleteState>;
3435
3643
  readonly required: _angular_core.InputSignal<boolean>;
3436
3644
  readonly showAsterisk: _angular_core.InputSignal<boolean>;
@@ -3537,7 +3745,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3537
3745
  private hengio;
3538
3746
  constructor(elementRef: ElementRef);
3539
3747
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
3540
- private donDepPanel;
3748
+ private detachPanelNow;
3541
3749
  onDocumentClick(event: MouseEvent): void;
3542
3750
  /**
3543
3751
  * Chưa gõ đủ `minTermLength` ở chế độ từ xa — template hiện
@@ -3592,7 +3800,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3592
3800
  *
3593
3801
  * Dưới `minTermLength` thì KHÔNG phát: nơi gọi không phải tự kiểm lại.
3594
3802
  */
3595
- private phatSearch;
3803
+ private emitSearch;
3596
3804
  selectOption(item: any): void;
3597
3805
  removeTag(valToRemove: any, event: MouseEvent): void;
3598
3806
  clearAll(event: MouseEvent): void;
@@ -3694,8 +3902,74 @@ declare class OlModal {
3694
3902
  readonly headingLevel: _angular_core.InputSignal<1 | 2 | 3 | 4 | 5 | 6>;
3695
3903
  readonly isOpen: _angular_core.ModelSignal<boolean>;
3696
3904
  readonly title: _angular_core.InputSignal<string>;
3905
+ /**
3906
+ * Icon đứng trước tiêu đề ở phần đầu mặc định.
3907
+ *
3908
+ * Thêm 2026-09-24 cùng `description`. Trước đó `ol-modal` chỉ có `title`,
3909
+ * nên muốn có icon hay dòng mô tả là phải lắp `ol-modal-header` thủ công —
3910
+ * và lắp rồi thì `title` bị bỏ qua, phải khai lại qua `heading`. Đo trên
3911
+ * /modal: 13 chỗ dùng modal, chỗ nào cần icon đều lặp lại đúng khuôn đó.
3912
+ *
3913
+ * Vẫn dùng `ol-modal-header` được khi cần thứ phần đầu mặc định không có
3914
+ * (`supportingText`, `helperText`, nút tuỳ biến); lắp nó vào thì phần đầu
3915
+ * mặc định tự tắt, xem `showDefaultHeader`.
3916
+ */
3917
+ readonly icon: _angular_core.InputSignal<string>;
3918
+ /** Dòng mô tả dưới tiêu đề ở phần đầu mặc định. Xem chú thích ở `icon`. */
3919
+ readonly description: _angular_core.InputSignal<string>;
3697
3920
  readonly size: _angular_core.InputSignal<ModalSize>;
3921
+ /**
3922
+ * Cho phép đóng hộp thoại bằng Escape hoặc bấm ra nền mờ.
3923
+ *
3924
+ * Tắt khi người dùng BUỘC phải chọn một phương án — xác nhận xoá, đồng ý
3925
+ * điều khoản. Không còn ẩn nút X nữa: xem `showClose`.
3926
+ */
3698
3927
  readonly dismissible: _angular_core.InputSignal<boolean>;
3928
+ /**
3929
+ * Có vẽ nút X ở góc phải phần đầu không. Mặc định **theo `dismissible`**.
3930
+ *
3931
+ * TÁCH KHỎI `dismissible` (2026-09-24). Trước đây một input gánh BỐN việc —
3932
+ * hiện nút X, đóng bằng Escape, đóng khi bấm nền, và quyết định có dựng
3933
+ * phần đầu — nên `[dismissible]="false"` (chỉ định chặn bấm-ra-ngoài) kéo
3934
+ * theo MẤT nút X. Người dùng phải tự lắp `ol-modal-header` để lấy lại nút,
3935
+ * tức phải khai lại cả tiêu đề vốn đã truyền qua `title`.
3936
+ *
3937
+ * Nay ba trường hợp tách bạch:
3938
+ *
3939
+ * `[dismissible]="false"` chặn Escape + nền mờ, VẪN có nút X
3940
+ * `[dismissible]="false" [showClose]="false"` không lối thoát nào ngoài nút trong nội dung
3941
+ * `[showClose]="false"` giấu nút X, vẫn đóng được bằng Escape/nền
3942
+ *
3943
+ * Mặc định `true` — nút X LUÔN hiện trừ khi tắt tường minh.
3944
+ *
3945
+ * ĐÂY LÀ THAY ĐỔI PHÁ VỠ so với bản trước: `[dismissible]="false"` vốn ẩn
3946
+ * 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
3947
+ * `[showClose]="false"`. Đổi vì hành vi cũ là LỖI chứ không phải lựa chọn:
3948
+ * người dùng chặn bấm-ra-ngoài rồi mất nút đóng, phải tự lắp
3949
+ * `ol-modal-header` để lấy lại — tức khai lại tiêu đề đã truyền qua `title`.
3950
+ */
3951
+ readonly showClose: _angular_core.InputSignal<boolean>;
3952
+ /** Nút X có hiện không. Tách hẳn khỏi `dismissible` từ 2026-09-24. */
3953
+ protected readonly closeVisible: _angular_core.Signal<boolean>;
3954
+ /**
3955
+ * Có `<ol-modal-header>` lồng bên trong không.
3956
+ *
3957
+ * Dùng để KHÔNG dựng phần đầu mặc định khi người dùng đã tự lắp header
3958
+ * riêng. Trước đây phải tắt tay bằng `title=""` kèm `[dismissible]="false"`
3959
+ * — quên một trong hai là lồng HAI khung header chồng nhau, mà build vẫn
3960
+ * xanh nên chỉ nhìn mới thấy.
3961
+ *
3962
+ * `descendants: false` để chỉ bắt header là CON TRỰC TIẾP. Một `ol-modal`
3963
+ * lồng trong nội dung của modal này thì header của nó không tính.
3964
+ */
3965
+ private readonly projectedHeader;
3966
+ /**
3967
+ * Có dựng phần đầu mặc định không.
3968
+ *
3969
+ * Người dùng lắp `ol-modal-header` riêng thì thôi — nếu không sẽ có hai
3970
+ * khung `p-3` chồng nhau, nút đóng hiện hai lần.
3971
+ */
3972
+ protected readonly showDefaultHeader: _angular_core.Signal<boolean>;
3699
3973
  readonly backdrop: _angular_core.InputSignal<OlModalBackdrop>;
3700
3974
  readonly placement: _angular_core.InputSignal<ModalPlacement>;
3701
3975
  /**
@@ -3715,7 +3989,7 @@ declare class OlModal {
3715
3989
  toggle(): void;
3716
3990
  isVisible(): boolean;
3717
3991
  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>;
3992
+ 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
3993
  }
3720
3994
 
3721
3995
  /**
@@ -5557,6 +5831,16 @@ declare class OlTreeNodeItem {
5557
5831
  selected: boolean;
5558
5832
  expanded: boolean;
5559
5833
  }>;
5834
+ /**
5835
+ * Chỉ `id` của nút, tách khỏi cả object.
5836
+ *
5837
+ * `effect` bên dưới đọc tín hiệu này thay vì `this.node()`: đọc cả object
5838
+ * 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
5839
+ * sách từ máy chủ — dù chính nút này không đổi gì. `computed` chỉ phát tín
5840
+ * hiệu khi GIÁ TRỊ đổi, mà `id` là chuỗi nên so sánh bằng `===`: tải lại
5841
+ * 100 nút với cùng id thì effect không chạy lần nào.
5842
+ */
5843
+ private readonly nodeId;
5560
5844
  constructor();
5561
5845
  /**
5562
5846
  * Nút cha có con chọn một phần thì ô của nó phải là dấu gạch.
@@ -6308,6 +6592,26 @@ declare class OlPositioningService {
6308
6592
  * 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
6593
  * trước.
6310
6594
  */
6595
+ /**
6596
+ * Một lớp nổi đang mở trong ngăn xếp.
6597
+ *
6598
+ * `contains` cho phép ngăn xếp biết một cú click thuộc về lớp nào. Thiếu nó
6599
+ * thì mỗi component phải tự nghe `document:click` và tự đoán — mà chúng không
6600
+ * biết nhau, nên MỘT cú bấm ra ngoài đóng luôn mọi lớp: mở datepicker trong
6601
+ * modal rồi bấm nền là mất cả hộp thoại đang điền dở. Đo /modal ngày
6602
+ * 2026-09-24 xác nhận đúng như vậy.
6603
+ */
6604
+ interface OverlayLayer {
6605
+ /** Đóng lớp này. */
6606
+ close: () => void;
6607
+ /**
6608
+ * Phần tử được bấm có thuộc lớp này không (kể cả phần đã treo ra `<body>`).
6609
+ *
6610
+ * Trả `undefined` nghĩa là lớp không tham gia cơ chế click-ngoài — nó tự lo,
6611
+ * hoặc chỉ đóng bằng Escape và nút đóng.
6612
+ */
6613
+ contains?: (target: EventTarget | null) => boolean;
6614
+ }
6311
6615
  declare class OlOverlayContainer {
6312
6616
  private readonly doc;
6313
6617
  private host;
@@ -6356,6 +6660,12 @@ declare class OlOverlayContainer {
6356
6660
  private readonly openLayers;
6357
6661
  /** Trình nghe Escape duy nhất, gắn khi có lớp đầu tiên mở. */
6358
6662
  private escapeListener;
6663
+ /**
6664
+ * Trình nghe click duy nhất — cùng nguyên tắc với Escape.
6665
+ *
6666
+ * Bấm ra ngoài chỉ đóng lớp TRÊN CÙNG, lớp dưới chờ cú bấm sau.
6667
+ */
6668
+ private clickListener;
6359
6669
  /**
6360
6670
  * Đăng ký một lớp nổi vừa mở. Trả hàm gỡ — gọi khi lớp đóng.
6361
6671
  *
@@ -6387,7 +6697,7 @@ declare class OlOverlayContainer {
6387
6697
  * Hàm gỡ **an toàn khi gọi nhiều lần** — `close()` thường chạy cả từ phím
6388
6698
  * Escape lẫn từ nút đóng, và cả hai đường đều gọi nó.
6389
6699
  */
6390
- register(close: () => void): () => void;
6700
+ register(close: () => void, contains?: (target: EventTarget | null) => boolean): () => void;
6391
6701
  /**
6392
6702
  * 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
6703
  *
@@ -6395,8 +6705,32 @@ declare class OlOverlayContainer {
6395
6705
  * ứng dụng — kể cả khi người dùng đang gõ trong ô nhập bình thường.
6396
6706
  */
6397
6707
  private ensureEscapeListener;
6398
- /** Gỡ trình nghe khi lớp cuối cùng đóng — không để nó nghe vô ích. */
6399
- private teardownEscapeListenerIfIdle;
6708
+ /**
6709
+ * Trình nghe click duy nhất cho cả ngăn xếp.
6710
+ *
6711
+ * ## Vì sao không để mỗi component tự nghe
6712
+ *
6713
+ * Trước đây `ol-datepicker`, `ol-select`, `ol-dropdown`… mỗi cái một
6714
+ * `@HostListener('document:click')`. Chúng không biết nhau, nên một cú bấm
6715
+ * ra ngoài kích hoạt TẤT CẢ cùng lúc: mở datepicker trong modal rồi bấm nền
6716
+ * thì cả datepicker lẫn modal cùng đóng, người dùng mất hộp thoại đang điền
6717
+ * dở. Đo /modal ngày 2026-09-24: `dp=1, modal=true` -> sau MỘT cú bấm còn
6718
+ * `dp=0, modal=false`.
6719
+ *
6720
+ * Escape đã giải đúng từ trước (xem `ensureEscapeListener`); đây là áp cùng
6721
+ * nguyên tắc cho chuột.
6722
+ *
6723
+ * ## Chỉ lớp TRÊN CÙNG đóng
6724
+ *
6725
+ * Click nằm trong lớp trên cùng -> không đóng gì. Nằm ngoài -> đóng đúng lớp
6726
+ * đó, và `stopPropagation` chặn lớp dưới nhận cùng cú bấm ấy.
6727
+ *
6728
+ * Lớp không khai `contains` thì bị bỏ qua hoàn toàn — nó tự lo phần click
6729
+ * của mình, ngăn xếp không đoán thay.
6730
+ */
6731
+ private ensureClickListener;
6732
+ /** Gỡ hai trình nghe khi lớp cuối cùng đóng — không để chúng nghe vô ích. */
6733
+ private teardownListenersIfIdle;
6400
6734
  /**
6401
6735
  * @deprecated Dùng `attach()`. Giữ lại vì đã publish trong
6402
6736
  * `@edmicro/olkit@1.1.1` — bỏ phương thức công khai là breaking change.
@@ -6406,6 +6740,6 @@ declare class OlOverlayContainer {
6406
6740
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
6407
6741
  }
6408
6742
 
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 };
6743
+ 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 };
6744
+ 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
6745
  //# sourceMappingURL=edmicro-olkit.d.ts.map