@edmicro/olkit 1.1.2 → 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.
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { OnInit, AfterContentInit, OnDestroy, TemplateRef, ElementRef, QueryList, WritableSignal } from '@angular/core';
2
+ import { OnInit, AfterContentInit, OnDestroy, TemplateRef, ElementRef, QueryList } from '@angular/core';
3
3
  import { ControlValueAccessor } from '@angular/forms';
4
4
  import * as _edmicro_olkit from '@edmicro/olkit';
5
5
  import { SafeHtml } from '@angular/platform-browser';
@@ -174,6 +174,7 @@ declare const boDau: typeof stripDiacritics;
174
174
  */
175
175
  declare class OlCommand {
176
176
  private readonly focusTrap;
177
+ private readonly overlayContainer;
177
178
  private readonly destroyRef;
178
179
  private readonly panel;
179
180
  private readonly searchInput;
@@ -348,6 +349,10 @@ type AlertDialogTone = 'danger' | 'warning' | 'brand';
348
349
  * Build vẫn xanh, chỉ bộ đọc màn hình là đọc sai.
349
350
  */
350
351
  declare class OlAlertDialog {
352
+ private readonly overlayContainer;
353
+ /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi đang đóng. */
354
+ private releaseEscape;
355
+ constructor();
351
356
  readonly isOpen: _angular_core.ModelSignal<boolean>;
352
357
  /**
353
358
  * `heading` chứ không phải `title`: `title` là thuộc tính HTML có sẵn, đặt
@@ -372,7 +377,6 @@ declare class OlAlertDialog {
372
377
  * đòi `alertdialog` phải đóng được bằng Esc, nên xử lý ở đây, và cho nó rơi
373
378
  * vào nhánh AN TOÀN chứ không phải nhánh xác nhận.
374
379
  */
375
- onEscape(): void;
376
380
  onConfirm(): void;
377
381
  onCancel(): void;
378
382
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlAlertDialog, never>;
@@ -510,7 +514,19 @@ declare class OlAvatarGroup {
510
514
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlAvatarGroup, "ol-avatar-group", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "counter": { "alias": "counter"; "required": false; "isSignal": true; }; "counterAriaLabel": { "alias": "counterAriaLabel"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
511
515
  }
512
516
 
513
- type BadgeTheme = 'brand' | 'gray' | 'white' | 'danger' | 'warning' | 'success' | 'default' | 'dark' | 'red' | 'yellow' | 'green';
517
+ /**
518
+ * Sáu màu CHUẨN (Figma `ol-badge` #1146:22250, trục `Theme`, 126 biến thể):
519
+ * Gray · White · Brand · Danger · Warning · Success.
520
+ *
521
+ * `ghost` là màu THỨ BẢY, CHƯA CÓ TRONG FIGMA — thêm 2026-09-23 theo yêu cầu,
522
+ * bê khuôn từ `ol-button` (nền trong suốt, chữ `text-heading`). Đừng gỡ khi
523
+ * đối chiếu Figma thấy thừa; còn nợ vẽ biến thể `Theme=Ghost`.
524
+ *
525
+ * Năm giá trị cuối là BÍ DANH, `normalizedColor()` gập về sáu màu chuẩn.
526
+ */
527
+ type BadgeColor = 'brand' | 'gray' | 'white' | 'danger' | 'warning' | 'success' | 'ghost' | 'default' | 'dark' | 'red' | 'yellow' | 'green';
528
+ /** @deprecated Tên cũ của `BadgeColor`. Giữ để không phá mã đang chạy. */
529
+ type BadgeTheme = BadgeColor;
514
530
  type BadgeSize = 'xs' | 'sm' | 'lg';
515
531
  type BadgeShape = 'square' | 'pill';
516
532
  /**
@@ -523,8 +539,36 @@ type BadgeShape = 'square' | 'pill';
523
539
  */
524
540
  type BadgeType = 'default' | 'dot' | 'avatar' | 'loader' | 'secondary';
525
541
  declare class OlBadge {
526
- readonly theme: _angular_core.InputSignal<BadgeTheme>;
527
- readonly color: _angular_core.InputSignal<BadgeTheme>;
542
+ /**
543
+ * Màu ngữ nghĩa của nhãn. Tên `color` cho khớp `ol-button` và 9 component
544
+ * khác trong thư viện — badge từng là chỗ duy nhất gọi trục này là `theme`.
545
+ */
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>;
562
+ /**
563
+ * @deprecated Dùng `color`.
564
+ *
565
+ * Bản cũ hoà giải hai input bằng cách SO VỚI GIÁ TRỊ MẶC ĐỊNH — `theme`
566
+ * thắng trừ khi nó bằng `'gray'` — nên `[color]="'brand'"` kèm
567
+ * `theme="danger"` thì `color` bị nuốt im lặng, không gì báo lỗi.
568
+ *
569
+ * Nay: cái nào được TRUYỀN thì thắng. Chuỗi rỗng là "không truyền".
570
+ */
571
+ readonly theme: _angular_core.InputSignal<"" | BadgeColor>;
528
572
  readonly size: _angular_core.InputSignal<BadgeSize>;
529
573
  readonly shape: _angular_core.InputSignal<BadgeShape>;
530
574
  readonly rounded: _angular_core.InputSignal<boolean>;
@@ -539,7 +583,7 @@ declare class OlBadge {
539
583
  readonly removable: _angular_core.InputSignal<boolean>;
540
584
  readonly href: _angular_core.InputSignal<string | null>;
541
585
  readonly dismiss: _angular_core.OutputEmitterRef<MouseEvent>;
542
- readonly normalizedTheme: _angular_core.Signal<string>;
586
+ readonly normalizedColor: _angular_core.Signal<string>;
543
587
  readonly isPill: _angular_core.Signal<boolean>;
544
588
  readonly badgeClasses: _angular_core.Signal<string>;
545
589
  /**
@@ -551,7 +595,7 @@ declare class OlBadge {
551
595
  readonly dotClasses: _angular_core.Signal<"bg-fg-brand-strong" | "bg-fg-danger-strong" | "bg-fg-warning" | "bg-success-strong" | "bg-heading">;
552
596
  onDismiss(event: MouseEvent): void;
553
597
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlBadge, never>;
554
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBadge, "ol-badge", never, { "theme": { "alias": "theme"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "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>;
555
599
  }
556
600
 
557
601
  type BannerVariant = 'brand' | 'gradient' | 'warning' | 'dark' | 'subtle';
@@ -643,12 +687,67 @@ declare class OlBreadcrumbItem {
643
687
  readonly status: _angular_core.InputSignal<BreadcrumbItemStatus>;
644
688
  readonly showSeparator: _angular_core.InputSignal<boolean>;
645
689
  readonly separator: _angular_core.InputSignal<BreadcrumbSeparator | null>;
690
+ /**
691
+ * Chặng không bấm được — ví dụ cấp cha mà người dùng chưa có quyền xem.
692
+ *
693
+ * VƯỢT FIGMA CÓ CHỦ Ý (2026-09-23): `ol-breadcrumb-item` #1148:31348 chỉ vẽ
694
+ * ba Status — Initial · Hover · Active. Không có Disabled. Đây là mở rộng
695
+ * theo yêu cầu, không phải đọc sót thiết kế; đừng gỡ khi đối chiếu Figma.
696
+ *
697
+ * Khác `current`: `current` là trang ĐANG đứng (mục cuối, có `aria-current`),
698
+ * còn `disabled` là chặng có tồn tại nhưng không đi tới được.
699
+ */
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>;
646
725
  private readonly parentBreadcrumb;
647
726
  readonly isCurrent: _angular_core.Signal<boolean>;
648
727
  readonly effectiveSeparator: _angular_core.Signal<BreadcrumbSeparator>;
728
+ /**
729
+ * `disabled` phải chặn ở ĐÂY, không chỉ đổi màu: còn sinh thẻ `<a href>` thì
730
+ * vẫn bấm được, vẫn Tab tới được, vẫn mở bằng chuột giữa — tô mờ chỉ làm
731
+ * người dùng tưởng nó hỏng.
732
+ */
649
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;
650
749
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlBreadcrumbItem, never>;
651
- 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; }; }, {}, 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>;
652
751
  }
653
752
 
654
753
  type AppDevice = 'appstore' | 'googleplay' | 'google-play' | 'apple' | 'google';
@@ -957,10 +1056,26 @@ declare class OlDatepicker implements ControlValueAccessor {
957
1056
  private readonly overlayContainer;
958
1057
  /** Hàm trả panel về chỗ cũ + hàm ngắt theo dõi vị trí. */
959
1058
  private detachPanel;
1059
+ /**
1060
+ * Hàm gỡ khỏi ngăn xếp Escape. `null` khi popup đang đóng.
1061
+ *
1062
+ * Xem `OlOverlayContainer.register` — chỉ lớp TRÊN CÙNG đóng khi nhấn
1063
+ * Escape, nên datepicker mở trong modal không còn kéo modal đóng theo.
1064
+ */
1065
+ private releaseEscape;
960
1066
  /** Hàm gỡ theo dõi trần chiều cao popup. */
961
1067
  private stopTrackingHeight;
962
1068
  readonly isOpen: _angular_core.WritableSignal<boolean>;
963
1069
  readonly inputValue: _angular_core.WritableSignal<string>;
1070
+ /**
1071
+ * Có gì để xoá hay không — dựa vào CHỮ TRONG Ô, không chỉ `value`.
1072
+ *
1073
+ * `value` chỉ được đặt khi chuỗi phân tích thành ngày hợp lệ (`if (parsed)`
1074
+ * trong `onInputChange`). Gõ dở "15/0" thì ô đã có chữ mà `value` vẫn rỗng,
1075
+ * nên nút xoá không hiện — đúng lúc người dùng gõ sai và cần xoá đi gõ lại.
1076
+ * Đo /datepicker: gõ "15/0" nút không hiện, gõ đủ "15/03/2026" mới hiện.
1077
+ */
1078
+ readonly hasContent: _angular_core.Signal<boolean>;
964
1079
  private onChange;
965
1080
  private onTouched;
966
1081
  private emitAllChanges;
@@ -987,7 +1102,7 @@ declare class OlDatepicker implements ControlValueAccessor {
987
1102
  * Bỏ `py-*`: chiều cao đã cố định thì đệm dọc chỉ ép chữ, giữ `px-*` cho đệm
988
1103
  * ngang và `ps-*` cho chỗ icon lịch.
989
1104
  */
990
- readonly inputSizeClasses: _angular_core.Signal<"h-9 px-3 ps-9 text-xs rounded-md" | "h-12 px-4 ps-11 text-base rounded-md" | "h-13 px-4 ps-11 text-base rounded-md" | "h-10 px-3.5 ps-10 text-sm rounded-md">;
1105
+ readonly inputSizeClasses: _angular_core.Signal<string>;
991
1106
  /**
992
1107
  * Cỡ icon lịch theo `size` của ô nhập.
993
1108
  *
@@ -1012,9 +1127,35 @@ declare class OlDatepicker implements ControlValueAccessor {
1012
1127
  onInputChange(val: string): void;
1013
1128
  clearInput(event: MouseEvent): void;
1014
1129
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
1015
- private donDepPanel;
1016
- onDocumentClick(event: MouseEvent): void;
1017
- onEscape(): 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;
1018
1159
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDatepicker, never>;
1019
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>;
1020
1161
  }
@@ -1073,11 +1214,18 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1073
1214
  private readonly overlayContainer;
1074
1215
  /** Hàm trả panel về chỗ cũ + hàm ngắt theo dõi vị trí. */
1075
1216
  private detachPanel;
1217
+ /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi đang đóng. */
1218
+ private releaseEscape;
1076
1219
  /** Hàm gỡ theo dõi trần chiều cao popup. */
1077
1220
  private stopTrackingHeight;
1078
1221
  readonly isOpen: _angular_core.WritableSignal<boolean>;
1079
1222
  readonly startInputValue: _angular_core.WritableSignal<string>;
1080
1223
  readonly endInputValue: _angular_core.WritableSignal<string>;
1224
+ /**
1225
+ * Có gì để xoá hay không — dựa vào CHỮ TRONG Ô, không chỉ `value`.
1226
+ * Cùng lý do với `ol-datepicker`: `value` chỉ đặt khi chuỗi phân tích được.
1227
+ */
1228
+ readonly hasContent: _angular_core.Signal<boolean>;
1081
1229
  private onChange;
1082
1230
  private onTouched;
1083
1231
  constructor();
@@ -1093,7 +1241,7 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1093
1241
  * dùng y hệt `py-2 / py-2.5 / py-3.5` nên lệch cùng một kiểu; sửa một nửa là
1094
1242
  * để lại đúng lỗi đó ở chỗ khác.
1095
1243
  */
1096
- readonly inputSizeClasses: _angular_core.Signal<"h-9 px-3 text-xs rounded-md" | "h-12 px-4 text-base rounded-md" | "h-13 px-4 text-base rounded-md" | "h-10 px-3.5 text-sm rounded-md">;
1244
+ readonly inputSizeClasses: _angular_core.Signal<string>;
1097
1245
  /**
1098
1246
  * Cỡ cho biến thể `joined` — khung bọc mang viền, hai input bên trong trong
1099
1247
  * suốt. Phải tách khỏi `inputSizeClasses()`: ở đây chiều cao thuộc về KHUNG,
@@ -1125,9 +1273,26 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1125
1273
  onEndInputChange(val: string): void;
1126
1274
  clearRange(event: MouseEvent): void;
1127
1275
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
1128
- private donDepPanel;
1129
- onDocumentClick(event: MouseEvent): void;
1130
- onEscape(): 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;
1131
1296
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDateRangePicker, never>;
1132
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>;
1133
1298
  }
@@ -1263,7 +1428,7 @@ declare class OlDatepickerPopup {
1263
1428
  /**
1264
1429
  * Trần chiều cao + cuộn CHỈ cho popup nổi.
1265
1430
  *
1266
- * Dùng `[class]` trả chuỗi chứ KHÔNG `[class.max-h-[var(...)]]`: tên class
1431
+ * Dùng `[class]` trả chuỗi chứ KHÔNG `[class.max-h-…]` với giá trị tuỳ ý: tên class
1267
1432
  * chứa dấu chấm và ngoặc vuông nên Angular cắt hỏng tên khi dùng class
1268
1433
  * binding từng cái (`[class.h-1.5]` ra `h-1`).
1269
1434
  */
@@ -1355,6 +1520,7 @@ type DrawerPlacement = 'left' | 'right' | 'top' | 'bottom';
1355
1520
  type DrawerSize = 'sm' | 'base' | 'lg' | 'xl' | 'full';
1356
1521
  declare class OlDrawer {
1357
1522
  private readonly focusTrap;
1523
+ private readonly overlayContainer;
1358
1524
  private readonly destroyRef;
1359
1525
  private readonly panel;
1360
1526
  readonly isOpen: _angular_core.ModelSignal<boolean>;
@@ -1406,11 +1572,18 @@ declare class OlDrawerHeader {
1406
1572
  */
1407
1573
  readonly headingLevel: _angular_core.InputSignal<1 | 2 | 3 | 4 | 5 | 6>;
1408
1574
  readonly heading: _angular_core.InputSignal<string>;
1575
+ /** Icon đầu tiêu đề. Rỗng thì KHÔNG dựng khung. Xem `ol-modal-header`. */
1576
+ readonly icon: _angular_core.InputSignal<string>;
1577
+ /** Mô tả phụ dưới tiêu đề. Cùng tên với `ol-modal-header`. */
1578
+ readonly description: _angular_core.InputSignal<string>;
1579
+ /** @deprecated Dùng `description`. Giữ để không phá mã đang chạy. */
1409
1580
  readonly helperText: _angular_core.InputSignal<string>;
1581
+ /** Cái nào được TRUYỀN thì thắng. Xem chú thích ở `ol-modal-header`. */
1582
+ readonly descriptionText: _angular_core.Signal<string>;
1410
1583
  readonly showClose: _angular_core.InputSignal<boolean>;
1411
1584
  readonly closeClick: _angular_core.OutputEmitterRef<void>;
1412
1585
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDrawerHeader, never>;
1413
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDrawerHeader, "ol-drawer-header", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "showClose": { "alias": "showClose"; "required": false; "isSignal": true; }; }, { "closeClick": "closeClick"; }, never, ["*"], true, never>;
1586
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDrawerHeader, "ol-drawer-header", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "showClose": { "alias": "showClose"; "required": false; "isSignal": true; }; }, { "closeClick": "closeClick"; }, never, ["*"], true, never>;
1414
1587
  }
1415
1588
 
1416
1589
  /** Khớp trục `Type` của Figma ol-drawer-footer (node 1149:37232). */
@@ -1450,6 +1623,8 @@ declare class OlDropdown {
1450
1623
  private cleanupAutoUpdate;
1451
1624
  /** Hàm trả menu về chỗ cũ khi đóng. */
1452
1625
  private detachMenu;
1626
+ /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi đang đóng. */
1627
+ private releaseEscape;
1453
1628
  readonly label: _angular_core.InputSignal<string | null>;
1454
1629
  readonly isOpen: _angular_core.ModelSignal<boolean>;
1455
1630
  /**
@@ -1489,7 +1664,6 @@ declare class OlDropdown {
1489
1664
  private anchorElement;
1490
1665
  private stopAutoUpdate;
1491
1666
  onDocumentClick(event: MouseEvent): void;
1492
- onEscape(): void;
1493
1667
  /** Toạ độ con trỏ lần bấm chuột phải gần nhất, đổ vào mốc ẩn. */
1494
1668
  readonly cursorX: _angular_core.WritableSignal<number>;
1495
1669
  readonly cursorY: _angular_core.WritableSignal<number>;
@@ -1555,6 +1729,112 @@ declare class OlFooter {
1555
1729
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFooter, "ol-footer", never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1556
1730
  }
1557
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
+
1558
1838
  /**
1559
1839
  * Nhãn cho ô nhập — khớp Figma `ol-label` (#1149:94727), trục
1560
1840
  * `Type=[Default | Required & icon | Two texts]`.
@@ -1911,6 +2191,20 @@ declare class OlInput implements ControlValueAccessor {
1911
2191
  readonly addOnIcon: _angular_core.InputSignal<boolean>;
1912
2192
  readonly leftIcon: _angular_core.InputSignal<boolean>;
1913
2193
  readonly rightIcon: _angular_core.InputSignal<boolean>;
2194
+ /**
2195
+ * Hiện nút xoá khi ô có chữ.
2196
+ *
2197
+ * Mặc định **TẮT**, khác `ol-select` và `ol-datepicker` (mặc định bật).
2198
+ * Lý do: hai cái kia chọn từ danh sách nên xoá là thao tác thường gặp, còn
2199
+ * ô nhập chữ thì người dùng vẫn chọn-hết-rồi-gõ-đè như mọi ô nhập khác.
2200
+ * Bật sẵn cho MỌI `ol-input` đang chạy là thêm một nút vào giao diện của
2201
+ * người khác mà họ không yêu cầu.
2202
+ */
2203
+ readonly clearable: _angular_core.InputSignal<boolean>;
2204
+ /** Tên trợ năng của nút xoá. Đổi được khi ô có nghĩa cụ thể. */
2205
+ readonly clearAriaLabel: _angular_core.InputSignal<string>;
2206
+ /** Phát khi người dùng bấm nút xoá — nơi dùng biết để tải lại danh sách. */
2207
+ readonly cleared: _angular_core.OutputEmitterRef<void>;
1914
2208
  readonly rightHelperText: _angular_core.InputSignal<string>;
1915
2209
  readonly id: _angular_core.InputSignal<string>;
1916
2210
  /** Id thực dùng: ưu tiên id do nơi gọi truyền, không thì dùng id tự sinh. */
@@ -1941,11 +2235,19 @@ declare class OlInput implements ControlValueAccessor {
1941
2235
  registerOnTouched(fn: () => void): void;
1942
2236
  setDisabledState(isDisabled: boolean): void;
1943
2237
  /** Gọi từ template mỗi khi giá trị đổi, để form biết. */
1944
- protected phatThayDoi(val: string): void;
2238
+ protected emitChange(val: string): void;
2239
+ /**
2240
+ * Xoá nội dung ô.
2241
+ *
2242
+ * `stopPropagation` vì nút nằm TRONG thẻ bọc: thiếu nó thì cú bấm lan ra
2243
+ * thẻ bọc, và ở component có hành vi bấm-để-mở (select, datepicker) sẽ mở
2244
+ * panel ngay sau khi xoá.
2245
+ */
2246
+ protected clearValue(event: MouseEvent): void;
1945
2247
  /** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
1946
2248
  protected markTouched(): void;
1947
2249
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlInput, never>;
1948
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlInput, "ol-input", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "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; }; "labelIcon": { "alias": "labelIcon"; "required": false; "isSignal": true; }; "labelTooltip": { "alias": "labelTooltip"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "prefixText": { "alias": "prefixText"; "required": false; "isSignal": true; }; "suffixText": { "alias": "suffixText"; "required": false; "isSignal": true; }; "addOnIcon": { "alias": "addOnIcon"; "required": false; "isSignal": true; }; "leftIcon": { "alias": "leftIcon"; "required": false; "isSignal": true; }; "rightIcon": { "alias": "rightIcon"; "required": false; "isSignal": true; }; "rightHelperText": { "alias": "rightHelperText"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; }, never, ["[addOnIcon], [leftIcon]", "[leftIcon]", "[rightIcon]", "[rightHelperAction]"], true, never>;
2250
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlInput, "ol-input", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "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; }; "labelIcon": { "alias": "labelIcon"; "required": false; "isSignal": true; }; "labelTooltip": { "alias": "labelTooltip"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "prefixText": { "alias": "prefixText"; "required": false; "isSignal": true; }; "suffixText": { "alias": "suffixText"; "required": false; "isSignal": true; }; "addOnIcon": { "alias": "addOnIcon"; "required": false; "isSignal": true; }; "leftIcon": { "alias": "leftIcon"; "required": false; "isSignal": true; }; "rightIcon": { "alias": "rightIcon"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "clearAriaLabel": { "alias": "clearAriaLabel"; "required": false; "isSignal": true; }; "rightHelperText": { "alias": "rightHelperText"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "cleared": "cleared"; }, never, ["[addOnIcon], [leftIcon]", "[leftIcon]", "[rightIcon]", "[rightHelperAction]"], true, never>;
1949
2251
  }
1950
2252
 
1951
2253
  type TextareaState = 'default' | 'error' | 'success';
@@ -1995,7 +2297,7 @@ declare class OlTextarea implements ControlValueAccessor {
1995
2297
  registerOnTouched(fn: () => void): void;
1996
2298
  setDisabledState(isDisabled: boolean): void;
1997
2299
  /** Gọi từ template mỗi khi giá trị đổi, để form biết. */
1998
- protected phatThayDoi(val: string): void;
2300
+ protected emitChange(val: string): void;
1999
2301
  /** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
2000
2302
  protected markTouched(): void;
2001
2303
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTextarea, never>;
@@ -2012,11 +2314,17 @@ declare class OlTextarea implements ControlValueAccessor {
2012
2314
  *
2013
2315
  * Cùng cách xử lý với `variant`/`currentPage` của `ol-pagination`.
2014
2316
  *
2015
- * LƯU Ý cho lượt sau: họ form nay là `sm | base | lg | xl` (`ol-input`,
2016
- * `ol-timepicker`, `ol-datepicker`). `ol-select` **thiếu `xl`** — thêm được
2017
- * nhưng là mở rộng API, cần chốt trước.
2317
+ * Họ form là `sm | base | lg | xl` (`ol-input`, `ol-timepicker`,
2318
+ * `ol-datepicker`), và `ol-select` **có đủ cả bốn** — `xl` đi qua bảng chuẩn
2319
+ * `FIELD_HEIGHT`/`FIELD_PADDING_X` trong `forms/shared/field-tokens.ts` như
2320
+ * mọi cỡ khác.
2321
+ *
2322
+ * (Chú thích cũ ở đây ghi "ol-select thiếu xl" — đã LẠC HẬU, gỡ 2026-09-23.
2323
+ * Bảng API của ba trang docs chép theo nó nên cũng thiếu `xl`; `check:api:days`
2324
+ * bắt được, nhưng guard đó chưa bao giờ chạy vì vòng lặp gọi guard dùng regex
2325
+ * `check:[a-z-]*` — không khớp tên có HAI dấu hai chấm.)
2018
2326
  */
2019
- type SelectSize = 'sm' | 'base' | 'lg' | 'md';
2327
+ type SelectSize = 'sm' | 'base' | 'lg' | 'xl' | 'md';
2020
2328
  type SelectState = 'default' | 'error' | 'success';
2021
2329
  interface SelectOption {
2022
2330
  label: string;
@@ -2133,6 +2441,26 @@ declare class OlSelect implements ControlValueAccessor {
2133
2441
  * và PrimeNG.
2134
2442
  */
2135
2443
  readonly appendTo: _angular_core.InputSignal<"body" | null>;
2444
+ /**
2445
+ * Lớp Tailwind cho panel thả xuống.
2446
+ *
2447
+ * Cùng tên và cùng ý nghĩa với `menuClass` của `ol-dropdown`.
2448
+ *
2449
+ * Chỉ có tác dụng khi `matchTriggerWidth` TẮT: bề rộng panel được đặt bằng
2450
+ * `style.width` inline, mà style inline thắng mọi lớp Tailwind.
2451
+ */
2452
+ readonly menuClass: _angular_core.InputSignal<string>;
2453
+ /**
2454
+ * Panel rộng đúng bằng ô neo hay không. Mặc định **bật** — giữ hành vi cũ.
2455
+ *
2456
+ * Tắt khi nội dung cần rộng hơn ô: nhãn dài, hai cột, hoặc nội dung tuỳ
2457
+ * biến truyền qua `ng-template`. Lúc đó `menuClass` mới có chỗ tác dụng.
2458
+ *
2459
+ * ```html
2460
+ * <ol-select [matchTriggerWidth]="false" menuClass="w-96" … />
2461
+ * ```
2462
+ */
2463
+ readonly matchTriggerWidth: _angular_core.InputSignal<boolean>;
2136
2464
  /** Tối đa bao nhiêu mục được chọn ở chế độ đa chọn. 0 = không giới hạn. */
2137
2465
  readonly maxSelectedItems: _angular_core.InputSignal<number>;
2138
2466
  /** Đóng danh sách ngay sau khi chọn. Mặc định: đơn chọn đóng, đa chọn không. */
@@ -2264,13 +2592,29 @@ declare class OlSelect implements ControlValueAccessor {
2264
2592
  readonly listboxId: _angular_core.Signal<string>;
2265
2593
  readonly describedBy: _angular_core.Signal<string | null>;
2266
2594
  readonly ariaInvalid: _angular_core.Signal<"true" | null>;
2267
- protected readonly resolvedSize: _angular_core.Signal<SelectSize>;
2595
+ /**
2596
+ * Cỡ đã bỏ bí danh `'md'`.
2597
+ *
2598
+ * Khai kiểu trả về là `FieldSize` chứ không để TypeScript suy: suy ra sẽ
2599
+ * giữ `'md'` trong kiểu hợp dù giá trị không bao giờ là `'md'`, và mọi chỗ
2600
+ * tra bảng chuẩn sẽ báo lỗi "Property 'md' does not exist".
2601
+ */
2602
+ protected readonly resolvedSize: _angular_core.Signal<FieldSize>;
2268
2603
  /**
2269
2604
  * Padding trái: 10 khi có icon, 3.5 khi không.
2270
2605
  * Không dùng `[class.ps-3.5]` vì Angular cắt phần sau dấu chấm — nó render
2271
2606
  * ra `ps-3` (12px thay vì 14px).
2272
2607
  */
2273
- readonly paddingStartClass: _angular_core.Signal<"ps-10" | "ps-3.5">;
2608
+ /**
2609
+ * Đệm trái theo bảng chuẩn, KHÔNG khoá cứng một giá trị cho mọi cỡ.
2610
+ *
2611
+ * Bản cũ trả `ps-3.5` (14px) cho cả bốn cỡ, trong khi `ol-input` dùng
2612
+ * 10/12/14/16px. Đặt `ol-select size="sm"` cạnh `ol-input size="sm"` là
2613
+ * lệch 4px, và ở `xl` lệch 2px theo chiều ngược lại.
2614
+ *
2615
+ * Có icon trái thì `ps-10` cố định — chỗ đó dành cho icon, không phải đệm.
2616
+ */
2617
+ readonly paddingStartClass: _angular_core.Signal<string>;
2274
2618
  private onChange;
2275
2619
  private onTouched;
2276
2620
  private readonly destroyRef;
@@ -2278,12 +2622,14 @@ declare class OlSelect implements ControlValueAccessor {
2278
2622
  private readonly overlayContainer;
2279
2623
  /** Hàm trả panel về chỗ cũ + hàm ngắt theo dõi vị trí. */
2280
2624
  private detachPanel;
2625
+ /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi panel đang đóng. */
2626
+ private releaseEscape;
2281
2627
  private stopTracking;
2282
2628
  private wasOpen;
2283
2629
  private hengio;
2284
2630
  constructor();
2285
2631
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
2286
- private donDepPanel;
2632
+ private detachPanelNow;
2287
2633
  /**
2288
2634
  * `[minTermLength]="undefined"` (bind vào khoá state chưa có) đè mất giá
2289
2635
  * trị mặc định, và `x < undefined` ra `NaN` — luôn `false`. Cùng lớp lỗi
@@ -2338,6 +2684,21 @@ declare class OlSelect implements ControlValueAccessor {
2338
2684
  * gì: nút chết còn khó hiểu hơn là không có nút.
2339
2685
  */
2340
2686
  readonly hasClearable: _angular_core.Signal<boolean>;
2687
+ /**
2688
+ * Có gì để nút xoá dọn hay không — mục đã chọn HOẶC chữ đang gõ ở ô tìm.
2689
+ *
2690
+ * `hasClearable()` chỉ nhìn mục đã chọn, nên gõ vào ô tìm mà chưa chọn gì
2691
+ * thì không có nút — người dùng gõ sai phải xoá từng ký tự. Đo /select: gõ
2692
+ * "to" vào ô tìm, số nút xoá vẫn 0.
2693
+ *
2694
+ * Nút dọn cả hai thứ (`clear()` đã gọi `searchQuery.set('')`), nên điều
2695
+ * kiện hiện phải nhìn cả hai.
2696
+ *
2697
+ * Tên `hasContent` dùng CHUNG với `ol-datepicker` và `ol-date-range-picker`
2698
+ * — cùng một khái niệm thì cùng một tên, để người đọc mã không phải nhớ ba
2699
+ * cách gọi cho một thứ.
2700
+ */
2701
+ readonly hasContent: _angular_core.Signal<boolean>;
2341
2702
  /**
2342
2703
  * Có hiện lối tạo mục mới không.
2343
2704
  *
@@ -2417,7 +2778,7 @@ declare class OlSelect implements ControlValueAccessor {
2417
2778
  * Phát `search` có GỠ NẢY. Gõ "nguyễn văn a" là 12 phím — không gỡ nảy thì
2418
2779
  * 12 lượt gọi API, mà chỉ lượt cuối có ích.
2419
2780
  */
2420
- private phatSearch;
2781
+ private emitSearch;
2421
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. */
2422
2783
  private shouldClose;
2423
2784
  selectItem(item: any): void;
@@ -2448,7 +2809,7 @@ declare class OlSelect implements ControlValueAccessor {
2448
2809
  registerOnTouched(fn: () => void): void;
2449
2810
  setDisabledState(isDisabled: boolean): void;
2450
2811
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSelect, never>;
2451
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSelect, "ol-select", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "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; }; "leftIcon": { "alias": "leftIcon"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "valueKey": { "alias": "valueKey"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "maxSelectedItems": { "alias": "maxSelectedItems"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "hideSelected": { "alias": "hideSelected"; "required": false; "isSignal": true; }; "allowCreate": { "alias": "allowCreate"; "required": false; "isSignal": true; }; "createText": { "alias": "createText"; "required": false; "isSignal": true; }; "groupSeparator": { "alias": "groupSeparator"; "required": false; "isSignal": true; }; "searchPosition": { "alias": "searchPosition"; "required": false; "isSignal": true; }; "searchIcon": { "alias": "searchIcon"; "required": false; "isSignal": true; }; "searchPrefix": { "alias": "searchPrefix"; "required": false; "isSignal": true; }; "triggerIcon": { "alias": "triggerIcon"; "required": false; "isSignal": true; }; "triggerLabel": { "alias": "triggerLabel"; "required": false; "isSignal": true; }; "remoteSearch": { "alias": "remoteSearch"; "required": false; "isSignal": true; }; "minTermLength": { "alias": "minTermLength"; "required": false; "isSignal": true; }; "debounceMs": { "alias": "debounceMs"; "required": false; "isSignal": true; }; "typeToSearchText": { "alias": "typeToSearchText"; "required": false; "isSignal": true; }; "virtualScroll": { "alias": "virtualScroll"; "required": false; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": false; "isSignal": true; }; "bufferAmount": { "alias": "bufferAmount"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "search": "search"; "opened": "opened"; "closed": "closed"; "cleared": "cleared"; "added": "added"; "removed": "removed"; "selectionChange": "selectionChange"; }, ["optionTemplate", "labelTemplate"], ["[leftIcon]"], true, never>;
2812
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSelect, "ol-select", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "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; }; "leftIcon": { "alias": "leftIcon"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "searchable": { "alias": "searchable"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "valueKey": { "alias": "valueKey"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "menuClass": { "alias": "menuClass"; "required": false; "isSignal": true; }; "matchTriggerWidth": { "alias": "matchTriggerWidth"; "required": false; "isSignal": true; }; "maxSelectedItems": { "alias": "maxSelectedItems"; "required": false; "isSignal": true; }; "closeOnSelect": { "alias": "closeOnSelect"; "required": false; "isSignal": true; }; "hideSelected": { "alias": "hideSelected"; "required": false; "isSignal": true; }; "allowCreate": { "alias": "allowCreate"; "required": false; "isSignal": true; }; "createText": { "alias": "createText"; "required": false; "isSignal": true; }; "groupSeparator": { "alias": "groupSeparator"; "required": false; "isSignal": true; }; "searchPosition": { "alias": "searchPosition"; "required": false; "isSignal": true; }; "searchIcon": { "alias": "searchIcon"; "required": false; "isSignal": true; }; "searchPrefix": { "alias": "searchPrefix"; "required": false; "isSignal": true; }; "triggerIcon": { "alias": "triggerIcon"; "required": false; "isSignal": true; }; "triggerLabel": { "alias": "triggerLabel"; "required": false; "isSignal": true; }; "remoteSearch": { "alias": "remoteSearch"; "required": false; "isSignal": true; }; "minTermLength": { "alias": "minTermLength"; "required": false; "isSignal": true; }; "debounceMs": { "alias": "debounceMs"; "required": false; "isSignal": true; }; "typeToSearchText": { "alias": "typeToSearchText"; "required": false; "isSignal": true; }; "virtualScroll": { "alias": "virtualScroll"; "required": false; "isSignal": true; }; "itemHeight": { "alias": "itemHeight"; "required": false; "isSignal": true; }; "bufferAmount": { "alias": "bufferAmount"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "search": "search"; "opened": "opened"; "closed": "closed"; "cleared": "cleared"; "added": "added"; "removed": "removed"; "selectionChange": "selectionChange"; }, ["optionTemplate", "labelTemplate"], ["[leftIcon]"], true, never>;
2452
2813
  }
2453
2814
 
2454
2815
  /**
@@ -2836,7 +3197,7 @@ declare class OlFloatingLabel implements ControlValueAccessor {
2836
3197
  * Thang cỡ theo Figma: `sm | base | lg`. `'md'` là BÍ DANH của `'base'`,
2837
3198
  * giữ cho mã cũ chạy tiếp và sẽ bỏ ở bản sau — dùng `'base'` cho mã mới.
2838
3199
  */
2839
- type InputCounterSize = 'sm' | 'base' | 'lg' | 'md';
3200
+ type InputCounterSize = 'sm' | 'base' | 'lg' | 'xl' | 'md';
2840
3201
  declare class OlInputCounter implements ControlValueAccessor {
2841
3202
  /** Id tự sinh khi nơi gọi không truyền id — để <label for> và
2842
3203
  * aria-describedby luôn nối được với ô nhập. */
@@ -2882,7 +3243,7 @@ declare class OlInputCounter implements ControlValueAccessor {
2882
3243
  /** Nhãn: `inline` bỏ margin dưới vì nó nằm cạnh, không nằm trên. */
2883
3244
  protected readonly labelClasses: _angular_core.Signal<"shrink-0 text-sm font-medium text-heading" | "block mb-1.5 text-sm font-medium text-heading">;
2884
3245
  /** Khối nút+ô: `ghost` bỏ nền, viền và đổ bóng. */
2885
- protected readonly groupClasses: _angular_core.Signal<"relative inline-flex items-center overflow-hidden" | "relative inline-flex items-center rounded-md shadow-xs border border-default bg-neutral-primary overflow-hidden">;
3246
+ protected readonly groupClasses: _angular_core.Signal<"relative inline-flex items-center overflow-hidden" | "relative inline-flex items-center rounded-md shadow-xs border border-default-medium bg-neutral-primary overflow-hidden">;
2886
3247
  /** Nút trong chế độ `ghost` cũng phải bỏ nền và đường ngăn. */
2887
3248
  protected readonly ghostNut: _angular_core.Signal<"" | "bg-transparent border-0">;
2888
3249
  increment(): void;
@@ -3114,6 +3475,20 @@ declare class OlTimepicker implements ControlValueAccessor {
3114
3475
  readonly placeholder: _angular_core.InputSignal<string>;
3115
3476
  readonly format: _angular_core.InputSignal<TimepickerFormat>;
3116
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>;
3117
3492
  readonly state: _angular_core.InputSignal<TimepickerState>;
3118
3493
  /**
3119
3494
  * Khai bằng `model()` thay vì `input()` để Reactive Forms khoá được component:
@@ -3189,7 +3564,7 @@ declare class OlTimepicker implements ControlValueAccessor {
3189
3564
  private emitAllChanges;
3190
3565
  constructor(elementRef: ElementRef);
3191
3566
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
3192
- private donDepPanel;
3567
+ private detachPanelNow;
3193
3568
  onDocumentClick(event: MouseEvent): void;
3194
3569
  toggleDropdown(): void;
3195
3570
  private scrollToSelected;
@@ -3215,7 +3590,7 @@ declare class OlTimepicker implements ControlValueAccessor {
3215
3590
  * Thang cỡ theo Figma: `sm | base | lg`. `'md'` là BÍ DANH của `'base'`,
3216
3591
  * giữ cho mã cũ chạy tiếp và sẽ bỏ ở bản sau — dùng `'base'` cho mã mới.
3217
3592
  */
3218
- type AutocompleteSize = 'sm' | 'base' | 'lg' | 'md';
3593
+ type AutocompleteSize = 'sm' | 'base' | 'lg' | 'xl' | 'md';
3219
3594
  type AutocompleteState = 'default' | 'error' | 'success';
3220
3595
  interface AutocompleteOption {
3221
3596
  label: string;
@@ -3246,7 +3621,24 @@ declare class OlAutocomplete implements ControlValueAccessor {
3246
3621
  readonly loading: _angular_core.InputSignal<boolean>;
3247
3622
  readonly size: _angular_core.InputSignal<AutocompleteSize>;
3248
3623
  /** Cỡ đã quy bí danh: `'md'` -> `'base'`. Template so với cái này. */
3249
- 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>;
3250
3642
  readonly state: _angular_core.InputSignal<AutocompleteState>;
3251
3643
  readonly required: _angular_core.InputSignal<boolean>;
3252
3644
  readonly showAsterisk: _angular_core.InputSignal<boolean>;
@@ -3272,6 +3664,26 @@ declare class OlAutocomplete implements ControlValueAccessor {
3272
3664
  * và PrimeNG.
3273
3665
  */
3274
3666
  readonly appendTo: _angular_core.InputSignal<"body" | null>;
3667
+ /**
3668
+ * Lớp Tailwind cho panel thả xuống.
3669
+ *
3670
+ * Cùng tên và cùng ý nghĩa với `menuClass` của `ol-dropdown`.
3671
+ *
3672
+ * Chỉ có tác dụng khi `matchTriggerWidth` TẮT: bề rộng panel được đặt bằng
3673
+ * `style.width` inline, mà style inline thắng mọi lớp Tailwind.
3674
+ */
3675
+ readonly menuClass: _angular_core.InputSignal<string>;
3676
+ /**
3677
+ * Panel rộng đúng bằng ô neo hay không. Mặc định **bật** — giữ hành vi cũ.
3678
+ *
3679
+ * Tắt khi nội dung cần rộng hơn ô: nhãn dài, hai cột, hoặc nội dung tuỳ
3680
+ * biến truyền qua `ng-template`. Lúc đó `menuClass` mới có chỗ tác dụng.
3681
+ *
3682
+ * ```html
3683
+ * <ol-select [matchTriggerWidth]="false" menuClass="w-96" … />
3684
+ * ```
3685
+ */
3686
+ readonly matchTriggerWidth: _angular_core.InputSignal<boolean>;
3275
3687
  readonly groupBy: _angular_core.InputSignal<string>;
3276
3688
  /**
3277
3689
  * LỌC TỪ XA. Bật lên thì component NGỪNG tự lọc `options` — nó chỉ phát
@@ -3323,6 +3735,8 @@ declare class OlAutocomplete implements ControlValueAccessor {
3323
3735
  private readonly overlayContainer;
3324
3736
  /** Hàm trả panel về chỗ cũ + hàm ngắt theo dõi vị trí. */
3325
3737
  private detachPanel;
3738
+ /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi panel đang đóng. */
3739
+ private releaseEscape;
3326
3740
  private stopTracking;
3327
3741
  private readonly destroyRef;
3328
3742
  /** Trạng thái mở ở lượt chạy trước — để `effect` chỉ phát khi ĐỔI. */
@@ -3331,7 +3745,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3331
3745
  private hengio;
3332
3746
  constructor(elementRef: ElementRef);
3333
3747
  /** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
3334
- private donDepPanel;
3748
+ private detachPanelNow;
3335
3749
  onDocumentClick(event: MouseEvent): void;
3336
3750
  /**
3337
3751
  * Chưa gõ đủ `minTermLength` ở chế độ từ xa — template hiện
@@ -3386,7 +3800,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3386
3800
  *
3387
3801
  * Dưới `minTermLength` thì KHÔNG phát: nơi gọi không phải tự kiểm lại.
3388
3802
  */
3389
- private phatSearch;
3803
+ private emitSearch;
3390
3804
  selectOption(item: any): void;
3391
3805
  removeTag(valToRemove: any, event: MouseEvent): void;
3392
3806
  clearAll(event: MouseEvent): void;
@@ -3398,7 +3812,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3398
3812
  registerOnTouched(fn: any): void;
3399
3813
  setDisabledState(isDisabled: boolean): void;
3400
3814
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlAutocomplete, never>;
3401
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlAutocomplete, "ol-autocomplete", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "valueKey": { "alias": "valueKey"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "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; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "errorText": { "alias": "errorText"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "allowCreate": { "alias": "allowCreate"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "remoteSearch": { "alias": "remoteSearch"; "required": false; "isSignal": true; }; "minTermLength": { "alias": "minTermLength"; "required": false; "isSignal": true; }; "debounceMs": { "alias": "debounceMs"; "required": false; "isSignal": true; }; "typeToSearchText": { "alias": "typeToSearchText"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "createOption": "createOption"; "search": "search"; "opened": "opened"; "closed": "closed"; "cleared": "cleared"; "added": "added"; "removed": "removed"; "selectionChange": "selectionChange"; }, ["optionTemplate"], never, true, never>;
3815
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlAutocomplete, "ol-autocomplete", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "labelKey": { "alias": "labelKey"; "required": false; "isSignal": true; }; "valueKey": { "alias": "valueKey"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "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; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "errorText": { "alias": "errorText"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "allowCreate": { "alias": "allowCreate"; "required": false; "isSignal": true; }; "emptyText": { "alias": "emptyText"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "menuClass": { "alias": "menuClass"; "required": false; "isSignal": true; }; "matchTriggerWidth": { "alias": "matchTriggerWidth"; "required": false; "isSignal": true; }; "groupBy": { "alias": "groupBy"; "required": false; "isSignal": true; }; "remoteSearch": { "alias": "remoteSearch"; "required": false; "isSignal": true; }; "minTermLength": { "alias": "minTermLength"; "required": false; "isSignal": true; }; "debounceMs": { "alias": "debounceMs"; "required": false; "isSignal": true; }; "typeToSearchText": { "alias": "typeToSearchText"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "createOption": "createOption"; "search": "search"; "opened": "opened"; "closed": "closed"; "cleared": "cleared"; "added": "added"; "removed": "removed"; "selectionChange": "selectionChange"; }, ["optionTemplate"], never, true, never>;
3402
3816
  }
3403
3817
 
3404
3818
  declare class OlKbd {
@@ -3467,6 +3881,16 @@ type OlModalRole = 'dialog' | 'alertdialog';
3467
3881
  declare class OlModal {
3468
3882
  private readonly focusTrap;
3469
3883
  private readonly destroyRef;
3884
+ private readonly overlayContainer;
3885
+ /**
3886
+ * Hàm gỡ khỏi ngăn xếp Escape. `null` khi modal đang đóng.
3887
+ *
3888
+ * Modal KHÔNG gọi `overlayContainer.attach()` — nó nằm trong cây DOM của
3889
+ * chính nó chứ không treo ra `<body>`. Nhưng vẫn phải vào ngăn xếp Escape,
3890
+ * nếu không lớp nổi mở bên trong nó (datepicker, dropdown) sẽ kéo nó đóng
3891
+ * theo khi nhấn Escape.
3892
+ */
3893
+ private releaseEscape;
3470
3894
  private readonly panel;
3471
3895
  /**
3472
3896
  * Bậc thẻ tiêu đề (h1–h6). Mặc định 3 — giữ nguyên bậc đang chạy.
@@ -3478,8 +3902,74 @@ declare class OlModal {
3478
3902
  readonly headingLevel: _angular_core.InputSignal<1 | 2 | 3 | 4 | 5 | 6>;
3479
3903
  readonly isOpen: _angular_core.ModelSignal<boolean>;
3480
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>;
3481
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
+ */
3482
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>;
3483
3973
  readonly backdrop: _angular_core.InputSignal<OlModalBackdrop>;
3484
3974
  readonly placement: _angular_core.InputSignal<ModalPlacement>;
3485
3975
  /**
@@ -3493,14 +3983,13 @@ declare class OlModal {
3493
3983
  constructor();
3494
3984
  readonly placementClasses: _angular_core.Signal<"items-start justify-start p-4" | "items-start justify-center p-4" | "items-start justify-end p-4" | "items-end justify-start p-4" | "items-end justify-center p-4" | "items-end justify-end p-4" | "items-center justify-center p-4">;
3495
3985
  readonly sizeClasses: _angular_core.Signal<"max-w-md" | "max-w-4xl" | "max-w-6xl" | "max-w-7xl" | "max-w-2xl">;
3496
- handleEscapeKey(): void;
3497
3986
  onBackdropClick(event: MouseEvent): void;
3498
3987
  show(): void;
3499
3988
  hide(): void;
3500
3989
  toggle(): void;
3501
3990
  isVisible(): boolean;
3502
3991
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlModal, never>;
3503
- 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>;
3504
3993
  }
3505
3994
 
3506
3995
  /**
@@ -3518,11 +4007,44 @@ declare class OlModalHeader {
3518
4007
  */
3519
4008
  readonly headingLevel: _angular_core.InputSignal<1 | 2 | 3 | 4 | 5 | 6>;
3520
4009
  readonly heading: _angular_core.InputSignal<string>;
4010
+ /**
4011
+ * Icon đầu tiêu đề. Rỗng thì KHÔNG dựng khung — không để lại thẻ trống.
4012
+ *
4013
+ * Figma `Type=Icon + Heading & helper text` (#1149:71412) vẽ khung 48px bo
4014
+ * 12px, icon 28px bên trong. Đó đúng là `ol-icon-shape` size `lg`, nên dùng
4015
+ * lại thay vì tự vẽ khung.
4016
+ *
4017
+ * `color="white"` chứ KHÔNG `gray`: Figma bind nền
4018
+ * `bg-neutral-primary-medium` (trắng) kèm viền `border-default-medium`, mà
4019
+ * trong `ol-icon-shape` chỉ nhánh `white` có cặp đó — `gray` ra nền xám đặc
4020
+ * và KHÔNG viền. Đo /modal lượt đầu: nền rgb(243,244,246), viền 0px.
4021
+ */
4022
+ readonly icon: _angular_core.InputSignal<string>;
4023
+ /**
4024
+ * Mô tả phụ dưới tiêu đề.
4025
+ *
4026
+ * Tên `description` theo `ol-alert-dialog` — thứ dựng trên chính `ol-modal`
4027
+ * — và `ol-empty`. `helperText` mang nghĩa "chú thích dưới ô nhập" ở họ form
4028
+ * (16 component dùng), đặt cho tiêu đề hộp thoại là lệch nghĩa.
4029
+ */
4030
+ readonly description: _angular_core.InputSignal<string>;
4031
+ /** @deprecated Dùng `description`. Giữ để không phá mã đang chạy. */
3521
4032
  readonly supportingText: _angular_core.InputSignal<string>;
4033
+ /** @deprecated Dùng `description`. Tên cũ của `ol-drawer-header`. */
4034
+ readonly helperText: _angular_core.InputSignal<string>;
4035
+ /**
4036
+ * Cái nào được TRUYỀN thì thắng — không phụ thuộc giá trị.
4037
+ *
4038
+ * `ol-badge` từng hoà giải hai input cùng nghĩa bằng cách so với giá trị mặc
4039
+ * định (`theme` thắng trừ khi nó bằng `'gray'`), nên truyền cả hai thì một
4040
+ * cái bị nuốt im lặng. Ở đây chuỗi rỗng là "không truyền", nên chỉ cần lấy
4041
+ * cái đầu tiên khác rỗng.
4042
+ */
4043
+ readonly descriptionText: _angular_core.Signal<string>;
3522
4044
  readonly showClose: _angular_core.InputSignal<boolean>;
3523
4045
  readonly closeClick: _angular_core.OutputEmitterRef<void>;
3524
4046
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlModalHeader, never>;
3525
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlModalHeader, "ol-modal-header", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "supportingText": { "alias": "supportingText"; "required": false; "isSignal": true; }; "showClose": { "alias": "showClose"; "required": false; "isSignal": true; }; }, { "closeClick": "closeClick"; }, never, ["*"], true, never>;
4047
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlModalHeader, "ol-modal-header", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "supportingText": { "alias": "supportingText"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "showClose": { "alias": "showClose"; "required": false; "isSignal": true; }; }, { "closeClick": "closeClick"; }, never, ["*"], true, never>;
3526
4048
  }
3527
4049
 
3528
4050
  /** Khớp trục `Type` của Figma ol-modal-footer (node 1149:71444). */
@@ -3533,9 +4055,15 @@ type ModalFooterLayout = 'buttons' | 'three-buttons' | 'buttons-links' | 'pagina
3533
4055
  * Nút thật do nơi dùng truyền vào qua <ng-content>; component lo bố cục.
3534
4056
  *
3535
4057
  * KHÔNG lắp vào slot `[footer]` của `ol-modal`. Slot đó ĐÃ nằm sẵn trong một
3536
- * khung `p-5 border-t bg-neutral-secondary` do chính `ol-modal` dựng, nên lắp
3537
- * thêm component này vào là LỒNG HAI KHUNG. Đo /modal (2026-09-16): đệm cộng
3538
- * dồn 20 + 20 = 40px, hai viền trên 1px chồng nhau, nền lặp hai lần.
4058
+ * khung do chính `ol-modal` dựng, nên lắp thêm component này vào là LỒNG HAI
4059
+ * KHUNG. Đo /modal (2026-09-16, khi cả hai còn `p-5`): đệm cộng dồn 20 + 20 =
4060
+ * 40px, hai viền trên chồng nhau, nền lặp hai lần.
4061
+ *
4062
+ * Từ 2026-09-23 cả hai khung đều PHẲNG (không viền, không nền) nên lỗi lồng
4063
+ * khung KHÓ THẤY hơn hẳn — chỉ còn đệm cộng dồn 12 + 12 = 24px. Vì vậy cả hai
4064
+ * khung mang thuộc tính dấu `footer-frame`, và spec đếm số thẻ mang dấu đó
4065
+ * phải bằng 1. Neo vào dấu chứ không vào `p-3`: đổi con số đệm là việc thiết
4066
+ * kế bình thường, không nên làm đỏ một spec vốn gác chuyện khác.
3539
4067
  *
3540
4068
  * Dùng nó khi tự dựng hộp thoại (không qua `ol-modal`), hoặc trong `ol-drawer`
3541
4069
  * — `ol-drawer` chỉ có `<ng-content />` TRẦN nên ghép vừa khít. Đó là lý do
@@ -3595,10 +4123,11 @@ declare class OlNavDropdown {
3595
4123
  readonly alignRight: _angular_core.InputSignal<boolean>;
3596
4124
  readonly isOpen: _angular_core.WritableSignal<boolean>;
3597
4125
  private readonly elementRef;
4126
+ private readonly overlayContainer;
4127
+ constructor();
3598
4128
  toggle(): void;
3599
4129
  close(): void;
3600
4130
  handleClickOutside(event: MouseEvent): void;
3601
- handleEscape(): void;
3602
4131
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlNavDropdown, never>;
3603
4132
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlNavDropdown, "ol-nav-dropdown", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "alignRight": { "alias": "alignRight"; "required": false; "isSignal": true; }; }, {}, never, ["[slot=trigger], [trigger]", "*"], true, never>;
3604
4133
  }
@@ -4074,17 +4603,218 @@ declare class OlRating {
4074
4603
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlRating, "ol-rating", never, { "rating": { "alias": "rating"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "score": { "alias": "score"; "required": false; "isSignal": true; }; "badgeText": { "alias": "badgeText"; "required": false; "isSignal": true; }; "minLabel": { "alias": "minLabel"; "required": false; "isSignal": true; }; "maxLabel": { "alias": "maxLabel"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; }, never, never, true, never>;
4075
4604
  }
4076
4605
 
4606
+ /**
4607
+ * Kiểu dữ liệu cho `ol-app-nav` — menu điều hướng Ôn Luyện.
4608
+ *
4609
+ * Tách riêng khỏi component để nơi dùng khai được danh sách menu ở bất kỳ đâu:
4610
+ * hằng số trong mã, tệp JSON, hoặc trả về từ API theo quyền tài khoản. Component
4611
+ * KHÔNG nhúng sẵn danh sách nào — ba quyền Học sinh · Giáo viên · Phụ huynh đều
4612
+ * là dữ liệu truyền vào.
4613
+ *
4614
+ * Figma `ol-app-nav` (#5303:30109) mô tả ba quyền, nhưng đó là NỘI DUNG mẫu,
4615
+ * không phải ràng buộc của component. Thêm quyền thứ tư chỉ là thêm một mảng.
4616
+ */
4617
+ /** Một mục trong menu. */
4618
+ interface AppNavItem {
4619
+ /**
4620
+ * Khoá định danh, dùng để đánh dấu mục đang chọn.
4621
+ *
4622
+ * Tách khỏi `route` vì một mục có thể không có đường dẫn (mở hộp thoại, gọi
4623
+ * hàm), và hai mục có thể trỏ cùng route với tham số khác nhau.
4624
+ */
4625
+ id: string;
4626
+ /** Chữ hiển thị. Ở trạng thái thu gọn, chữ này thành tên trợ năng. */
4627
+ label: string;
4628
+ /**
4629
+ * Nhãn rút gọn cho chỗ hẹp — navbar đáy trên mobile.
4630
+ *
4631
+ * Đo ở 375px: navbar 5 ô, mỗi ô 75px. Nhãn dài hơn ~10 ký tự bị cắt cụt
4632
+ * ("Lịch học trực tuyến" cần 95px), và 8/15 nhãn của ba quyền mẫu vượt
4633
+ * ngưỡng đó — tức không phải ca hiếm.
4634
+ *
4635
+ * Rút tự động bằng thuật toán ra chữ vô nghĩa, nên để nơi dùng tự đặt.
4636
+ * Bỏ trống thì dùng `label`; chỗ hẹp vẫn nên đặt cho chắc.
4637
+ *
4638
+ * KHÔNG thay `label` ở tooltip và trợ năng — hai chỗ đó vẫn đọc tên đầy đủ.
4639
+ */
4640
+ shortLabel?: string;
4641
+ /** Tên icon theo `OlIconService` — dạng ngắn (`star`) hoặc có tiền tố bộ. */
4642
+ icon: string;
4643
+ /** Đường dẫn router. Bỏ trống thì mục chỉ phát sự kiện `itemClick`. */
4644
+ route?: string;
4645
+ /** Huy hiệu đếm bên phải nhãn, ví dụ số tin chưa đọc. */
4646
+ badge?: string | number;
4647
+ /** Mờ 40% và không bấm được — theo Figma State=Disabled. */
4648
+ disabled?: boolean;
4649
+ }
4650
+ /**
4651
+ * Một nhóm mục. Figma chia menu làm hai phần, ngăn bằng một đường kẻ:
4652
+ *
4653
+ * `main` phần trên — nghiệp vụ chính, chiếm hết chỗ trống
4654
+ * `footer` phần dưới — tiện ích, luôn nằm sát thẻ tài khoản
4655
+ *
4656
+ * Dùng `section` chứ không dựa vào thứ tự mảng: nơi dùng khai một danh sách
4657
+ * phẳng, component tự tách hai vùng.
4658
+ */
4659
+ interface AppNavGroup {
4660
+ section: 'main' | 'footer';
4661
+ items: AppNavItem[];
4662
+ }
4663
+ /** Thẻ tài khoản dưới cùng. */
4664
+ interface AppNavUser {
4665
+ name: string;
4666
+ /** Dòng phụ — Figma dùng email, nhưng nơi dùng đặt gì cũng được. */
4667
+ subtitle?: string;
4668
+ /** Ảnh đại diện. Bỏ trống thì `ol-avatar` tự dựng chữ cái đầu từ `name`. */
4669
+ avatarSrc?: string;
4670
+ }
4671
+ /**
4672
+ * Trọn bộ dữ liệu một menu — đúng thứ nơi dùng nạp từ JSON.
4673
+ *
4674
+ * ```ts
4675
+ * const menuHocSinh: AppNavConfig = {
4676
+ * groups: [
4677
+ * { section: 'main', items: [{ id: 'lop-hoc-so', label: 'Lớp học số', icon: 'calendar-add' }] },
4678
+ * { section: 'footer', items: [{ id: 'ho-tro', label: 'Hướng dẫn & Hỗ trợ', icon: 'question-circle' }] },
4679
+ * ],
4680
+ * user: { name: 'Nguyễn Nhật Linh', subtitle: 'hocsinh.demo@onluyen.vn' },
4681
+ * };
4682
+ * ```
4683
+ */
4684
+ interface AppNavConfig {
4685
+ groups: AppNavGroup[];
4686
+ user?: AppNavUser;
4687
+ }
4688
+
4689
+ /**
4690
+ * `ol-app-nav` — menu điều hướng dọc của Ôn Luyện.
4691
+ *
4692
+ * Figma `ol-app-nav` (#5303:30109), 6 biến thể: 3 quyền × 2 trạng thái.
4693
+ *
4694
+ * ## Khác gì `ol-sidebar`
4695
+ *
4696
+ * `ol-sidebar` là thanh bên ĐA DỤNG: nền sáng theo token, nhận nội dung tuỳ ý
4697
+ * qua `ng-content`, có menu con accordion. Component này là thanh điều hướng
4698
+ * RIÊNG của sản phẩm Ôn Luyện: nền gradient navy cố định, cấu trúc chốt sẵn
4699
+ * (logo → menu chính → đường kẻ → menu phụ → thẻ tài khoản), nhận dữ liệu qua
4700
+ * một đối tượng thay vì qua slot.
4701
+ *
4702
+ * Không gộp hai cái làm một: gộp thì `ol-sidebar` phải mang theo nền gradient
4703
+ * và bố cục ba vùng mà 90% nơi dùng không cần.
4704
+ *
4705
+ * ## Dữ liệu từ ngoài, không nhúng cứng
4706
+ *
4707
+ * Ba quyền trong Figma là NỘI DUNG mẫu. Component nhận `config` — nơi dùng nạp
4708
+ * từ hằng số, tệp JSON, hay API đều được. Thêm quyền thứ tư chỉ là thêm một
4709
+ * mảng, không phải sửa component.
4710
+ *
4711
+ * ## Nền gradient nằm ngoài thang token
4712
+ *
4713
+ * Figma chốt `linear-gradient(164.52deg, #445A9A, #0F2977)`. Hai màu này không
4714
+ * có trong `primitives.css` và cũng không nên có — chúng là bản sắc của MỘT
4715
+ * sản phẩm, không phải bậc màu dùng chung. Khai biến cục bộ trong template.
4716
+ *
4717
+ * Hệ quả: chữ trên nền này luôn trắng, không đổi theo chế độ sáng/tối. Đúng
4718
+ * như Figma mô tả ("dùng trên nền tối, chữ và biểu tượng đều `text-white`").
4719
+ */
4720
+ declare class OlAppNav {
4721
+ /** Danh sách menu và thẻ tài khoản. Xem `AppNavConfig`. */
4722
+ readonly config: _angular_core.InputSignal<AppNavConfig>;
4723
+ /**
4724
+ * Thu gọn còn 70px, chỉ hiện icon.
4725
+ *
4726
+ * `model()` để nơi dùng bind hai chiều `[(collapsed)]` — nút thu gọn thường
4727
+ * nằm ngoài component (trên thanh header), nhưng cũng có nơi tự điều khiển.
4728
+ */
4729
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
4730
+ /**
4731
+ * Mục đang chọn, theo `id`.
4732
+ *
4733
+ * Dùng khi menu KHÔNG đi qua router — ví dụ một trang đơn đổi nội dung bằng
4734
+ * signal. Có `route` thì `routerLinkActive` tự lo, không cần đặt cái này.
4735
+ */
4736
+ readonly activeId: _angular_core.InputSignal<string | null>;
4737
+ /** Ảnh logo. Bỏ trống thì dựng chữ "Ôn luyện" bằng text. */
4738
+ readonly logoSrc: _angular_core.InputSignal<string>;
4739
+ /** Chiều rộng khi mở. Figma: 270px. */
4740
+ readonly width: _angular_core.InputSignal<string>;
4741
+ /** Chiều rộng khi thu gọn. Figma: 70px. */
4742
+ readonly collapsedWidth: _angular_core.InputSignal<string>;
4743
+ /** Phát khi bấm một mục. Mục có `route` thì router đã lo điều hướng. */
4744
+ readonly itemClick: _angular_core.OutputEmitterRef<AppNavItem>;
4745
+ /** Phát khi bấm thẻ tài khoản — nơi dùng mở menu đổi tài khoản, đăng xuất. */
4746
+ readonly userClick: _angular_core.OutputEmitterRef<void>;
4747
+ /**
4748
+ * Hiện thanh kéo (rail) ở mép phải để thu gọn / mở lại.
4749
+ *
4750
+ * Tắt mặc định vì nó chỉ hợp khi menu đứng cạnh nội dung trong một layout
4751
+ * thật. Dùng menu rời (như trang docs) thì thanh này lơ lửng vô nghĩa.
4752
+ */
4753
+ readonly showRail: _angular_core.InputSignal<boolean>;
4754
+ /** Chữ trên tooltip của thanh kéo, đổi theo trạng thái. */
4755
+ readonly railLabel: _angular_core.Signal<"Mở rộng" | "Thu gọn">;
4756
+ /** Mục của phần trên. */
4757
+ readonly mainItems: _angular_core.Signal<AppNavItem[]>;
4758
+ /** Mục của phần dưới, nằm sát thẻ tài khoản. */
4759
+ readonly footerItems: _angular_core.Signal<AppNavItem[]>;
4760
+ /** Đường kẻ chỉ vẽ khi phần dưới có mục — không thì thừa một vạch. */
4761
+ readonly showDivider: _angular_core.Signal<boolean>;
4762
+ readonly navWidth: _angular_core.Signal<string>;
4763
+ /**
4764
+ * Class cho một mục, theo trạng thái.
4765
+ *
4766
+ * Figma `ol-app-sidebar-item` (#5301:338): cao 44, bo 12, đệm 10, khe 8.
4767
+ *
4768
+ * Initial trong suốt
4769
+ * Hover nền brand/600
4770
+ * Active nền bg-warning, chữ đậm
4771
+ * Disabled mờ 40%
4772
+ *
4773
+ * Gộp vào MỘT chuỗi thay vì nhiều `[class.x]`: hai binding cùng tên thuộc
4774
+ * tính thì cái sau thắng, cái trước mất im lặng.
4775
+ */
4776
+ itemClasses(item: AppNavItem): string;
4777
+ /**
4778
+ * Chữ cái đầu cho ảnh đại diện, khi không có ảnh.
4779
+ *
4780
+ * `ol-avatar` IN NGUYÊN chuỗi truyền vào `text` chứ không tự cắt — truyền cả
4781
+ * họ tên thì nó hiện trọn tên trong vòng tròn 40px. Lấy chữ cái đầu của HAI
4782
+ * từ CUỐI: tên Việt để họ trước, "Nguyễn Nhật Linh" phải ra "NL" chứ không
4783
+ * phải "NN".
4784
+ */
4785
+ readonly userInitials: _angular_core.Signal<string>;
4786
+ onItemClick(item: AppNavItem): void;
4787
+ toggle(): void;
4788
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlAppNav, never>;
4789
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlAppNav, "ol-app-nav", never, { "config": { "alias": "config"; "required": true; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "activeId": { "alias": "activeId"; "required": false; "isSignal": true; }; "logoSrc": { "alias": "logoSrc"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "collapsedWidth": { "alias": "collapsedWidth"; "required": false; "isSignal": true; }; "showRail": { "alias": "showRail"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "itemClick": "itemClick"; "userClick": "userClick"; }, never, never, true, never>;
4790
+ }
4791
+
4077
4792
  declare class OlSidebar {
4078
4793
  readonly collapsed: _angular_core.ModelSignal<boolean>;
4079
4794
  readonly width: _angular_core.InputSignal<string>;
4080
4795
  readonly collapsedWidth: _angular_core.InputSignal<string>;
4081
4796
  readonly collapsible: _angular_core.InputSignal<boolean>;
4082
4797
  readonly bordered: _angular_core.InputSignal<boolean>;
4798
+ /**
4799
+ * Hiện thanh kéo ở mép phải để thu gọn / mở rộng.
4800
+ *
4801
+ * Mặc định TẮT: `toggle()` vốn để nơi dùng tự dựng nút bên ngoài, bật sẵn
4802
+ * sẽ thêm một nút vào mọi sidebar đang chạy mà không ai yêu cầu.
4803
+ *
4804
+ * Thanh này ẩn dưới `md` — ở màn hẹp sidebar thường được thay bằng ngăn kéo
4805
+ * trượt, và một vạch 8px cạnh mép màn hình rất khó trúng bằng ngón tay.
4806
+ */
4807
+ readonly showRail: _angular_core.InputSignal<boolean>;
4808
+ /**
4809
+ * Chữ trên tooltip của thanh kéo — theo trạng thái SẼ TỚI, không phải trạng
4810
+ * thái đang đứng. Người dùng đọc nhãn để biết bấm vào thì được gì.
4811
+ */
4812
+ readonly railLabel: _angular_core.Signal<"Mở rộng" | "Thu gọn">;
4083
4813
  readonly currentWidth: _angular_core.Signal<string>;
4084
4814
  readonly sidebarClasses: _angular_core.Signal<string>;
4085
4815
  toggle(): void;
4086
4816
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSidebar, never>;
4087
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSidebar, "ol-sidebar", never, { "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "collapsedWidth": { "alias": "collapsedWidth"; "required": false; "isSignal": true; }; "collapsible": { "alias": "collapsible"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; }, never, ["[slot=header], [header]", "*", "[slot=footer], [footer]"], true, never>;
4817
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSidebar, "ol-sidebar", never, { "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; "collapsedWidth": { "alias": "collapsedWidth"; "required": false; "isSignal": true; }; "collapsible": { "alias": "collapsible"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "showRail": { "alias": "showRail"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; }, never, ["[slot=header], [header]", "*", "[slot=footer], [footer]"], true, never>;
4088
4818
  }
4089
4819
 
4090
4820
  declare class OlSidebarItem {
@@ -4096,7 +4826,7 @@ declare class OlSidebarItem {
4096
4826
  readonly href: _angular_core.InputSignal<string | null>;
4097
4827
  readonly icon: _angular_core.InputSignal<string | null>;
4098
4828
  readonly badge: _angular_core.InputSignal<string | number | null>;
4099
- readonly badgeTheme: _angular_core.InputSignal<BadgeTheme>;
4829
+ readonly badgeColor: _angular_core.InputSignal<BadgeColor>;
4100
4830
  readonly active: _angular_core.InputSignal<boolean>;
4101
4831
  readonly disabled: _angular_core.InputSignal<boolean>;
4102
4832
  readonly dot: _angular_core.InputSignal<boolean>;
@@ -4106,7 +4836,7 @@ declare class OlSidebarItem {
4106
4836
  */
4107
4837
  readonly itemClasses: _angular_core.Signal<string>;
4108
4838
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSidebarItem, never>;
4109
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSidebarItem, "ol-sidebar-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "routerLinkActive": { "alias": "routerLinkActive"; "required": false; "isSignal": true; }; "routerLinkActiveOptions": { "alias": "routerLinkActiveOptions"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeTheme": { "alias": "badgeTheme"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
4839
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSidebarItem, "ol-sidebar-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "routerLinkActive": { "alias": "routerLinkActive"; "required": false; "isSignal": true; }; "routerLinkActiveOptions": { "alias": "routerLinkActiveOptions"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeColor": { "alias": "badgeColor"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "dot": { "alias": "dot"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
4110
4840
  }
4111
4841
 
4112
4842
  declare class OlSidebarGroup {
@@ -4119,7 +4849,7 @@ declare class OlSidebarSubmenu {
4119
4849
  readonly label: _angular_core.InputSignal<string>;
4120
4850
  readonly icon: _angular_core.InputSignal<string | null>;
4121
4851
  readonly badge: _angular_core.InputSignal<string | number | null>;
4122
- readonly badgeTheme: _angular_core.InputSignal<BadgeTheme>;
4852
+ readonly badgeColor: _angular_core.InputSignal<BadgeColor>;
4123
4853
  readonly isOpen: _angular_core.ModelSignal<boolean>;
4124
4854
  readonly active: _angular_core.InputSignal<boolean>;
4125
4855
  /**
@@ -4129,7 +4859,7 @@ declare class OlSidebarSubmenu {
4129
4859
  readonly triggerClasses: _angular_core.Signal<string>;
4130
4860
  toggle(): void;
4131
4861
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSidebarSubmenu, never>;
4132
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSidebarSubmenu, "ol-sidebar-submenu", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeTheme": { "alias": "badgeTheme"; "required": false; "isSignal": true; }; "isOpen": { "alias": "isOpen"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, { "isOpen": "isOpenChange"; }, never, ["*"], true, never>;
4862
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSidebarSubmenu, "ol-sidebar-submenu", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeColor": { "alias": "badgeColor"; "required": false; "isSignal": true; }; "isOpen": { "alias": "isOpen"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, { "isOpen": "isOpenChange"; }, never, ["*"], true, never>;
4133
4863
  }
4134
4864
 
4135
4865
  /**
@@ -4233,6 +4963,10 @@ interface SpeedDialAction {
4233
4963
 
4234
4964
  declare class OlSpeedDial {
4235
4965
  private readonly elementRef;
4966
+ private readonly overlayContainer;
4967
+ /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi đang đóng. */
4968
+ private releaseEscape;
4969
+ constructor();
4236
4970
  readonly direction: _angular_core.InputSignal<SpeedDialDirection>;
4237
4971
  readonly type: _angular_core.InputSignal<SpeedDialType>;
4238
4972
  readonly trigger: _angular_core.InputSignal<SpeedDialTrigger>;
@@ -4263,7 +4997,6 @@ declare class OlSpeedDial {
4263
4997
  onMouseLeave(): void;
4264
4998
  onBackdropMouseEnter(): void;
4265
4999
  onDocumentClick(event: MouseEvent): void;
4266
- onEscape(): void;
4267
5000
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSpeedDial, never>;
4268
5001
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSpeedDial, "ol-speed-dial", never, { "direction": { "alias": "direction"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "trigger": { "alias": "trigger"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "tooltip": { "alias": "tooltip"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "backdrop": { "alias": "backdrop"; "required": false; "isSignal": true; }; "closeOnClickOutside": { "alias": "closeOnClickOutside"; "required": false; "isSignal": true; }; "closeOnEscape": { "alias": "closeOnEscape"; "required": false; "isSignal": true; }; "actions": { "alias": "actions"; "required": false; "isSignal": true; }; "customClass": { "alias": "class"; "required": false; "isSignal": true; }; "isOpen": { "alias": "isOpen"; "required": false; "isSignal": true; }; }, { "isOpen": "isOpenChange"; "onOpen": "onOpen"; "onClose": "onClose"; "actionClick": "actionClick"; }, never, ["*"], true, never>;
4269
5002
  }
@@ -4469,7 +5202,7 @@ declare class OlTabItem {
4469
5202
  readonly icon: _angular_core.InputSignal<string | null>;
4470
5203
  readonly avatar: _angular_core.InputSignal<string | null>;
4471
5204
  readonly badge: _angular_core.InputSignal<string | number | null>;
4472
- readonly badgeTheme: _angular_core.InputSignal<BadgeTheme>;
5205
+ readonly badgeColor: _angular_core.InputSignal<BadgeColor>;
4473
5206
  readonly disabled: _angular_core.InputSignal<boolean>;
4474
5207
  readonly displayLabel: _angular_core.Signal<string>;
4475
5208
  readonly displaySubtitle: _angular_core.Signal<string>;
@@ -4494,7 +5227,7 @@ declare class OlTabItem {
4494
5227
  */
4495
5228
  readonly hasTabRole: _angular_core.Signal<boolean>;
4496
5229
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTabItem, never>;
4497
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTabItem, "ol-tab-item", never, { "tabId": { "alias": "tabId"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "helper": { "alias": "helper"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "avatar": { "alias": "avatar"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeTheme": { "alias": "badgeTheme"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
5230
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTabItem, "ol-tab-item", never, { "tabId": { "alias": "tabId"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "helper": { "alias": "helper"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "avatar": { "alias": "avatar"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeColor": { "alias": "badgeColor"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
4498
5231
  }
4499
5232
 
4500
5233
  /**
@@ -4869,12 +5602,12 @@ declare class OlBottomNavItem {
4869
5602
  readonly icon: _angular_core.InputSignal<string>;
4870
5603
  readonly label: _angular_core.InputSignal<string>;
4871
5604
  readonly badge: _angular_core.InputSignal<string | number | null>;
4872
- readonly badgeTheme: _angular_core.InputSignal<BadgeTheme>;
5605
+ readonly badgeColor: _angular_core.InputSignal<BadgeColor>;
4873
5606
  readonly isCenterAction: _angular_core.InputSignal<boolean>;
4874
5607
  readonly active: _angular_core.InputSignal<boolean>;
4875
5608
  readonly itemClasses: _angular_core.Signal<string>;
4876
5609
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlBottomNavItem, never>;
4877
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBottomNavItem, "ol-bottom-nav-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "routerLinkActive": { "alias": "routerLinkActive"; "required": false; "isSignal": true; }; "routerLinkActiveOptions": { "alias": "routerLinkActiveOptions"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeTheme": { "alias": "badgeTheme"; "required": false; "isSignal": true; }; "isCenterAction": { "alias": "isCenterAction"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
5610
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlBottomNavItem, "ol-bottom-nav-item", never, { "routerLink": { "alias": "routerLink"; "required": false; "isSignal": true; }; "routerLinkActive": { "alias": "routerLinkActive"; "required": false; "isSignal": true; }; "routerLinkActiveOptions": { "alias": "routerLinkActiveOptions"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; "badgeColor": { "alias": "badgeColor"; "required": false; "isSignal": true; }; "isCenterAction": { "alias": "isCenterAction"; "required": false; "isSignal": true; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
4878
5611
  }
4879
5612
 
4880
5613
  type StepState = 'initial' | 'active' | 'completed' | 'error' | 'disabled';
@@ -4938,7 +5671,7 @@ interface OlTreeNode {
4938
5671
  icon?: string;
4939
5672
  count?: number;
4940
5673
  badge?: string;
4941
- badgeTheme?: 'brand' | 'green' | 'yellow' | 'red' | 'gray';
5674
+ badgeColor?: 'brand' | 'green' | 'yellow' | 'red' | 'gray';
4942
5675
  children?: OlTreeNode[];
4943
5676
  expanded?: boolean;
4944
5677
  selected?: boolean;
@@ -4966,6 +5699,25 @@ declare class OlTree {
4966
5699
  * được truyền xuống MỌI cấp của cây đệ quy.
4967
5700
  */
4968
5701
  nodeTemplate?: TemplateRef<unknown>;
5702
+ /**
5703
+ * Nút/hành động gắn ở CUỐI mỗi hàng, khai bằng `#actionsTemplate`:
5704
+ *
5705
+ * ```html
5706
+ * <ol-tree [nodes]="nodes">
5707
+ * <ng-template #actionsTemplate let-node>
5708
+ * <ol-button size="xs" color="ghost" (btnClick)="sua(node)">Sửa</ol-button>
5709
+ * </ng-template>
5710
+ * </ol-tree>
5711
+ * ```
5712
+ *
5713
+ * Khác `#nodeTemplate` ở chỗ nó **cộng thêm** chứ không thay thế: icon, nhãn,
5714
+ * count và badge mặc định vẫn nguyên. `#nodeTemplate` là all-or-nothing —
5715
+ * bật lên thì phải tự vẽ lại cả bốn thứ đó, nên chỉ muốn thêm một nút ở cuối
5716
+ * mà phải dùng nó là chép thừa.
5717
+ *
5718
+ * Dùng được ĐỒNG THỜI với `#nodeTemplate`: nút vẫn hiện ở cả hai nhánh.
5719
+ */
5720
+ actionsTemplate?: TemplateRef<unknown>;
4969
5721
  readonly checkable: _angular_core.InputSignal<boolean>;
4970
5722
  readonly selectionMode: _angular_core.InputSignal<OlTreeSelectionMode>;
4971
5723
  readonly bordered: _angular_core.InputSignal<boolean>;
@@ -5012,7 +5764,7 @@ declare class OlTree {
5012
5764
  private setSelectedNodeSingle;
5013
5765
  private cloneTree;
5014
5766
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTree, never>;
5015
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTree, "ol-tree", never, { "nodes": { "alias": "nodes"; "required": false; "isSignal": true; }; "checkable": { "alias": "checkable"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "toggleIcon": { "alias": "toggleIcon"; "required": false; "isSignal": true; }; }, { "nodes": "nodesChange"; "nodeSelect": "nodeSelect"; "nodeCheck": "nodeCheck"; }, ["nodeTemplate"], never, true, never>;
5767
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTree, "ol-tree", never, { "nodes": { "alias": "nodes"; "required": false; "isSignal": true; }; "checkable": { "alias": "checkable"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "toggleIcon": { "alias": "toggleIcon"; "required": false; "isSignal": true; }; }, { "nodes": "nodesChange"; "nodeSelect": "nodeSelect"; "nodeCheck": "nodeCheck"; }, ["nodeTemplate", "actionsTemplate"], never, true, never>;
5016
5768
  }
5017
5769
 
5018
5770
  type OlTreeNodeSelectionMode = 'single' | 'multiple';
@@ -5032,6 +5784,13 @@ declare class OlTreeNodeItem {
5032
5784
  * BUG-017 — họ không có cách nào biết phải khai lại những thứ đó.
5033
5785
  */
5034
5786
  readonly nodeTemplate: _angular_core.InputSignal<TemplateRef<unknown> | null>;
5787
+ /**
5788
+ * Template cho phần CUỐI hàng — nút, menu, hành động.
5789
+ *
5790
+ * Cộng thêm chứ không thay thế: dựng NGOÀI cặp `@if (nodeTemplate())` nên
5791
+ * hiện ở cả hai nhánh, dùng chung được với `nodeTemplate`.
5792
+ */
5793
+ readonly actionsTemplate: _angular_core.InputSignal<TemplateRef<unknown> | null>;
5035
5794
  readonly checkable: _angular_core.InputSignal<boolean>;
5036
5795
  readonly selectionMode: _angular_core.InputSignal<OlTreeNodeSelectionMode>;
5037
5796
  readonly toggleIcon: _angular_core.InputSignal<OlTreeNodeToggleIcon>;
@@ -5055,6 +5814,33 @@ declare class OlTreeNodeItem {
5055
5814
  protected readonly ariaLevel: _angular_core.Signal<number>;
5056
5815
  /** `aria-expanded` CHỈ đặt cho node có con; node lá để `null`. */
5057
5816
  protected readonly ariaExpanded: _angular_core.Signal<boolean | null>;
5817
+ /**
5818
+ * Ngữ cảnh cho CẢ HAI template. Một `computed` dùng chung chứ không viết
5819
+ * object thẳng trong template: viết thẳng thì mỗi lần dò thay đổi lại sinh
5820
+ * một object mới, `ngTemplateOutlet` thấy khác tham chiếu nên dựng lại nội
5821
+ * dung — mất cả trạng thái bên trong nút người dùng đặt vào.
5822
+ *
5823
+ * `$implicit` cho `let-node`; `node` cho `let-n="node"`. Ba khoá còn lại bắt
5824
+ * kịp `ol-autocomplete` (đã có `selected`) và mở thêm `level`/`expanded` —
5825
+ * hàng cây thường cần biết mình sâu bao nhiêu để thụt chữ hay đổi cỡ.
5826
+ */
5827
+ protected readonly tplContext: _angular_core.Signal<{
5828
+ $implicit: OlTreeNode;
5829
+ node: OlTreeNode;
5830
+ level: number;
5831
+ selected: boolean;
5832
+ expanded: boolean;
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;
5058
5844
  constructor();
5059
5845
  /**
5060
5846
  * Nút cha có con chọn một phần thì ô của nó phải là dấu gạch.
@@ -5075,7 +5861,7 @@ declare class OlTreeNodeItem {
5075
5861
  handleClick(): void;
5076
5862
  handleCheck(checked: boolean): void;
5077
5863
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTreeNodeItem, never>;
5078
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTreeNodeItem, "ol-tree-node", never, { "node": { "alias": "node"; "required": true; "isSignal": true; }; "nodeTemplate": { "alias": "nodeTemplate"; "required": false; "isSignal": true; }; "checkable": { "alias": "checkable"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "toggleIcon": { "alias": "toggleIcon"; "required": false; "isSignal": true; }; "level": { "alias": "level"; "required": false; "isSignal": true; }; "tabbableId": { "alias": "tabbableId"; "required": false; "isSignal": true; }; "focusedId": { "alias": "focusedId"; "required": false; "isSignal": true; }; }, { "nodeClick": "nodeClick"; "nodeCheck": "nodeCheck"; "toggleExpand": "toggleExpand"; "nodeFocus": "nodeFocus"; }, never, never, true, never>;
5864
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTreeNodeItem, "ol-tree-node", never, { "node": { "alias": "node"; "required": true; "isSignal": true; }; "nodeTemplate": { "alias": "nodeTemplate"; "required": false; "isSignal": true; }; "actionsTemplate": { "alias": "actionsTemplate"; "required": false; "isSignal": true; }; "checkable": { "alias": "checkable"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "toggleIcon": { "alias": "toggleIcon"; "required": false; "isSignal": true; }; "level": { "alias": "level"; "required": false; "isSignal": true; }; "tabbableId": { "alias": "tabbableId"; "required": false; "isSignal": true; }; "focusedId": { "alias": "focusedId"; "required": false; "isSignal": true; }; }, { "nodeClick": "nodeClick"; "nodeCheck": "nodeCheck"; "toggleExpand": "toggleExpand"; "nodeFocus": "nodeFocus"; }, never, never, true, never>;
5079
5865
  }
5080
5866
 
5081
5867
  type ChartType = 'line' | 'smooth-line' | 'area' | 'smooth-area' | 'step-line' | 'bar' | 'stacked-bar' | 'horizontal-bar' | 'stacked-horizontal-bar' | 'diverging-bar' | 'pie' | 'donut' | 'rose' | 'radar' | 'gauge' | 'radial' | 'scatter' | 'heatmap' | 'boxplot';
@@ -5574,84 +6360,6 @@ declare class OlJumbotron {
5574
6360
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlJumbotron, "ol-jumbotron", never, { "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "subtitle": { "alias": "subtitle"; "required": false; "isSignal": true; }; "badgeText": { "alias": "badgeText"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; }, {}, never, ["[slot=badge-icon]", "[slot=actions]", "*"], true, never>;
5575
6361
  }
5576
6362
 
5577
- /**
5578
- * Abstract base class cho các overlay components (Modal, Drawer, Popover)
5579
- * Cung cấp functionality chung cho việc mở/đóng và xử lý interactions
5580
- */
5581
- declare abstract class OlOverlayBase implements OnDestroy {
5582
- /**
5583
- * Signal tracking trạng thái mở/đóng của overlay
5584
- */
5585
- isOpen: WritableSignal<boolean>;
5586
- /**
5587
- * Signal cho việc hiển thị backdrop
5588
- */
5589
- backdrop: WritableSignal<boolean>;
5590
- /**
5591
- * Signal cho phép đóng overlay khi nhấn Escape
5592
- */
5593
- closeOnEscape: WritableSignal<boolean>;
5594
- /**
5595
- * Signal cho phép đóng overlay khi click vào backdrop
5596
- */
5597
- closeOnBackdrop: WritableSignal<boolean>;
5598
- /**
5599
- * Signal tracking animation state
5600
- */
5601
- protected animating: WritableSignal<boolean>;
5602
- /** Handle của setTimeout animation — lưu lại để huỷ khi destroy, tránh
5603
- * callback chạy trên instance đã chết. */
5604
- private animationTimer;
5605
- constructor();
5606
- /**
5607
- * Mở overlay với optional animation
5608
- */
5609
- open(): void;
5610
- /**
5611
- * Đóng overlay với optional animation
5612
- */
5613
- close(): void;
5614
- /**
5615
- * Toggle trạng thái overlay
5616
- */
5617
- toggle(): void;
5618
- /**
5619
- * Xử lý sự kiện click vào backdrop
5620
- */
5621
- handleBackdropClick(event: MouseEvent): void;
5622
- /**
5623
- * Xử lý sự kiện nhấn phím Escape
5624
- */
5625
- protected handleEscape: (event: KeyboardEvent) => void;
5626
- /**
5627
- * Lifecycle hook được gọi khi overlay mở
5628
- * Override trong subclass để customize behavior
5629
- */
5630
- protected onOpen(): void;
5631
- /**
5632
- * Lifecycle hook được gọi khi overlay đóng
5633
- * Override trong subclass để customize behavior
5634
- */
5635
- protected onClose(): void;
5636
- /**
5637
- * Thêm event listener cho phím Escape
5638
- */
5639
- private addEscapeListener;
5640
- /**
5641
- * Xóa event listener cho phím Escape
5642
- */
5643
- private removeEscapeListener;
5644
- /**
5645
- * Cleanup khi component bị destroy.
5646
- * Gọi tự động qua ngOnDestroy — trước đây hàm này tồn tại nhưng không ai gọi,
5647
- * nên component destroy lúc đang mở sẽ để lại listener keydown trên document.
5648
- */
5649
- protected cleanup(): void;
5650
- ngOnDestroy(): void;
5651
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlOverlayBase, never>;
5652
- static ɵdir: _angular_core.ɵɵDirectiveDeclaration<OlOverlayBase, never, never, {}, {}, never, never, true, never>;
5653
- }
5654
-
5655
6363
  /**
5656
6364
  * Standalone Backdrop Component
5657
6365
  * Hiển thị overlay backdrop với animation fade in/out và dark mode support
@@ -5884,6 +6592,26 @@ declare class OlPositioningService {
5884
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
5885
6593
  * trước.
5886
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
+ }
5887
6615
  declare class OlOverlayContainer {
5888
6616
  private readonly doc;
5889
6617
  private host;
@@ -5923,6 +6651,86 @@ declare class OlOverlayContainer {
5923
6651
  * Trả về hàm hoàn tác — gọi khi lớp đóng để trả phần tử về chỗ cũ.
5924
6652
  */
5925
6653
  attach(el: HTMLElement): () => void;
6654
+ /**
6655
+ * Các lớp nổi ĐANG MỞ, xếp theo thứ tự mở — phần tử cuối là lớp trên cùng.
6656
+ *
6657
+ * Dùng mảng chứ không `Set`: thứ tự chính là thông tin cần, và một lớp không
6658
+ * thể mở hai lần nên không sợ trùng.
6659
+ */
6660
+ private readonly openLayers;
6661
+ /** Trình nghe Escape duy nhất, gắn khi có lớp đầu tiên mở. */
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;
6669
+ /**
6670
+ * Đăng ký một lớp nổi vừa mở. Trả hàm gỡ — gọi khi lớp đóng.
6671
+ *
6672
+ * ## Vì sao cần
6673
+ *
6674
+ * Trước đây mỗi component tự gắn `@HostListener('document:keydown.escape')`.
6675
+ * Không ai chặn lan truyền và không ai biết ai, nên **một phím Escape đóng
6676
+ * MỌI lớp đang mở**: mở datepicker trong modal rồi nhấn Escape thì cả hai
6677
+ * cùng đóng, người dùng mất luôn hộp thoại đang điền dở.
6678
+ *
6679
+ * Ở đây một trình nghe duy nhất, chỉ gọi `close()` của lớp **trên cùng**.
6680
+ * Nhấn lần nữa mới tới lớp dưới — đúng cách người dùng chờ đợi.
6681
+ *
6682
+ * ## Dùng thế nào
6683
+ *
6684
+ * ```ts
6685
+ * private releaseEscape: (() => void) | null = null;
6686
+ *
6687
+ * open(): void {
6688
+ * this.releaseEscape = this.overlayContainer.register(() => this.close());
6689
+ * }
6690
+ *
6691
+ * close(): void {
6692
+ * this.releaseEscape?.();
6693
+ * this.releaseEscape = null;
6694
+ * }
6695
+ * ```
6696
+ *
6697
+ * Hàm gỡ **an toàn khi gọi nhiều lần** — `close()` thường chạy cả từ phím
6698
+ * Escape lẫn từ nút đóng, và cả hai đường đều gọi nó.
6699
+ */
6700
+ register(close: () => void, contains?: (target: EventTarget | null) => boolean): () => void;
6701
+ /**
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.
6703
+ *
6704
+ * Không có lớp nào mở mà vẫn nghe phím là bắt mọi lần nhấn Escape của cả
6705
+ * ứng dụng — kể cả khi người dùng đang gõ trong ô nhập bình thường.
6706
+ */
6707
+ private ensureEscapeListener;
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;
5926
6734
  /**
5927
6735
  * @deprecated Dùng `attach()`. Giữ lại vì đã publish trong
5928
6736
  * `@edmicro/olkit@1.1.1` — bỏ phương thức công khai là breaking change.
@@ -5932,6 +6740,6 @@ declare class OlOverlayContainer {
5932
6740
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
5933
6741
  }
5934
6742
 
5935
- export { CHAT_BUBBLE_STATUS, MONTHS_FULL_EN, MONTHS_FULL_VI, MONTHS_SHORT_VI, OlAccordion, OlAccordionItem, OlAlert, OlAlertDialog, 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, OlOverlayBase, 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 };
5936
- export type { AccordionVariant, AlertColor, AlertDialogTone, AlertLayout, AlertRounded, AlertType, AlertVariant, AppDevice, AppTheme, AutocompleteOption, AutocompleteSize, AutocompleteState, AvatarShape, AvatarSize, AvatarStatus, 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 };
5937
6745
  //# sourceMappingURL=edmicro-olkit.d.ts.map