@edmicro/olkit 1.1.5 → 1.1.7

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.
@@ -131,6 +131,17 @@ declare class OlAlert {
131
131
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlAlert, "ol-alert", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "headingLevel": { "alias": "headingLevel"; "required": false; "isSignal": true; }; "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "dismissible": { "alias": "dismissible"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "badge": { "alias": "badge"; "required": false; "isSignal": true; }; }, { "onDismiss": "onDismiss"; }, never, ["[badge]", "*", "[alertActions]"], true, never>;
132
132
  }
133
133
 
134
+ /**
135
+ * Bỏ dấu tiếng Việt để gõ không dấu vẫn tìm ra.
136
+ *
137
+ * Phải xử lý `đ`/`Đ` riêng: chúng KHÔNG phải `d` cộng dấu phụ nên `NFD` không
138
+ * tách ra được, `đăng nhập` sẽ không khớp `dang nhap` nếu bỏ bước này.
139
+ *
140
+ * Dùng chung cho mọi ô có tìm kiếm (`ol-command`, `ol-select`); `ol-command`
141
+ * export lại để giữ API công khai từ 1.1.1.
142
+ */
143
+ declare function stripDiacritics(text: string): string;
144
+
134
145
  /** Một lệnh trong bảng. `keywords` để tìm được bằng từ khác với chữ hiển thị. */
135
146
  interface CommandItem {
136
147
  id: string;
@@ -145,13 +156,7 @@ interface CommandGroup {
145
156
  label: string;
146
157
  items: CommandItem[];
147
158
  }
148
- /**
149
- * Bỏ dấu tiếng Việt để gõ không dấu vẫn tìm ra.
150
- *
151
- * Phải xử lý `đ`/`Đ` riêng: chúng KHÔNG phải `d` cộng dấu phụ nên `NFD` không
152
- * tách ra được, `đăng nhập` sẽ không khớp `dang nhap` nếu bỏ bước này.
153
- */
154
- declare function stripDiacritics(text: string): string;
159
+
155
160
  /**
156
161
  * @deprecated Dùng `stripDiacritics`. Giữ lại vì hàm này ĐÃ publish trong
157
162
  * `@edmicro/olkit@1.1.1` (kiểm bằng `npm pack` rồi đọc `types/*.d.ts` — có
@@ -246,6 +251,106 @@ declare class OlScrollArea {
246
251
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlScrollArea, "ol-scroll-area", never, { "scrollbar": { "alias": "scrollbar"; "required": false; "isSignal": true; }; "axis": { "alias": "axis"; "required": false; "isSignal": true; }; "smooth": { "alias": "smooth"; "required": false; "isSignal": true; }; "innerClass": { "alias": "innerClass"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
247
252
  }
248
253
 
254
+ /** Một mục trong mục lục. */
255
+ interface OlScrollspyItem {
256
+ /** `id` của phần tử trên trang mà mục này trỏ tới. */
257
+ id: string;
258
+ label: string;
259
+ /** Cấp thụt lề: 1 là mục chính, 2 là mục con. */
260
+ level?: 1 | 2;
261
+ }
262
+ type OlScrollspyOrientation = 'vertical' | 'horizontal';
263
+ /**
264
+ * Mục lục tự đánh dấu phần đang xem.
265
+ *
266
+ * Chưa có node Figma — dựng theo design system, mục đang xem dùng
267
+ * `text-fg-brand` và một vạch `bg-brand` bên trái (dọc) hoặc dưới (ngang).
268
+ *
269
+ * ## Vì sao `IntersectionObserver` chứ không nghe sự kiện `scroll`
270
+ *
271
+ * Nghe `scroll` thì mỗi lần cuộn là một lần chạy tay tính `getBoundingClientRect()`
272
+ * cho MỌI phần — trang 63 mục như docs olkit là 63 phép đo mỗi khung hình.
273
+ * `IntersectionObserver` để trình duyệt tự báo, và chỉ báo phần nào đổi trạng
274
+ * thái.
275
+ *
276
+ * ## `rootMargin` quyết định "đang xem" nghĩa là gì
277
+ *
278
+ * Mặc định `-20% 0px -70% 0px` thu vùng quan sát thành một dải ngang mỏng ở
279
+ * khoảng 1/4 trên màn hình. Không thu thì phần nào chạm mép dưới màn hình cũng
280
+ * tính là đang xem, và cuộn một nhịp có thể sáng ba mục cùng lúc.
281
+ *
282
+ * Dải mỏng cũng xử lý được phần NGẮN hơn màn hình: nếu đợi phần chiếm trọn
283
+ * viewport mới tính, mục ngắn không bao giờ được đánh dấu.
284
+ *
285
+ * ## Phần cuối trang không bao giờ chạm dải
286
+ *
287
+ * Cuộn hết cỡ mà phần cuối vẫn nằm dưới dải quan sát thì nó không sáng. Nên
288
+ * khi chạm đáy trang, component đánh dấu thẳng mục cuối — `atBottom()`.
289
+ *
290
+ * ## Lưu ý khi viết test
291
+ *
292
+ * jsdom KHÔNG có `IntersectionObserver` (đo 2026-09-26: `typeof` trả
293
+ * `'undefined'`). Component tự bảo vệ bằng cách kiểm trước khi dùng, nên spec
294
+ * dựng được và khoá được phần hiển thị; còn việc đánh dấu theo cuộn phải
295
+ * nghiệm thu trên trình duyệt. Đừng giả lập `IntersectionObserver` rồi tưởng
296
+ * đã kiểm hành vi thật.
297
+ */
298
+ declare class OlScrollspy {
299
+ private readonly elementRef;
300
+ private readonly destroyRef;
301
+ readonly items: _angular_core.InputSignal<OlScrollspyItem[]>;
302
+ readonly orientation: _angular_core.InputSignal<OlScrollspyOrientation>;
303
+ readonly ariaLabel: _angular_core.InputSignal<string>;
304
+ /**
305
+ * Vùng quan sát, theo cú pháp `rootMargin` của `IntersectionObserver`.
306
+ *
307
+ * Đổi giá trị này khi trang có thanh trên cố định: `-80px 0px -70% 0px` đẩy
308
+ * mép trên xuống dưới thanh đó.
309
+ */
310
+ readonly rootMargin: _angular_core.InputSignal<string>;
311
+ /** Cuộn mượt khi bấm mục. Tắt nếu trang tự lo việc cuộn. */
312
+ readonly smoothScroll: _angular_core.InputSignal<boolean>;
313
+ /**
314
+ * Khung cuộn chứa nội dung. `null` (mặc định) nghĩa là cả trang.
315
+ *
316
+ * BẮT BUỘC truyền khi nội dung nằm trong một khung `overflow-y-auto` riêng:
317
+ * `IntersectionObserver` mặc định quan sát theo VIEWPORT, nên phần nào cũng
318
+ * "đang hiện" khi khung con nằm trong tầm mắt — đo được: cuộn khung con từ
319
+ * đầu tới đáy mà không mục nào sáng.
320
+ */
321
+ readonly scrollRoot: _angular_core.InputSignal<HTMLElement | null>;
322
+ private readonly currentId;
323
+ /** `id` của phần đang xem. Rỗng khi chưa phần nào vào vùng quan sát. */
324
+ readonly activeId: _angular_core.Signal<string>;
325
+ readonly activeChange: _angular_core.OutputEmitterRef<string>;
326
+ private observer;
327
+ constructor();
328
+ private startObserving;
329
+ /** Khung (hoặc trang) đã cuộn tới sát đáy chưa — sai số 4px cho tròn số lẻ. */
330
+ private atBottom;
331
+ private stopObserving;
332
+ private setActive;
333
+ /**
334
+ * Bấm mục thì cuộn tới phần tương ứng và đánh dấu NGAY.
335
+ *
336
+ * Không đợi `IntersectionObserver` báo: cuộn mượt mất vài trăm mili giây, và
337
+ * trong quãng đó mục vừa bấm chưa sáng — người dùng tưởng bấm hụt.
338
+ */
339
+ protected onClick(event: MouseEvent, item: OlScrollspyItem): void;
340
+ protected readonly navClasses: _angular_core.Signal<"block" | "block w-full overflow-x-auto">;
341
+ protected readonly listClasses: _angular_core.Signal<"flex flex-col gap-0.5 border-s border-default ps-0" | "flex items-center gap-1 border-b border-default">;
342
+ /**
343
+ * Class của một mục.
344
+ *
345
+ * Gom vào MỘT chuỗi qua ba-ngôi thay vì nhiều `[class.x]` rời: hai class
346
+ * cùng thuộc tính thì trình duyệt chọn theo thứ tự luật trong file CSS chứ
347
+ * không theo thứ tự viết, kết quả đổi theo bản build.
348
+ */
349
+ protected linkClasses(item: OlScrollspyItem): string;
350
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlScrollspy, never>;
351
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlScrollspy, "ol-scrollspy", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "orientation": { "alias": "orientation"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "rootMargin": { "alias": "rootMargin"; "required": false; "isSignal": true; }; "smoothScroll": { "alias": "smoothScroll"; "required": false; "isSignal": true; }; "scrollRoot": { "alias": "scrollRoot"; "required": false; "isSignal": true; }; }, { "activeChange": "activeChange"; }, never, never, true, never>;
352
+ }
353
+
249
354
  type ButtonColor = 'default' | 'brand' | 'primary' | 'alternative' | 'secondary' | 'tertiary' | 'ghost' | 'success' | 'green' | 'danger' | 'red' | 'warning' | 'yellow' | 'dark' | 'light';
250
355
  type ButtonSize = 'xs' | 'sm' | 'base' | 'l' | 'xl';
251
356
  type OlButtonType = 'button' | 'submit' | 'reset';
@@ -952,6 +1057,178 @@ interface DatepickerPreset {
952
1057
  range: () => [Date, Date];
953
1058
  }
954
1059
 
1060
+ /**
1061
+ * Bảng chuẩn kích thước và trạng thái cho MỌI ô nhập của thư viện.
1062
+ *
1063
+ * ## Vì sao cần
1064
+ *
1065
+ * Chín component họ form từng tự khai kích thước riêng, và chúng trôi khỏi
1066
+ * nhau. Đo ngày 2026-09-23 trên chín component:
1067
+ *
1068
+ * thiếu cỡ `xl` 3 component (select · autocomplete · input-counter)
1069
+ * nền sai 3 component (autocomplete · timepicker · datepicker)
1070
+ * viền sai 5 component
1071
+ * đệm ngang lệch 2px ở cả ba cỡ đầu (datepicker so với input)
1072
+ * cỡ chữ `sm` lệch 2px (14px vs 12px)
1073
+ *
1074
+ * Nền và viền lệch **chỉ lộ ở chế độ tối**: sáng thì `--bg-secondary` và
1075
+ * `--bg-secondary-medium` cùng ra `#f9fafb`, tối thì `gray-950` vs `gray-800`.
1076
+ * 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.
1077
+ *
1078
+ * Đọc từ đây thì thêm ô nhập mới cũng tự khớp, và đổi thang chỉ sửa một chỗ.
1079
+ *
1080
+ * ## Không gộp chiều cao với đệm ngang
1081
+ *
1082
+ * Hai thứ tách riêng vì component có icon cần `ps-*` thay `px-*`. Gộp lại thì
1083
+ * mỗi component phải cắt chuỗi ra để bỏ phần đệm — dễ sai hơn là ghép vào.
1084
+ */
1085
+ /** Bốn cỡ của họ form. Trùng `InputSize`, `SelectSize`, `DatepickerSize`. */
1086
+ type FieldSize = 'sm' | 'base' | 'lg' | 'xl';
1087
+ /** Ba trạng thái hiển thị. */
1088
+ type FieldState = 'default' | 'error' | 'success';
1089
+ /**
1090
+ * Chiều cao theo cỡ — **cố định bằng `h-*`**, không suy từ `py-*`.
1091
+ *
1092
+ * Suy từ đệm dọc thì chiều cao phụ thuộc `line-height` của cỡ chữ và không
1093
+ * bao giờ khớp thang chung. Đo trước khi chuẩn hoá: datepicker ra 34/42/54
1094
+ * trong khi input ra 36/40/48 — đặt cạnh nhau là so le ngay, nặng nhất ở `lg`.
1095
+ *
1096
+ * `xl` (52px) là cỡ mới nhất; ba component còn thiếu nó sẽ so le 12px khi đặt
1097
+ * cạnh một `ol-input size="xl"`.
1098
+ */
1099
+ declare const FIELD_HEIGHT: Record<FieldSize, string>;
1100
+ /**
1101
+ * Chiều cao TỐI THIỂU — cho component cao theo nội dung.
1102
+ *
1103
+ * `ol-select` nhiều lựa chọn, `ol-tag-input` nhiều thẻ: chúng phải giãn ra khi
1104
+ * nội dung xuống dòng, nên `h-*` cố định sẽ cắt mất. Giá trị bằng đúng
1105
+ * `FIELD_HEIGHT` để một dòng vẫn cao bằng các ô khác.
1106
+ */
1107
+ declare const FIELD_MIN_HEIGHT: Record<FieldSize, string>;
1108
+ /**
1109
+ * Đệm ngang theo Figma `ol-input`: sm 10px · base 12px · lg 14px · xl 16px.
1110
+ *
1111
+ * KHÔNG dùng `[class.px-2.5]` trên template: Angular cắt phần sau dấu chấm
1112
+ * 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.
1113
+ */
1114
+ declare const FIELD_PADDING_X: Record<FieldSize, string>;
1115
+ /**
1116
+ * `FIELD_PADDING_X` tách hai đầu — cho ô có icon: phần tử ĐẦU TIÊN trong khung
1117
+ * (icon hoặc ô gõ) nhận lề đầu, phần tử CUỐI nhận lề cuối, ở giữa chỉ còn
1118
+ * `FIELD_ICON_GAP`.
1119
+ */
1120
+ declare const FIELD_PADDING_START: Record<FieldSize, string>;
1121
+ declare const FIELD_PADDING_END: Record<FieldSize, string>;
1122
+ /**
1123
+ * Icon ↔ chữ trong khung: 8px, mọi cỡ — Figma `ol-input` (1149:46826) và
1124
+ * `ol-timepicker` (1149:52243) đều `itemSpacing: 8`.
1125
+ *
1126
+ * Chỉ một nguồn tạo khoảng này. Bản cũ của `ol-input` có cả `gap-2` ở khung
1127
+ * lẫn `ps-2.5`/`pe-2.5` ở ô gõ → icon cách chữ 18px (đo 2026-09-29).
1128
+ */
1129
+ declare const FIELD_ICON_GAP = "gap-2";
1130
+ /**
1131
+ * Đệm dọc của khung CHỌN NHIỀU (chip cao 24px). Nhãn nổi Bordered nằm vắt
1132
+ * qua viền trên, nửa dưới lấn vào khung ~10px — đệm 4px thường lệ để chip
1133
+ * nằm dưới chữ nhãn (đo 2026-09-29: đè 3px, 32/96 ô). Chừa 10px thì một
1134
+ * dòng chip vẫn cao đúng 40px: 1 + 10 + 24 + 4 + 1.
1135
+ */
1136
+ declare function multiValuePadY(floatingOutlined: boolean): string;
1137
+ /**
1138
+ * Chỗ dành cho icon 16px nằm `absolute` ở đầu/cuối khung: lề cỡ + 16px +
1139
+ * `FIELD_ICON_GAP` 8px. Dùng khi icon ra khỏi luồng (select, autocomplete
1140
+ * chọn nhiều) nên khung phải tự chừa chỗ.
1141
+ */
1142
+ declare const FIELD_ICON_RESERVE_START: Record<FieldSize, string>;
1143
+ declare const FIELD_ICON_RESERVE_END: Record<FieldSize, string>;
1144
+ /** Chỗ cho nút xoá 14px (`close` cỡ xs) nằm `absolute` cuối ô: lề cỡ + 14 + 8. */
1145
+ declare const FIELD_CLEAR_RESERVE_END: Record<FieldSize, string>;
1146
+ /**
1147
+ * Như `FIELD_ICON_RESERVE_START` nhưng cho icon lịch của datepicker, vốn to
1148
+ * theo cỡ ô (14 / 16 / 20 / 20px): lề cỡ + icon + 8px = 32 / 36 / 42 / 44.
1149
+ */
1150
+ declare const PICKER_ICON_RESERVE_START: Record<FieldSize, string>;
1151
+ /**
1152
+ * Cỡ chữ theo cỡ ô.
1153
+ *
1154
+ * `sm` là **14px** (`text-sm`), không phải 12px. Datepicker từng dùng
1155
+ * `text-xs` nên chữ trong ô nhỏ hơn hẳn các ô cùng hàng.
1156
+ */
1157
+ declare const FIELD_TEXT: Record<FieldSize, string>;
1158
+ /**
1159
+ * Nền · viền · vòng focus theo trạng thái.
1160
+ *
1161
+ * Dùng `-medium` cho cả nền lẫn viền ở trạng thái thường. Hai tên gần giống
1162
+ * nhau (`bg-neutral-secondary` vs `bg-neutral-secondary-medium`) nhưng **khác
1163
+ * hẳn ở chế độ tối**, nên nhầm một chữ là ô đó lệch màu mà chế độ sáng không
1164
+ * lộ ra.
1165
+ *
1166
+ * Dùng `focus-within:` chứ không `focus:`: viền nằm trên thẻ bọc, còn focus
1167
+ * rơi vào `<input>` bên trong. `focus:` trên thẻ bọc không bao giờ khớp.
1168
+ */
1169
+ declare const FIELD_STATE: Record<FieldState, string>;
1170
+ /** Nền và con trỏ khi ô bị khoá. Áp đè lên `FIELD_STATE`. */
1171
+ declare const FIELD_DISABLED = "bg-disabled opacity-60 cursor-not-allowed";
1172
+ /** Bo góc — 12px cho mọi cỡ, theo Figma `bieu-mau.md`. */
1173
+ declare const FIELD_RADIUS = "rounded-md";
1174
+ /**
1175
+ * Lớp của nhãn phía trên ô.
1176
+ *
1177
+ * `text-heading` chứ không `text-body`: nhãn là thứ người dùng đọc đầu tiên để
1178
+ * biết ô này hỏi gì, nó phải đậm hơn chữ phụ.
1179
+ */
1180
+ declare const FIELD_LABEL = "inline-flex items-center gap-1 text-sm font-medium text-heading";
1181
+ /** Khoảng cách nhãn → ô. */
1182
+ declare const FIELD_LABEL_GAP = "mb-1.5";
1183
+ /**
1184
+ * Vị trí nhãn so với ô: `top` (mặc định, nhãn trên ô) hoặc `left` (nhãn cùng
1185
+ * hàng, bên trái — form dạng bảng: cài đặt, hồ sơ). Nhãn nổi thắng cả hai.
1186
+ */
1187
+ type LabelPosition = 'top' | 'left';
1188
+ /**
1189
+ * Khung ngoài của host khi nhãn `left`: lưới 2 cột. Cột 1 = nhãn, rộng theo
1190
+ * `--ol-label-w` (input `labelWidth`, mặc định 8rem). Cột 2 = mọi thứ còn lại
1191
+ * — khung nhập, dòng gợi ý/lỗi — nên chữ lỗi thẳng mép ô, không chui xuống
1192
+ * dưới nhãn. `[&>ol-field-label]` cụ thể hơn `[&>*]` nên nhãn thắng cột.
1193
+ */
1194
+ declare const FIELD_LABEL_BESIDE = "grid grid-cols-[var(--ol-label-w,8rem)_minmax(0,1fr)] items-start gap-x-3 [&>*]:col-start-2 [&>ol-field-label]:col-start-1 [&>ol-field-label]:row-start-1";
1195
+ /** Lớp khung ngoài theo vị trí nhãn — rỗng khi nhãn trên hoặc nhãn nổi. */
1196
+ declare function fieldLabelLayout(position: LabelPosition, floating: boolean): string;
1197
+ /** Dấu sao bắt buộc. */
1198
+ declare const FIELD_ASTERISK = "text-fg-danger font-bold";
1199
+ /**
1200
+ * Chữ trợ giúp dưới ô, đổi màu theo trạng thái.
1201
+ *
1202
+ * 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
1203
+ * giải thích lỗi hay chú thích thường.
1204
+ */
1205
+ declare const FIELD_HELPER: Record<FieldState, string>;
1206
+ /** Khoảng cách ô → chữ trợ giúp. */
1207
+ declare const FIELD_HELPER_GAP = "mt-1.5";
1208
+ /**
1209
+ * Gộp chiều cao · đệm ngang · cỡ chữ · bo góc cho một cỡ.
1210
+ *
1211
+ * Tiện cho component không có icon. Component có icon tự ghép từ các bảng
1212
+ * riêng để thay `px-*` bằng `ps-*`.
1213
+ */
1214
+ declare function fieldSizeClasses(size: FieldSize): string;
1215
+ /**
1216
+ * Ba kiểu nhãn nổi — Figma `ol-floating-label` (#1149:50454) trục `Type`:
1217
+ *
1218
+ * - `outlined` — **Bordered**: viền đủ, nhãn nổi nằm ĐÈ lên viền trên.
1219
+ * - `filled` — **With background**: nền xám + gạch chân 2px, nhãn nổi thành
1220
+ * một hàng 20px ở trên, nằm trong nền.
1221
+ * - `standard` — **Default**: chỉ gạch chân 2px, nhãn nổi thành hàng 20px ở trên.
1222
+ */
1223
+ type FloatingVariant = 'outlined' | 'filled' | 'standard';
1224
+ /**
1225
+ * Vị trí ngang của nhãn KHI NỔI (px) = đệm ngang của ô (`FIELD_PADDING_X`) —
1226
+ * Figma đặt nhãn nổi thẳng mép chữ, kể cả khi có icon đầu (nhãn nổi lên trên
1227
+ * cột icon). Phải là SỐ vì nhãn đặt bằng `style.left`, cùng hệ với vị trí nằm
1228
+ * yên (đo lúc chạy).
1229
+ */
1230
+ declare const FLOAT_LABEL_START: Record<FieldSize, number>;
1231
+
955
1232
  declare class OlDatepickerStateService {
956
1233
  readonly viewMode: _angular_core.WritableSignal<DatepickerViewMode>;
957
1234
  readonly viewDate: _angular_core.WritableSignal<Date>;
@@ -999,7 +1276,11 @@ declare class OlDatepickerStateService {
999
1276
  }
1000
1277
 
1001
1278
  declare class OlDatepicker implements ControlValueAccessor {
1002
- readonly state: OlDatepickerStateService;
1279
+ /**
1280
+ * Trạng thái lịch (ngày đang xem, khoảng đã chọn…). Tên cũ `state` nhường
1281
+ * cho input `state` (lỗi / thành công) — cùng tên với mọi ô nhập khác.
1282
+ */
1283
+ readonly pickerState: OlDatepickerStateService;
1003
1284
  private readonly hostEl;
1004
1285
  /** Id do nơi gọi truyền; trống thì dùng id tự sinh bên dưới. */
1005
1286
  readonly id: _angular_core.InputSignal<string>;
@@ -1013,6 +1294,33 @@ declare class OlDatepicker implements ControlValueAccessor {
1013
1294
  readonly value: _angular_core.ModelSignal<Date | null>;
1014
1295
  readonly placeholder: _angular_core.InputSignal<string>;
1015
1296
  readonly label: _angular_core.InputSignal<string>;
1297
+ /**
1298
+ * Trạng thái hiển thị — lỗi / thành công, cùng thang `FIELD_STATE` với mọi ô
1299
+ * nhập khác. Trước đây datepicker không có (tên `state` bị service chiếm).
1300
+ */
1301
+ readonly state: _angular_core.InputSignal<FieldState>;
1302
+ /**
1303
+ * Nhãn nổi: `label` nằm TRONG khung (sau icon lịch), nổi lên khi focus, mở
1304
+ * lịch hoặc đã có ngày (kể cả chữ đang gõ dở).
1305
+ */
1306
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
1307
+ /**
1308
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
1309
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
1310
+ */
1311
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
1312
+ /**
1313
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
1314
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
1315
+ * mép ô thẳng hàng.
1316
+ */
1317
+ readonly labelWidth: _angular_core.InputSignal<string>;
1318
+ protected readonly labelLayout: _angular_core.Signal<string>;
1319
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
1320
+ /** Khung nhãn + ô: cột dọc (nhãn trên) hoặc lưới 2 cột (nhãn trái). */
1321
+ protected readonly stackClasses: _angular_core.Signal<string>;
1322
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
1323
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
1016
1324
  readonly helperText: _angular_core.InputSignal<string>;
1017
1325
  readonly required: _angular_core.InputSignal<boolean>;
1018
1326
  /**
@@ -1103,6 +1411,16 @@ declare class OlDatepicker implements ControlValueAccessor {
1103
1411
  * ngang và `ps-*` cho chỗ icon lịch.
1104
1412
  */
1105
1413
  readonly inputSizeClasses: _angular_core.Signal<string>;
1414
+ private readonly sizeClasses;
1415
+ protected readonly showClear: _angular_core.Signal<boolean>;
1416
+ /**
1417
+ * `ms-px`/`me-px`: div bọc icon tính từ mép NGOÀI viền 1px của ô, còn đệm
1418
+ * ô tính từ mép trong. Nhãn nổi `filled`/`standard` không có viền hai bên
1419
+ * (chỉ gạch dưới) thì không bù.
1420
+ */
1421
+ private readonly sideBorder;
1422
+ protected readonly leadPadClass: _angular_core.Signal<string>;
1423
+ protected readonly trailPadClass: _angular_core.Signal<string>;
1106
1424
  /**
1107
1425
  * Cỡ icon lịch theo `size` của ô nhập.
1108
1426
  *
@@ -1157,17 +1475,57 @@ declare class OlDatepicker implements ControlValueAccessor {
1157
1475
  */
1158
1476
  ngOnDestroy(): void;
1159
1477
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDatepicker, never>;
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>;
1478
+ 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; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "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>;
1161
1479
  }
1162
1480
 
1163
1481
  type OlDateRangePickerVariant = 'dual-inputs' | 'joined';
1164
1482
  declare class OlDateRangePicker implements ControlValueAccessor {
1165
- readonly state: OlDatepickerStateService;
1483
+ /**
1484
+ * Trạng thái lịch (ngày đang xem, khoảng đã chọn…). Tên cũ `state` nhường
1485
+ * cho input `state` (lỗi / thành công) — cùng tên với mọi ô nhập khác.
1486
+ */
1487
+ readonly pickerState: OlDatepickerStateService;
1166
1488
  private readonly hostEl;
1167
1489
  readonly value: _angular_core.ModelSignal<DateRange | null>;
1168
1490
  readonly startPlaceholder: _angular_core.InputSignal<string>;
1169
1491
  readonly endPlaceholder: _angular_core.InputSignal<string>;
1170
1492
  readonly label: _angular_core.InputSignal<string>;
1493
+ /** Lỗi / thành công — cùng thang `FIELD_STATE` với mọi ô nhập khác. */
1494
+ readonly state: _angular_core.InputSignal<FieldState>;
1495
+ /**
1496
+ * Nhãn nổi. `joined`: một nhãn (`label`) cho cả khung. `dual-inputs`: mỗi ô
1497
+ * một nhãn (`fromLabel` / `toLabel`), nổi riêng theo ô đó có ngày hay không.
1498
+ */
1499
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
1500
+ /**
1501
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
1502
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
1503
+ */
1504
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
1505
+ /**
1506
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
1507
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
1508
+ * mép ô thẳng hàng.
1509
+ */
1510
+ readonly labelWidth: _angular_core.InputSignal<string>;
1511
+ protected readonly labelLayout: _angular_core.Signal<string>;
1512
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
1513
+ /**
1514
+ * `joined`: một nhãn cho cả khung → khung ngoài thành lưới. `dual-inputs`:
1515
+ * mỗi ô một nhãn ("Từ" / "Đến") → khung ngoài giữ cột dọc, từng CỘT thành lưới.
1516
+ */
1517
+ protected readonly stackClasses: _angular_core.Signal<string>;
1518
+ protected readonly columnClasses: _angular_core.Signal<string>;
1519
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
1520
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
1521
+ /**
1522
+ * Id hai ô `dual-inputs` — để `<label for>` nối được. Bản cũ hai nhãn
1523
+ * "Từ"/"Đến" KHÔNG có `for`, ô không có id: bấm nhãn không focus, trình đọc
1524
+ * màn hình đọc hai ô không tên.
1525
+ */
1526
+ private readonly uid;
1527
+ readonly startInputId: string;
1528
+ readonly endInputId: string;
1171
1529
  readonly fromLabel: _angular_core.InputSignal<string>;
1172
1530
  readonly toLabel: _angular_core.InputSignal<string>;
1173
1531
  readonly showInfoIcon: _angular_core.InputSignal<boolean>;
@@ -1241,7 +1599,27 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1241
1599
  * 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à
1242
1600
  * để lại đúng lỗi đó ở chỗ khác.
1243
1601
  */
1602
+ /**
1603
+ * Hai ô của dạng `dual-inputs`: icon lịch ở lề theo cỡ, chữ cách icon 8px.
1604
+ * Ô "Đến" mang nút xoá `absolute` nên lề phải chừa chỗ cho nó.
1605
+ */
1244
1606
  readonly inputSizeClasses: _angular_core.Signal<string>;
1607
+ readonly endInputClasses: _angular_core.Signal<string>;
1608
+ private inputClasses;
1609
+ protected readonly showClear: _angular_core.Signal<boolean>;
1610
+ /**
1611
+ * `ms-px`/`me-px`: div bọc icon tính từ mép NGOÀI viền 1px của ô, còn đệm
1612
+ * ô tính từ mép trong. Nhãn nổi `filled`/`standard` không có viền hai bên
1613
+ * (chỉ gạch dưới) thì không bù.
1614
+ */
1615
+ private readonly sideBorder;
1616
+ protected readonly leadPadClass: _angular_core.Signal<string>;
1617
+ protected readonly trailPadClass: _angular_core.Signal<string>;
1618
+ /**
1619
+ * Dạng `joined`: khung có `p-1` (4px), nên icon lùi thêm lề cỡ − 4px để
1620
+ * mép icon cách viền đúng lề cỡ như mọi ô; chữ cách icon 8px.
1621
+ */
1622
+ protected readonly joinedIconPadClass: _angular_core.Signal<string>;
1245
1623
  /**
1246
1624
  * Cỡ cho biến thể `joined` — khung bọc mang viền, hai input bên trong trong
1247
1625
  * suốt. Phải tách khỏi `inputSizeClasses()`: ở đây chiều cao thuộc về KHUNG,
@@ -1256,9 +1634,10 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1256
1634
  * Khung có `p-1` = 4px mỗi cạnh, nên input con lùi 8px để TỔNG chiều cao
1257
1635
  * khớp thang: h-9 → h-7, h-10 → h-8, h-12 → h-10, h-13 → h-11.
1258
1636
  */
1259
- readonly joinedWrapperClasses: _angular_core.Signal<"h-9" | "h-12" | "h-10" | "h-13">;
1637
+ readonly joinedWrapperClasses: _angular_core.Signal<string>;
1638
+ private readonly joinedHeight;
1260
1639
  /** Input bên trong khung `joined` — trừ đi `p-1` của khung. */
1261
- readonly joinedInputClasses: _angular_core.Signal<"h-7 text-xs" | "h-10 text-base" | "h-11 text-base" | "h-8 text-sm">;
1640
+ readonly joinedInputClasses: _angular_core.Signal<string>;
1262
1641
  writeValue(val: DateRange | null): void;
1263
1642
  registerOnChange(fn: (val: DateRange | null) => void): void;
1264
1643
  registerOnTouched(fn: () => void): void;
@@ -1294,7 +1673,7 @@ declare class OlDateRangePicker implements ControlValueAccessor {
1294
1673
  */
1295
1674
  ngOnDestroy(): void;
1296
1675
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDateRangePicker, never>;
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>;
1676
+ 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; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "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>;
1298
1677
  }
1299
1678
 
1300
1679
  declare class OlDatepickerDirective implements OnDestroy {
@@ -1629,6 +2008,8 @@ declare class OlDropdown {
1629
2008
  private detachMenu;
1630
2009
  /** Hàm gỡ khỏi ngăn xếp Escape. `null` khi đang đóng. */
1631
2010
  private releaseEscape;
2011
+ /** Hàm gỡ listener Tab trên panel. `null` khi đang đóng. */
2012
+ private releaseMenuKeydown;
1632
2013
  readonly label: _angular_core.InputSignal<string | null>;
1633
2014
  readonly isOpen: _angular_core.ModelSignal<boolean>;
1634
2015
  /**
@@ -1679,6 +2060,75 @@ declare class OlDropdown {
1679
2060
  onTriggerClick(): void;
1680
2061
  onMouseEnter(): void;
1681
2062
  onMouseLeave(): void;
2063
+ /**
2064
+ * Danh sách phần tử nhận được tiêu điểm, theo thứ tự nhìn thấy.
2065
+ *
2066
+ * `tabindex="-1"` bị loại: đó là dấu hiệu tác giả cố ý đưa phần tử ra khỏi
2067
+ * đường Tab. `:disabled` cũng loại — trình duyệt không cho nó nhận tiêu điểm.
2068
+ *
2069
+ * Lọc phần ẩn bằng `[hidden]`/`[aria-hidden]` chứ KHÔNG `offsetParent`:
2070
+ * `offsetParent` là `null` với mọi phần tử trong `position: fixed`, và luôn
2071
+ * `null` trong jsdom — dùng nó thì danh sách rỗng, hàm thoát sớm, và toàn bộ
2072
+ * xử lý Tab lặng lẽ không chạy. Đã mắc: spec đỏ 3 ca trong khi trình duyệt
2073
+ * thật vẫn đúng, vì hai môi trường cho `offsetParent` khác nhau.
2074
+ */
2075
+ private focusableItems;
2076
+ /**
2077
+ * Nút mở panel, kể cả khi nơi dùng tự khai qua slot `[trigger]`.
2078
+ *
2079
+ * `triggerButtonRef` CHỈ có giá trị với nút mặc định của component — hai
2080
+ * nhánh `@if` trong template vẽ ra nó. Nơi dùng truyền nút riêng qua
2081
+ * `<ng-content select="[trigger]">` thì cả hai nhánh đều không chạy và ref
2082
+ * là `undefined`. Đo 2026-09-25: header onluyenv4 (streak, chuông) đều dùng
2083
+ * nút slot, nên mọi xử lý bám vào ref đó lặng lẽ không chạy — gồm cả việc
2084
+ * trả tiêu điểm về nút sau khi Escape, vốn có từ trước.
2085
+ *
2086
+ * Nên tìm nút thật trong cây DOM của host khi không có ref.
2087
+ */
2088
+ private triggerElement;
2089
+ /**
2090
+ * Phần tử nhận tiêu điểm đứng ngay sau `element` trong thứ tự Tab của trang.
2091
+ *
2092
+ * Quét từ `<body>` và loại chính panel ra: panel treo cuối `<body>` nên nếu
2093
+ * tính cả nó thì "phần tử sau nút trigger" lại rơi vào panel vừa đóng.
2094
+ */
2095
+ private nextFocusableAfter;
2096
+ /**
2097
+ * Tab từ nút trigger khi panel đang mở → nhảy VÀO mục đầu panel.
2098
+ *
2099
+ * Vì sao cần: `appendTo="body"` treo panel ra cuối `<body>`, nên trong thứ
2100
+ * tự DOM nó nằm SAU toàn bộ nội dung trang. Bàn phím đi theo thứ tự DOM chứ
2101
+ * không theo vị trí nhìn thấy, nên Tab từ nút trigger nhảy sang phần tử kế
2102
+ * tiếp trong header rồi xuống thân trang — panel mở ngay trước mắt mà không
2103
+ * bao giờ tới được. Đo 2026-09-25: 8 lượt Tab, không lượt nào vào panel.
2104
+ *
2105
+ * KHÔNG cuốn tiêu điểm lúc mở: người dùng chuột không cần, và nhảy tiêu điểm
2106
+ * khi họ chưa yêu cầu là cướp quyền điều khiển. Chỉ khi họ chủ động nhấn Tab
2107
+ * mới dẫn vào — đúng lúc họ đang tìm đường đi tiếp.
2108
+ *
2109
+ * Shift+Tab bỏ qua: người dùng đang muốn lùi RA, giữ họ lại là bẫy.
2110
+ */
2111
+ onTriggerKeydown(event: KeyboardEvent): void;
2112
+ /**
2113
+ * Tab BÊN TRONG panel — giữ đường đi liền mạch với chỗ panel đang hiện.
2114
+ *
2115
+ * Panel nằm cuối `<body>` nên nếu để trình duyệt tự xử, Tab ở mục cuối sẽ
2116
+ * rơi xuống phần tử sau panel trong DOM (thường là hết trang), còn Shift+Tab
2117
+ * ở mục đầu nhảy ngược lên thân trang — cả hai đều bỏ qua nút trigger, tức
2118
+ * người dùng mất mốc quay về.
2119
+ *
2120
+ * KHÔNG bẫy vòng tròn như hộp thoại: chuẩn WAI-ARIA cho menu quy định Tab
2121
+ * phải THOÁT được ra trang. Nên:
2122
+ *
2123
+ * - Tab ở mục CUỐI → đóng panel, trả tiêu điểm về nút trigger rồi để trình
2124
+ * duyệt đi tiếp bình thường (phần tử sau nút trong header)
2125
+ * - Shift+Tab ở mục ĐẦU → về thẳng nút trigger, panel vẫn mở để người dùng
2126
+ * còn thấy mình vừa rời khỏi đâu
2127
+ *
2128
+ * Giữa các mục thì không can thiệp — panel là một khối liền trong DOM nên
2129
+ * trình duyệt tự đi đúng.
2130
+ */
2131
+ onMenuKeydown(event: KeyboardEvent): void;
1682
2132
  show(): void;
1683
2133
  hide(): void;
1684
2134
  toggle(): void;
@@ -1702,6 +2152,19 @@ declare class OlDropdownItem {
1702
2152
  readonly itemClick: _angular_core.OutputEmitterRef<MouseEvent>;
1703
2153
  private readonly parentDropdown;
1704
2154
  readonly isItemActive: _angular_core.Signal<boolean>;
2155
+ /**
2156
+ * Hình học theo Figma `ol-dropdown-item` (node 1149:39652, đo 2026-09-28):
2157
+ * lề 8px bốn cạnh (`px-2 py-2`, cao 36px), icon 16px cách chữ 6px, bo 8px
2158
+ * (`rounded` = `--radius: 8px`), chữ 14px medium. Bản trước `px-3` (12px),
2159
+ * icon cách chữ 10px, bo 6px.
2160
+ *
2161
+ * Độ đậm nằm TRONG từng nhánh, không gắn vô điều kiện — hai class cùng thuộc
2162
+ * tính thì thắng thua theo thứ tự luật CSS, không theo thứ tự trong chuỗi.
2163
+ *
2164
+ * Trạng thái đang chọn: Figma KHÔNG vẽ (chỉ có Initial / Hover / Disabled).
2165
+ * Bản trước tự đặt `bg-brand-softer` (#F5F8FF trên nền trắng — gần như không
2166
+ * thấy, báo 2026-09-28). Nay `bg-brand-soft` (#E3EAFF) + chữ brand đậm.
2167
+ */
1705
2168
  readonly itemClasses: _angular_core.Signal<string>;
1706
2169
  handleClick(event: MouseEvent): void;
1707
2170
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDropdownItem, never>;
@@ -1722,122 +2185,129 @@ declare class OlDropdownHeader {
1722
2185
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDropdownHeader, "ol-dropdown-header", never, { "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1723
2186
  }
1724
2187
 
1725
- declare class OlFooter {
2188
+ /**
2189
+ * Ô tìm ở đầu menu — Figma `ol-dropdown-header` Type=Form (1149:39893): một
2190
+ * `ol-input` cỡ sm có icon tìm, đặt trên danh sách (`ol-dropdown` Type=Users,
2191
+ * Type=With scroll).
2192
+ *
2193
+ * Component KHÔNG tự lọc: mục trong menu do nơi dùng chiếu vào, có thể lấy từ
2194
+ * máy chủ, nên nơi dùng lọc theo `query` rồi hiện `ol-dropdown-state` khi rỗng
2195
+ * hoặc đang tải.
2196
+ *
2197
+ * Bàn phím: ↓ xuống mục đầu, Enter chọn mục đầu còn bấm được. Esc do
2198
+ * `ol-dropdown` lo (đóng + trả tiêu điểm về nút mở).
2199
+ *
2200
+ * `sticky`: menu dài có `menuClass="max-h-… overflow-y-auto"` thì ô tìm đứng
2201
+ * yên trên đỉnh khi cuộn danh sách. `-top-2 -mt-2 pt-2` phủ luôn đệm 8px của
2202
+ * menu, để mục cuộn qua không lộ ra phía trên ô tìm.
2203
+ */
2204
+ declare class OlDropdownSearch {
2205
+ readonly query: _angular_core.ModelSignal<string>;
2206
+ readonly placeholder: _angular_core.InputSignal<string>;
2207
+ readonly ariaLabel: _angular_core.InputSignal<string>;
2208
+ /** Mở menu thì đưa tiêu điểm vào ô tìm — người mở menu có ô tìm là để gõ. */
2209
+ readonly autofocus: _angular_core.InputSignal<boolean>;
2210
+ /** Đóng menu thì xoá từ khoá, lần mở sau thấy lại đủ danh sách. */
2211
+ readonly clearOnClose: _angular_core.InputSignal<boolean>;
2212
+ private readonly host;
2213
+ private readonly dropdown;
2214
+ constructor();
2215
+ protected onKeydown(event: KeyboardEvent): void;
2216
+ private field;
1726
2217
  /**
1727
- * Giới hạn nội dung trong khung rộng tối đa và căn giữa. Tắt đi thì chân
1728
- * trang trải hết bề ngang — dùng khi trang đã có khung bao ngoài.
2218
+ * Mục đầu còn bấm được trong CÙNG menu — bỏ qua mục khoá. Tính cả
2219
+ * `menuitemcheckbox`/`menuitemradio` (menu chọn nhiều, chọn một).
1729
2220
  */
1730
- readonly container: _angular_core.InputSignal<boolean>;
1731
- readonly innerClasses: _angular_core.Signal<string>;
1732
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFooter, never>;
1733
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFooter, "ol-footer", never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2221
+ private firstItem;
2222
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDropdownSearch, never>;
2223
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDropdownSearch, "ol-dropdown-search", never, { "query": { "alias": "query"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "autofocus": { "alias": "autofocus"; "required": false; "isSignal": true; }; "clearOnClose": { "alias": "clearOnClose"; "required": false; "isSignal": true; }; }, { "query": "queryChange"; }, never, never, true, never>;
1734
2224
  }
1735
2225
 
2226
+ type DropdownStateType = 'loading' | 'empty' | 'no-results';
1736
2227
  /**
1737
- * Bảng chuẩn kích thước và trạng thái cho MỌI ô nhập của thư viện.
2228
+ * Trạng thái của menu khi KHÔNG có mục nào để hiện — Figma `ol-dropdown`
2229
+ * Type=Đang tải (5266:29107) · Rỗng (5266:29111) · Không có kết quả (5266:29114).
1738
2230
  *
1739
- * ## Vì sao cần
2231
+ * Figma ghi: "Menu lấy dữ liệu từ máy chủ phải có đủ bốn trạng thái. Thiếu
2232
+ * Đang tải / Rỗng / Không có kết quả là lỗi hay gặp nhất ở ô tìm kiếm gợi ý."
1740
2233
  *
1741
- * Chín component họ form từng tự khai kích thước riêng, và chúng trôi khỏi
1742
- * nhau. Đo ngày 2026-09-23 trên chín component:
2234
+ * Đệm 24/16px, căn giữa. Đang tải: ba thanh xương cao 10px (thanh cuối ngắn
2235
+ * ~70%), cách 10px. Hai trạng thái chữ: tiêu đề 14px đậm vừa `text-heading`,
2236
+ * dòng phụ 12px `text-body-subtle`, cách 8px.
1743
2237
  *
1744
- * thiếu cỡ `xl` 3 component (select · autocomplete · input-counter)
1745
- * nền sai 3 component (autocomplete · timepicker · datepicker)
1746
- * viền sai 5 component
1747
- * đệm ngang lệch 2px ở cả ba cỡ đầu (datepicker so với input)
1748
- * cỡ chữ `sm` lệch 2px (14px vs 12px)
1749
- *
1750
- * Nền và viền lệch **chỉ lộ ở chế độ tối**: sáng thì `--bg-secondary` và
1751
- * `--bg-secondary-medium` cùng ra `#f9fafb`, tối thì `gray-950` vs `gray-800`.
1752
- * Nên lỗi sống rất lâu — người dựng nhìn chế độ sáng, thấy giống hệt nhau.
1753
- *
1754
- * Đọc từ đây thì thêm ô nhập mới cũng tự khớp, và đổi thang chỉ sửa một chỗ.
1755
- *
1756
- * ## Không gộp chiều cao với đệm ngang
1757
- *
1758
- * Hai thứ tách riêng vì component có icon cần `ps-*` thay `px-*`. Gộp lại thì
1759
- * mỗi component phải cắt chuỗi ra để bỏ phần đệm — dễ sai hơn là ghép vào.
2238
+ * `role="status"`: trình đọc màn hình báo khi danh sách vừa lọc ra rỗng —
2239
+ * người dùng gõ tìm không nhìn thấy menu thì vẫn biết không có kết quả.
1760
2240
  */
1761
- /** Bốn cỡ của họ form. Trùng `InputSize`, `SelectSize`, `DatepickerSize`. */
1762
- type FieldSize = 'sm' | 'base' | 'lg' | 'xl';
1763
- /** Ba trạng thái hiển thị. */
1764
- type FieldState = 'default' | 'error' | 'success';
1765
- /**
1766
- * Chiều cao theo cỡ — **cố định bằng `h-*`**, không suy từ `py-*`.
1767
- *
1768
- * Suy từ đệm dọc thì chiều cao phụ thuộc `line-height` của cỡ chữ và không
1769
- * bao giờ khớp thang chung. Đo trước khi chuẩn hoá: datepicker ra 34/42/54
1770
- * trong khi input ra 36/40/48 — đặt cạnh nhau là so le ngay, nặng nhất ở `lg`.
1771
- *
1772
- * `xl` (52px) là cỡ mới nhất; ba component còn thiếu nó sẽ so le 12px khi đặt
1773
- * cạnh một `ol-input size="xl"`.
1774
- */
1775
- declare const FIELD_HEIGHT: Record<FieldSize, string>;
2241
+ declare class OlDropdownState {
2242
+ readonly type: _angular_core.InputSignal<DropdownStateType>;
2243
+ /** Tiêu đề — bỏ trống thì dùng câu mặc định theo `type`. */
2244
+ readonly heading: _angular_core.InputSignal<string>;
2245
+ /** Dòng phụ — bỏ trống thì dùng câu mặc định theo `type`. */
2246
+ readonly description: _angular_core.InputSignal<string>;
2247
+ /** Từ khoá vừa tìm — chèn vào câu mặc định của `no-results`. */
2248
+ readonly query: _angular_core.InputSignal<string>;
2249
+ protected readonly boxClasses: _angular_core.Signal<"flex flex-col items-center gap-2.5 px-4 py-6" | "flex flex-col items-center gap-2 px-4 py-6 text-center">;
2250
+ protected readonly resolvedHeading: _angular_core.Signal<string>;
2251
+ protected readonly resolvedDescription: _angular_core.Signal<string>;
2252
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlDropdownState, never>;
2253
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlDropdownState, "ol-dropdown-state", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "heading": { "alias": "heading"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "query": { "alias": "query"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
2254
+ }
2255
+
2256
+ declare class OlFooter {
2257
+ /**
2258
+ * Giới hạn nội dung trong khung rộng tối đa và căn giữa. Tắt đi thì chân
2259
+ * trang trải hết bề ngang — dùng khi trang đã có khung bao ngoài.
2260
+ */
2261
+ readonly container: _angular_core.InputSignal<boolean>;
2262
+ readonly innerClasses: _angular_core.Signal<string>;
2263
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFooter, never>;
2264
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFooter, "ol-footer", never, { "container": { "alias": "container"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2265
+ }
2266
+
2267
+ type OlFrameSpacing = 'xs' | 'sm' | 'base' | 'lg';
2268
+ type OlFrameRounded = 'sm' | 'md' | 'lg' | 'xl';
2269
+ type OlFrameVariant = 'default' | 'ghost';
1776
2270
  /**
1777
- * Chiều cao TỐI THIỂU — cho component cao theo nội dung.
2271
+ * Khung bọc nhiều mảng nội dung, khoảng cách đều nhau.
1778
2272
  *
1779
- * `ol-select` nhiều lựa chọn, `ol-tag-input` nhiều thẻ: chúng phải giãn ra khi
1780
- * nội dung xuống dòng, nên `h-*` cố định sẽ cắt mất. Giá trị bằng đúng
1781
- * `FIELD_HEIGHT` để một dòng vẫn cao bằng các ô khác.
1782
- */
1783
- declare const FIELD_MIN_HEIGHT: Record<FieldSize, string>;
1784
- /**
1785
- * Đệm ngang theo Figma `ol-input`: sm 10px · base 12px · lg 14px · xl 16px.
2273
+ * Chưa có node Figma — dựng theo design system.
1786
2274
  *
1787
- * KHÔNG dùng `[class.px-2.5]` trên template: Angular cắt phần sau dấu chấm
1788
- * nên nó render ra `px-2`, lệch 2px so với thiết kế. Phải gom vào một chuỗi.
1789
- */
1790
- declare const FIELD_PADDING_X: Record<FieldSize, string>;
1791
- /**
1792
- * Cỡ chữ theo cỡ ô.
2275
+ * ## Vì sao KHÔNG dùng `ol-card` cho việc này
1793
2276
  *
1794
- * `sm` là **14px** (`text-sm`), không phải 12px. Datepicker từng dùng
1795
- * `text-xs` nên chữ trong ô nhỏ hơn hẳn các ô cùng hàng.
1796
- */
1797
- declare const FIELD_TEXT: Record<FieldSize, string>;
1798
- /**
1799
- * Nền · viền · vòng focus theo trạng thái.
2277
+ * `ol-card` là MỘT thẻ nội dung: có ảnh bìa, có `href`, có trạng thái chọn.
2278
+ * Còn đây là cái khung xếp NHIỀU mảng cạnh nhau với khoảng cách đều — như cột
2279
+ * thiết lập, bảng điều khiển, hay danh sách nhóm. Lồng nhiều `ol-card` vào một
2280
+ * `div` rồi tự đặt `space-y-*` cũng ra kết quả, nhưng mỗi nơi dùng lại chọn
2281
+ * một khoảng cách khác và trang trông lộn xộn.
1800
2282
  *
1801
- * Dùng `-medium` cho cả nền lẫn viền ở trạng thái thường. Hai tên gần giống
1802
- * nhau (`bg-neutral-secondary` vs `bg-neutral-secondary-medium`) nhưng **khác
1803
- * hẳn ở chế độ tối**, nên nhầm một chữ là ô đó lệch màu mà chế độ sáng không
1804
- * lộ ra.
2283
+ * ## `stacked` bỏ viền giữa, không phải bỏ viền hết
1805
2284
  *
1806
- * Dùng `focus-within:` chứ không `focus:`: viền nằm trên thẻ bọc, còn focus
1807
- * rơi vào `<input>` bên trong. `focus:` trên thẻ bọc không bao giờ khớp.
1808
- */
1809
- declare const FIELD_STATE: Record<FieldState, string>;
1810
- /** Nền và con trỏ khi ô bị khoá. Áp đè lên `FIELD_STATE`. */
1811
- declare const FIELD_DISABLED = "bg-disabled opacity-60 cursor-not-allowed";
1812
- /** Bo góc — 12px cho mọi cỡ, theo Figma `bieu-mau.md`. */
1813
- declare const FIELD_RADIUS = "rounded-md";
1814
- /**
1815
- * Lớp của nhãn phía trên ô.
2285
+ * Xếp chồng thì các mảng dính thành một khối liền, chỉ còn một viền ngoài và
2286
+ * kẻ ngăn mảnh ở giữa — giống `ol-list-group`. Tách rời thì mỗi mảng là một
2287
+ * thẻ riêng có viền đủ bốn cạnh.
1816
2288
  *
1817
- * `text-heading` chứ không `text-body`: nhãn là thứ người dùng đọc đầu tiên để
1818
- * biết ô này hỏi gì, nó phải đậm hơn chữ phụ.
1819
- */
1820
- declare const FIELD_LABEL = "inline-flex items-center gap-1 text-sm font-medium text-heading";
1821
- /** Khoảng cách nhãn → ô. */
1822
- declare const FIELD_LABEL_GAP = "mb-1.5";
1823
- /** Dấu sao bắt buộc. */
1824
- declare const FIELD_ASTERISK = "text-fg-danger font-bold";
1825
- /**
1826
- * Chữ trợ giúp dưới ô, đổi màu theo trạng thái.
2289
+ * Làm bằng `divide-y` cộng `overflow-hidden` trên khung, KHÔNG bằng
2290
+ * `[&>*:not(:last-child)]:border-b`: biến thể `:last-child` trong chuỗi class
2291
+ * của component con sẽ khớp MỌI thể hiện của con đó, kể cả khi nó là con duy
2292
+ * nhất ở nơi khác — lỗi đã gặp trong repo này.
1827
2293
  *
1828
- * Trạng thái lỗi mà chữ vẫn màu phụ thì người dùng không biết dòng đó là lời
1829
- * giải thích lỗi hay chú thích thường.
1830
- */
1831
- declare const FIELD_HELPER: Record<FieldState, string>;
1832
- /** Khoảng cách ô → chữ trợ giúp. */
1833
- declare const FIELD_HELPER_GAP = "mt-1.5";
1834
- /**
1835
- * Gộp chiều cao · đệm ngang · cỡ chữ · bo góc cho một cỡ.
2294
+ * ## `ghost` bỏ nền và viền, giữ khoảng cách
1836
2295
  *
1837
- * Tiện cho component không có icon. Component có icon tự ghép từ các bảng
1838
- * riêng để thay `px-*` bằng `ps-*`.
2296
+ * Dùng khi khung nằm trong một thẻ đã có viền rồi — hai lớp viền lồng nhau
2297
+ * trông như lỗi hiển thị.
1839
2298
  */
1840
- declare function fieldSizeClasses(size: FieldSize): string;
2299
+ declare class OlFrame {
2300
+ readonly spacing: _angular_core.InputSignal<OlFrameSpacing>;
2301
+ readonly rounded: _angular_core.InputSignal<OlFrameRounded>;
2302
+ readonly variant: _angular_core.InputSignal<OlFrameVariant>;
2303
+ /** Dính các mảng thành một khối liền, chỉ còn kẻ ngăn ở giữa. */
2304
+ readonly stacked: _angular_core.InputSignal<boolean>;
2305
+ /** Xếp ngang thay vì dọc. Khoảng cách và kẻ ngăn đổi trục theo. */
2306
+ readonly horizontal: _angular_core.InputSignal<boolean>;
2307
+ protected readonly frameClasses: _angular_core.Signal<string>;
2308
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFrame, never>;
2309
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFrame, "ol-frame", never, { "spacing": { "alias": "spacing"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "stacked": { "alias": "stacked"; "required": false; "isSignal": true; }; "horizontal": { "alias": "horizontal"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
2310
+ }
1841
2311
 
1842
2312
  /**
1843
2313
  * Nhãn cho ô nhập — khớp Figma `ol-label` (#1149:94727), trục
@@ -2211,6 +2681,28 @@ declare class OlInput implements ControlValueAccessor {
2211
2681
  readonly cleared: _angular_core.OutputEmitterRef<void>;
2212
2682
  readonly rightHelperText: _angular_core.InputSignal<string>;
2213
2683
  readonly id: _angular_core.InputSignal<string>;
2684
+ /**
2685
+ * Nhãn nổi: `label` nằm TRONG khung, nổi lên khi focus hoặc có chữ — thay
2686
+ * cho nhãn phía trên ô. Xem `OlFieldFrame`.
2687
+ */
2688
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
2689
+ /**
2690
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
2691
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
2692
+ */
2693
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
2694
+ /**
2695
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
2696
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
2697
+ * mép ô thẳng hàng.
2698
+ */
2699
+ readonly labelWidth: _angular_core.InputSignal<string>;
2700
+ protected readonly labelLayout: _angular_core.Signal<string>;
2701
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
2702
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
2703
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
2704
+ /** Có chữ → nhãn nổi. `0` (input number) là CÓ giá trị. */
2705
+ readonly hasValue: _angular_core.Signal<boolean>;
2214
2706
  /** Id thực dùng: ưu tiên id do nơi gọi truyền, không thì dùng id tự sinh. */
2215
2707
  readonly inputId: _angular_core.Signal<string>;
2216
2708
  readonly helperId: _angular_core.Signal<string>;
@@ -2227,6 +2719,18 @@ declare class OlInput implements ControlValueAccessor {
2227
2719
  * lệch 2px so với thiết kế.
2228
2720
  */
2229
2721
  readonly paddingClasses: _angular_core.Signal<string>;
2722
+ /**
2723
+ * Khoảng icon ↔ chữ đúng 8px (`FIELD_ICON_GAP`): icon mang lề đầu/cuối của
2724
+ * khung, ô gõ bỏ lề phía có icon. Khung KHÔNG có `gap` — add-on chữ/icon
2725
+ * phải sát ô gõ (Figma Type=Add-on: gap 0).
2726
+ */
2727
+ protected readonly hasLeadingIcon: _angular_core.Signal<boolean>;
2728
+ protected readonly hasTrailingIcon: _angular_core.Signal<boolean>;
2729
+ protected readonly showClear: _angular_core.Signal<boolean>;
2730
+ protected readonly leadIconClasses: _angular_core.Signal<string>;
2731
+ /** Nút xoá đứng trước icon trạng thái thì để `ps-2` của icon tạo khoảng. */
2732
+ protected readonly clearClasses: _angular_core.Signal<string>;
2733
+ protected readonly trailIconClasses: _angular_core.Signal<string>;
2230
2734
  /**
2231
2735
  * Bốn phương thức này là thứ Angular Forms gọi tới. Thiếu chúng thì
2232
2736
  * `formControlName` / `[formControl]` KHÔNG gắn được — dùng component trong
@@ -2251,7 +2755,7 @@ declare class OlInput implements ControlValueAccessor {
2251
2755
  /** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
2252
2756
  protected markTouched(): void;
2253
2757
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlInput, never>;
2254
- 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>;
2758
+ 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; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "cleared": "cleared"; }, never, ["[addOnIcon], [leftIcon]", "[rightIcon]", "[rightHelperAction]"], true, never>;
2255
2759
  }
2256
2760
 
2257
2761
  type TextareaState = 'default' | 'error' | 'success';
@@ -2287,6 +2791,26 @@ declare class OlTextarea implements ControlValueAccessor {
2287
2791
  readonly showAsterisk: _angular_core.InputSignal<boolean>;
2288
2792
  readonly commentBox: _angular_core.InputSignal<boolean>;
2289
2793
  readonly id: _angular_core.InputSignal<string>;
2794
+ /**
2795
+ * Nhãn nổi: `label` nằm TRONG khung ở dòng đầu, nổi lên khi focus hoặc có
2796
+ * chữ. Không áp cho `commentBox` (thẻ bình luận có bố cục riêng).
2797
+ */
2798
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
2799
+ /**
2800
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
2801
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
2802
+ */
2803
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
2804
+ /**
2805
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
2806
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
2807
+ * mép ô thẳng hàng.
2808
+ */
2809
+ readonly labelWidth: _angular_core.InputSignal<string>;
2810
+ protected readonly labelLayout: _angular_core.Signal<string>;
2811
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
2812
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
2813
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
2290
2814
  readonly submitComment: _angular_core.OutputEmitterRef<string>;
2291
2815
  onSubmit(): void;
2292
2816
  /**
@@ -2305,7 +2829,7 @@ declare class OlTextarea implements ControlValueAccessor {
2305
2829
  /** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
2306
2830
  protected markTouched(): void;
2307
2831
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTextarea, never>;
2308
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTextarea, "ol-textarea", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "showCharCount": { "alias": "showCharCount"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "showAsterisk": { "alias": "showAsterisk"; "required": false; "isSignal": true; }; "commentBox": { "alias": "commentBox"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "submitComment": "submitComment"; }, never, never, true, never>;
2832
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTextarea, "ol-textarea", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "rows": { "alias": "rows"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "maxLength": { "alias": "maxLength"; "required": false; "isSignal": true; }; "showCharCount": { "alias": "showCharCount"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "showAsterisk": { "alias": "showAsterisk"; "required": false; "isSignal": true; }; "commentBox": { "alias": "commentBox"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "submitComment": "submitComment"; }, never, never, true, never>;
2309
2833
  }
2310
2834
 
2311
2835
  /**
@@ -2417,6 +2941,26 @@ declare class OlSelect implements ControlValueAccessor {
2417
2941
  * cần `set()` được. Cách dùng từ ngoài không đổi.
2418
2942
  */
2419
2943
  readonly disabled: _angular_core.ModelSignal<boolean>;
2944
+ /**
2945
+ * Nhãn nổi: `label` nằm TRONG khung, nổi lên khi focus, mở danh sách hoặc có
2946
+ * lựa chọn. Đa chọn: nhãn nằm yên ở dòng đầu, nổi lên không đẩy thẻ.
2947
+ */
2948
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
2949
+ /**
2950
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
2951
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
2952
+ */
2953
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
2954
+ /**
2955
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
2956
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
2957
+ * mép ô thẳng hàng.
2958
+ */
2959
+ readonly labelWidth: _angular_core.InputSignal<string>;
2960
+ protected readonly labelLayout: _angular_core.Signal<string>;
2961
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
2962
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
2963
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
2420
2964
  readonly placeholder: _angular_core.InputSignal<string>;
2421
2965
  readonly multiple: _angular_core.InputSignal<boolean>;
2422
2966
  readonly searchable: _angular_core.InputSignal<boolean>;
@@ -2426,6 +2970,10 @@ declare class OlSelect implements ControlValueAccessor {
2426
2970
  readonly labelKey: _angular_core.InputSignal<string>;
2427
2971
  readonly valueKey: _angular_core.InputSignal<string>;
2428
2972
  readonly groupBy: _angular_core.InputSignal<string>;
2973
+ /**
2974
+ * Tiêu đề khi danh sách rỗng. Bỏ trống thì theo Figma: có từ khoá →
2975
+ * "Không tìm thấy “…”", chưa có mục nào → "Chưa có mục nào".
2976
+ */
2429
2977
  readonly emptyText: _angular_core.InputSignal<string>;
2430
2978
  readonly searchPlaceholder: _angular_core.InputSignal<string>;
2431
2979
  /**
@@ -2567,6 +3115,7 @@ declare class OlSelect implements ControlValueAccessor {
2567
3115
  private readonly listboxRef;
2568
3116
  /** Ô tìm ở đầu panel, để chuyển focus vào khi mở. */
2569
3117
  private readonly oTimPanel;
3118
+ private readonly inlineSearch;
2570
3119
  readonly isOpen: _angular_core.WritableSignal<boolean>;
2571
3120
  readonly searchQuery: _angular_core.WritableSignal<string>;
2572
3121
  readonly activeIndex: _angular_core.WritableSignal<number>;
@@ -2616,9 +3165,15 @@ declare class OlSelect implements ControlValueAccessor {
2616
3165
  * 10/12/14/16px. Đặt `ol-select size="sm"` cạnh `ol-input size="sm"` là
2617
3166
  * lệch 4px, và ở `xl` lệch 2px theo chiều ngược lại.
2618
3167
  *
2619
- * Có icon trái thì `ps-10` cố định — chỗ đó dành cho icon, không phải đệm.
3168
+ * Icon trái và cụm nút phải đều `absolute`, nên khung tự chừa chỗ cho
3169
+ * chúng bằng `FIELD_ICON_RESERVE_*`: icon ↔ chữ đúng 8px. Bản cũ `ps-10` /
3170
+ * `pe-10` cố định cho mọi cỡ ra 10px ở cỡ base, 14px ở sm (đo 2026-09-29).
2620
3171
  */
2621
3172
  readonly paddingStartClass: _angular_core.Signal<string>;
3173
+ private readonly floatingOutlined;
3174
+ /** Icon trái và cụm nút phải nằm ở lề theo cỡ, như `ol-input`. */
3175
+ protected readonly leftIconPadClass: _angular_core.Signal<string>;
3176
+ protected readonly controlPadClass: _angular_core.Signal<string>;
2622
3177
  private onChange;
2623
3178
  private onTouched;
2624
3179
  private readonly destroyRef;
@@ -2813,7 +3368,7 @@ declare class OlSelect implements ControlValueAccessor {
2813
3368
  registerOnTouched(fn: () => void): void;
2814
3369
  setDisabledState(isDisabled: boolean): void;
2815
3370
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSelect, never>;
2816
- 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>;
3371
+ 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; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "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>;
2817
3372
  }
2818
3373
 
2819
3374
  /**
@@ -3031,45 +3586,531 @@ declare class OlToggleGroup {
3031
3586
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlToggleGroup, "ol-toggle-group", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
3032
3587
  }
3033
3588
 
3589
+ /**
3590
+ * Một lựa chọn của `ol-segmented`. `value` là thứ đổ vào model — dùng chuỗi
3591
+ * hoặc số, so bằng `===`.
3592
+ */
3593
+ interface SegmentedOption<T = string | number> {
3594
+ value: T;
3595
+ label: string;
3596
+ icon?: string;
3597
+ disabled?: boolean;
3598
+ /**
3599
+ * Màu của mục KHI ĐANG CHỌN — cho lựa chọn mang nghĩa (Đạt / Chưa đạt,
3600
+ * Đúng / Sai). Không truyền thì nền trắng chữ đậm như mặc định.
3601
+ */
3602
+ color?: SegmentedColor;
3603
+ }
3604
+ type SegmentedColor = 'neutral' | 'brand' | 'success' | 'warning' | 'danger';
3605
+ type SegmentedSize = 'sm' | 'base';
3606
+ /**
3607
+ * Chọn MỘT trong vài chế độ loại trừ nhau, đặt cạnh nhau trên một rãnh xám;
3608
+ * mục đang chọn nổi lên nền trắng (segmented control).
3609
+ *
3610
+ * Khác `ol-tabs`: tabs chuyển PANEL nội dung, đây chọn GIÁ TRỊ (có CVA, dùng
3611
+ * được trong form). Khác `ol-toggle`: công tắc chỉ có hai trạng thái bật/tắt.
3612
+ *
3613
+ * Trợ năng theo mẫu radio group của WAI-ARIA: `role="radiogroup"`, mỗi mục
3614
+ * `role="radio"`, chỉ mục đang chọn nằm trong thứ tự Tab (roving tabindex),
3615
+ * mũi tên trái/phải/lên/xuống chuyển VÀ chọn luôn, Home/End về đầu/cuối.
3616
+ *
3617
+ * Figma chưa có thiết kế (tra `lip4g3...` 2026-09-28: không có component nào
3618
+ * tên segmented) — dựng theo token của hệ thống.
3619
+ */
3620
+ declare class OlSegmented implements ControlValueAccessor {
3621
+ readonly options: _angular_core.InputSignal<SegmentedOption<string | number>[]>;
3622
+ readonly value: _angular_core.ModelSignal<string | number | null>;
3623
+ readonly size: _angular_core.InputSignal<SegmentedSize>;
3624
+ /** Giãn hết bề ngang khung, các mục chia đều. */
3625
+ readonly fullWidth: _angular_core.InputSignal<boolean>;
3626
+ /** `model()` để `setDisabledState()` của Reactive Forms đặt được. */
3627
+ readonly disabled: _angular_core.ModelSignal<boolean>;
3628
+ /** Tên nhóm cho bộ đọc màn hình — nên đặt, vì nhóm không có nhãn nhìn thấy. */
3629
+ readonly ariaLabel: _angular_core.InputSignal<string>;
3630
+ private readonly host;
3631
+ private onChange;
3632
+ private onTouched;
3633
+ protected readonly hostClasses: _angular_core.Signal<"block w-full" | "inline-block max-w-full">;
3634
+ protected readonly trackClasses: _angular_core.Signal<string>;
3635
+ /** Mục đang Tab vào được: mục đang chọn, không có thì mục bật đầu tiên. */
3636
+ protected readonly focusIndex: _angular_core.Signal<number>;
3637
+ protected itemClasses(option: SegmentedOption): string;
3638
+ select(option: SegmentedOption): void;
3639
+ protected onKeydown(event: KeyboardEvent, index: number): void;
3640
+ /** Mục bật kế tiếp theo chiều `step`, vòng quanh. -1 khi không có. */
3641
+ private nextEnabled;
3642
+ writeValue(v: string | number | null): void;
3643
+ registerOnChange(fn: (v: string | number | null) => void): void;
3644
+ registerOnTouched(fn: () => void): void;
3645
+ setDisabledState(isDisabled: boolean): void;
3646
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlSegmented, never>;
3647
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlSegmented, "ol-segmented", never, { "options": { "alias": "options"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "fullWidth": { "alias": "fullWidth"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; }, never, never, true, never>;
3648
+ }
3649
+
3650
+ /**
3651
+ * Loại tệp — nhận biết để chọn icon, màu và chữ mô tả.
3652
+ *
3653
+ * Mười loại phủ gần hết tệp giáo viên/học sinh tải lên: bài làm (ảnh, PDF,
3654
+ * Word), bảng điểm (Excel), bài giảng (PowerPoint, video), nghe (audio), nộp
3655
+ * gộp (nén), bài tin học (mã nguồn), ghi chú (văn bản thô).
3656
+ */
3657
+ type FileKind = 'image' | 'pdf' | 'word' | 'excel' | 'powerpoint' | 'video' | 'audio' | 'archive' | 'code' | 'text' | 'other';
3658
+ interface FileKindMeta {
3659
+ label: string;
3660
+ /** Họ lucide `file-*` — một họ nét cho mọi loại, nhìn đồng bộ. */
3661
+ icon: string;
3662
+ /**
3663
+ * Màu ô icon, lấy từ bảng màu `ol-icon-shape` — không tự tô. KHÔNG dùng
3664
+ * `dark`: nó là ô ĐẶC đen giữa các ô nền nhạt, nặng hơn hẳn (đo 2026-09-28).
3665
+ */
3666
+ color: IconShapeColor;
3667
+ }
3668
+ declare const FILE_KIND_META: Record<FileKind, FileKindMeta>;
3669
+ /** Phần mở rộng, chữ thường, không dấu chấm. Không có → chuỗi rỗng. */
3670
+ declare function fileExtension(name: string): string;
3671
+ /**
3672
+ * Nhận biết loại tệp — ưu tiên PHẦN MỞ RỘNG, lùi về MIME.
3673
+ *
3674
+ * Không tin MIME trước: trình duyệt đoán MIME theo đuôi và hệ điều hành, nên
3675
+ * cùng một tệp `.docx` có máy báo `application/vnd.openxml…`, có máy báo rỗng
3676
+ * (Windows thiếu Office), và `.csv` có máy báo `application/vnd.ms-excel`.
3677
+ */
3678
+ declare function detectFileKind(name: string, mime?: string): FileKind;
3679
+ /**
3680
+ * Byte → chữ ngắn theo cách người Việt đọc: `850 B`, `12,4 KB`, `3,2 MB`.
3681
+ * Dấu phẩy thập phân; số nguyên thì bỏ phần lẻ (`2 MB`, không `2,0 MB`).
3682
+ */
3683
+ declare function formatFileSize(bytes: number): string;
3684
+ /**
3685
+ * Tệp có khớp chuỗi `accept` của thẻ `<input type="file">` không.
3686
+ *
3687
+ * Hiểu đủ ba dạng của chuẩn HTML: đuôi (`.pdf`), MIME đầy đủ
3688
+ * (`application/pdf`), MIME đại diện (`image/*`). `*` hoặc rỗng → nhận hết.
3689
+ *
3690
+ * Cần tự kiểm vì `accept` chỉ LỌC hộp thoại chọn tệp — kéo thả thì trình duyệt
3691
+ * không lọc gì, và người dùng đổi bộ lọc hộp thoại sang "Tất cả tệp" là chọn
3692
+ * được tệp bất kỳ.
3693
+ */
3694
+ declare function matchesAccept(file: {
3695
+ name: string;
3696
+ type: string;
3697
+ }, accept: string): boolean;
3698
+ type FileRejectReason = 'type' | 'size' | 'count';
3699
+ interface FileRejection {
3700
+ file: File;
3701
+ reason: FileRejectReason;
3702
+ /** Câu báo lỗi tiếng Việt, hiện được ngay. */
3703
+ message: string;
3704
+ }
3705
+ interface FileValidationRules {
3706
+ accept?: string;
3707
+ /** Byte. 0 hoặc không truyền → không giới hạn. */
3708
+ maxSize?: number;
3709
+ /** Tổng số tệp tối đa, TÍNH CẢ tệp đã có. 0 → không giới hạn. */
3710
+ maxFiles?: number;
3711
+ /** Số tệp đang có sẵn trước lượt chọn này. */
3712
+ existingCount?: number;
3713
+ }
3714
+ interface FileValidationResult {
3715
+ accepted: File[];
3716
+ rejected: FileRejection[];
3717
+ }
3718
+ /**
3719
+ * Chia tệp thành nhận / loại, kèm lý do từng tệp bị loại.
3720
+ *
3721
+ * Thứ tự kiểm: định dạng → dung lượng → số lượng. Tệp sai định dạng không
3722
+ * chiếm chỗ trong hạn mức số lượng — chọn 3 tệp, 1 tệp `.exe`, giới hạn 2 thì
3723
+ * nhận 2 tệp hợp lệ chứ không loại oan tệp thứ ba vì tệp `.exe` đứng trước.
3724
+ */
3725
+ declare function validateFiles(files: File[], rules?: FileValidationRules): FileValidationResult;
3726
+ /** Trạng thái tải của một tệp — nơi gọi cập nhật theo tiến trình HTTP thật. */
3727
+ type UploadStatus = 'idle' | 'uploading' | 'success' | 'error';
3728
+ /**
3729
+ * Một tệp trong danh sách tải lên.
3730
+ *
3731
+ * `file` có thể vắng: tệp đã có sẵn trên máy chủ (mở lại bài đã nộp) chỉ có
3732
+ * tên, dung lượng và `url`, không có đối tượng `File`.
3733
+ */
3734
+ interface OlUploadFile {
3735
+ id: string;
3736
+ name: string;
3737
+ size: number;
3738
+ file?: File;
3739
+ /** Ảnh xem trước hoặc liên kết tải về. */
3740
+ url?: string;
3741
+ status?: UploadStatus;
3742
+ /** 0–100, chỉ dùng khi `status = 'uploading'`. */
3743
+ progress?: number;
3744
+ /** Câu báo lỗi khi `status = 'error'`. */
3745
+ error?: string;
3746
+ }
3747
+ /** File → OlUploadFile, gán id duy nhất để `@for track` không lẫn hai tệp trùng tên. */
3748
+ declare function toUploadFile(file: File): OlUploadFile;
3749
+ /**
3750
+ * Tệp trong một lần dán (Ctrl+V / ⌘V).
3751
+ *
3752
+ * Đọc cả `clipboardData.files` lẫn `items` loại `file`: Chrome đặt ảnh chụp
3753
+ * màn hình ở cả hai, Safari có lúc chỉ ở `items`. Gộp theo đối tượng để không
3754
+ * nhận một ảnh hai lần.
3755
+ *
3756
+ * Ảnh chụp màn hình dán vào LUÔN tên `image.png` — dán ba lần là ba tệp trùng
3757
+ * tên, không phân biệt được trong danh sách. Đổi thành "Ảnh dán 14-32-05.png".
3758
+ */
3759
+ declare function filesFromClipboard(data: DataTransfer | null, now?: Date): File[];
3760
+ /** Phím tắt dán theo hệ điều hành — để gợi ý đúng chữ người dùng thấy trên bàn phím. */
3761
+ declare function pasteShortcutLabel(platform?: string): string;
3762
+ /** Đang gõ trong ô nhập chữ — dán ở đó là dán CHỮ, không được cướp. */
3763
+ declare function isEditableTarget(target: EventTarget | null): boolean;
3764
+
3034
3765
  /**
3035
3766
  * Thang cỡ theo Figma: `sm | base | lg`. `'md'` là BÍ DANH của `'base'`,
3036
3767
  * giữ cho mã cũ chạy tiếp và sẽ bỏ ở bản sau — dùng `'base'` cho mã mới.
3037
3768
  */
3038
- type FileInputSize = 'sm' | 'base' | 'lg' | 'md';
3039
- declare class OlFileInput {
3040
- /** Id tự sinh khi nơi gọi không truyền id — để <label for> và
3041
- * aria-describedby luôn nối được với ô nhập. */
3042
- private readonly uid;
3043
- readonly inputId: _angular_core.Signal<string>;
3044
- readonly helperId: _angular_core.Signal<string>;
3045
- readonly describedBy: _angular_core.Signal<string | null>;
3046
- readonly ariaInvalid: _angular_core.Signal<null>;
3047
- readonly label: _angular_core.InputSignal<string>;
3048
- readonly helperText: _angular_core.InputSignal<string>;
3049
- readonly disabled: _angular_core.InputSignal<boolean>;
3050
- readonly dropzone: _angular_core.InputSignal<boolean>;
3769
+ type FileInputSize = 'sm' | 'base' | 'lg' | 'md';
3770
+ declare class OlFileInput {
3771
+ /** Id tự sinh khi nơi gọi không truyền id — để <label for> và
3772
+ * aria-describedby luôn nối được với ô nhập. */
3773
+ private readonly uid;
3774
+ readonly inputId: _angular_core.Signal<string>;
3775
+ readonly helperId: _angular_core.Signal<string>;
3776
+ readonly describedBy: _angular_core.Signal<string | null>;
3777
+ readonly ariaInvalid: _angular_core.Signal<null>;
3778
+ readonly label: _angular_core.InputSignal<string>;
3779
+ readonly helperText: _angular_core.InputSignal<string>;
3780
+ readonly disabled: _angular_core.InputSignal<boolean>;
3781
+ readonly dropzone: _angular_core.InputSignal<boolean>;
3782
+ readonly title: _angular_core.InputSignal<string>;
3783
+ readonly description: _angular_core.InputSignal<string>;
3784
+ readonly size: _angular_core.InputSignal<FileInputSize>;
3785
+ /** Cỡ đã quy bí danh: `'md'` -> `'base'`. Template so với cái này. */
3786
+ protected readonly resolvedSize: _angular_core.Signal<FileInputSize>;
3787
+ /**
3788
+ * Padding theo size. Không dùng [class.p-1.5] trên template vì Angular
3789
+ * cắt phần sau dấu chấm — nó render ra "p-1" (4px thay vì 6px).
3790
+ */
3791
+ readonly paddingClass: _angular_core.Signal<"p-1" | "p-1.5" | "p-2">;
3792
+ readonly accept: _angular_core.InputSignal<string>;
3793
+ readonly multiple: _angular_core.InputSignal<boolean>;
3794
+ readonly id: _angular_core.InputSignal<string>;
3795
+ /** Byte. 0 → không giới hạn. Có giá trị thì vùng kéo thả ghi "Dung lượng tối đa". */
3796
+ readonly maxSize: _angular_core.InputSignal<number>;
3797
+ /** Nhãn nút trong vùng kéo thả (Figma Advanced). Rỗng → không vẽ nút. */
3798
+ readonly ctaLabel: _angular_core.InputSignal<string>;
3799
+ /** Figma State: Danger / Success. Tệp bị loại tự chuyển sang `error`. */
3800
+ readonly state: _angular_core.InputSignal<"error" | "success" | "default">;
3801
+ /** Tệp đã nhận (sau khi kiểm). */
3802
+ readonly selectedFiles: _angular_core.WritableSignal<File[]>;
3803
+ protected readonly items: _angular_core.WritableSignal<OlUploadFile[]>;
3804
+ protected readonly errors: _angular_core.WritableSignal<string[]>;
3805
+ readonly filesChanged: _angular_core.OutputEmitterRef<File[]>;
3806
+ /** Tệp bị loại kèm lý do — component đã tự hiện câu lỗi. */
3807
+ readonly rejected: _angular_core.OutputEmitterRef<FileRejection[]>;
3808
+ private readonly dropInput;
3809
+ private readonly host;
3810
+ protected readonly zoneState: _angular_core.Signal<"error" | "success" | "default">;
3811
+ /**
3812
+ * Dòng phụ trong vùng: "Dung lượng tối đa" nếu có `maxSize`, không thì
3813
+ * `description` — giữ nguyên chữ của mã cũ đang truyền `description`.
3814
+ */
3815
+ protected readonly zoneHint: _angular_core.Signal<string>;
3816
+ onFileSelected(event: Event): void;
3817
+ protected openPicker(): void;
3818
+ /** Nhận tệp từ hộp chọn hoặc kéo thả: kiểm định dạng + dung lượng rồi phát. */
3819
+ protected receive(incoming: File[]): void;
3820
+ /** Dán tệp (Ctrl+V / ⌘V) khi tiêu điểm ở ô hoặc vùng kéo thả. */
3821
+ protected onPaste(event: ClipboardEvent): void;
3822
+ /**
3823
+ * Ô thường (không dropzone) hiện tên tệp bằng CHÍNH `<input type="file">` —
3824
+ * nhận tệp dán mà không gán vào nó thì ô vẫn ghi "Chưa chọn tệp" dù đã nhận.
3825
+ * `DataTransfer` là cách duy nhất gán `files` cho ô tệp.
3826
+ */
3827
+ private syncNativeInput;
3828
+ protected removeItem(item: OlUploadFile): void;
3829
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFileInput, never>;
3830
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFileInput, "ol-file-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "dropzone": { "alias": "dropzone"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; }, { "filesChanged": "filesChanged"; "rejected": "rejected"; }, never, never, true, never>;
3831
+ }
3832
+
3833
+ /**
3834
+ * Ba dạng của một tệp:
3835
+ *
3836
+ * - `row` — dòng trong danh sách: icon loại tệp, tên, dung lượng, thanh tiến
3837
+ * trình khi đang tải, nút thử lại khi lỗi. Figma không vẽ dạng này; dựng theo
3838
+ * token để danh sách tải nhiều tệp có chỗ báo tiến trình từng tệp.
3839
+ * - `chip` — Figma "Button header + Dropbox (uploaded no limit)": viên bo góc,
3840
+ * nút xem 👁 + tên + ✕.
3841
+ * - `card` — Figma "Button header + Dropbox (uploaded max 1 file)": khung viền,
3842
+ * icon + tên ở giữa, ✕ ở góc.
3843
+ */
3844
+ type FileItemVariant = 'row' | 'chip' | 'card';
3845
+ declare class OlFileItem {
3846
+ readonly file: _angular_core.InputSignal<OlUploadFile>;
3847
+ readonly variant: _angular_core.InputSignal<FileItemVariant>;
3848
+ readonly removable: _angular_core.InputSignal<boolean>;
3849
+ /** Hiện nút xem 👁 — nơi gọi tự mở tệp khi nhận `(preview)`. */
3850
+ readonly previewable: _angular_core.InputSignal<boolean>;
3851
+ readonly disabled: _angular_core.InputSignal<boolean>;
3852
+ readonly remove: _angular_core.OutputEmitterRef<OlUploadFile>;
3853
+ readonly preview: _angular_core.OutputEmitterRef<OlUploadFile>;
3854
+ readonly retry: _angular_core.OutputEmitterRef<OlUploadFile>;
3855
+ protected readonly kind: _angular_core.Signal<_edmicro_olkit.FileKind>;
3856
+ protected readonly meta: _angular_core.Signal<_edmicro_olkit.FileKindMeta>;
3857
+ protected readonly sizeText: _angular_core.Signal<string>;
3858
+ protected readonly status: _angular_core.Signal<_edmicro_olkit.UploadStatus>;
3859
+ protected readonly progress: _angular_core.Signal<number>;
3860
+ /** Dòng phụ dưới tên: dung lượng + trạng thái, đọc được cả khi không nhìn màu. */
3861
+ protected readonly metaText: _angular_core.Signal<string>;
3862
+ protected readonly metaClass: _angular_core.Signal<"text-xs text-fg-danger" | "text-xs text-body-subtle">;
3863
+ protected readonly rowClass: _angular_core.Signal<string>;
3864
+ /** Nút hành động nhỏ — cùng hình với nút đóng của drawer/modal. */
3865
+ protected readonly actionClass = "inline-flex size-8 shrink-0 items-center justify-center rounded-md text-body-subtle transition-colors hover:bg-neutral-tertiary hover:text-heading focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-brand-subtle disabled:pointer-events-none disabled:opacity-50";
3866
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFileItem, never>;
3867
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFileItem, "ol-file-item", never, { "file": { "alias": "file"; "required": true; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "removable": { "alias": "removable"; "required": false; "isSignal": true; }; "previewable": { "alias": "previewable"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; }, { "remove": "remove"; "preview": "preview"; "retry": "retry"; }, never, never, true, never>;
3868
+ }
3869
+
3870
+ /**
3871
+ * Vùng kéo–thả, theo Figma `ol-file-input-form` Type=Advanced (1149:48977):
3872
+ * icon tải lên · "Nhấn để tải lên hoặc kéo thả" · "Dung lượng tối đa: …" ·
3873
+ * (tuỳ chọn) nút "Chọn tệp".
3874
+ *
3875
+ * ## Cả vùng là MỘT `<button>`
3876
+ *
3877
+ * Bản cũ trong `ol-file-input` đặt `<input type="file" class="hidden">` —
3878
+ * `display: none` nên Tab không tới được, bàn phím không mở được hộp chọn tệp.
3879
+ * Ở đây vùng là nút thật: Tab tới, Enter/Space mở hộp chọn (qua `(pick)`, nơi
3880
+ * gọi gọi `input.click()`), và trình đọc màn hình đọc nhãn của nút.
3881
+ *
3882
+ * Nút "Chọn tệp" bên trong chỉ là HÌNH (`<span>`), không lồng `<button>` trong
3883
+ * `<button>` — HTML cấm, và hai nút lồng nhau làm một lần bấm bắn hai sự kiện.
3884
+ */
3885
+ declare class OlFileDropzone {
3886
+ readonly title: _angular_core.InputSignal<string>;
3887
+ /** Dòng phụ, ví dụ "Dung lượng tối đa: 30MB". Rỗng → không vẽ. */
3888
+ readonly hint: _angular_core.InputSignal<string>;
3889
+ /** Nhãn nút "Chọn tệp" trong vùng (Figma Advanced). Rỗng → không vẽ nút. */
3890
+ readonly ctaLabel: _angular_core.InputSignal<string>;
3891
+ readonly icon: _angular_core.InputSignal<string>;
3892
+ readonly disabled: _angular_core.InputSignal<boolean>;
3893
+ /** Trạng thái viền: lỗi / thành công (Figma State=Danger / Success). */
3894
+ readonly state: _angular_core.InputSignal<"error" | "success" | "default">;
3895
+ /** Chiều cao tối thiểu — vùng trong khung có tiêu đề thấp hơn vùng đứng riêng. */
3896
+ readonly compact: _angular_core.InputSignal<boolean>;
3897
+ readonly describedBy: _angular_core.InputSignal<string>;
3898
+ /** Dòng "hoặc dán ⌘V / Ctrl+V". Tắt khi nơi gọi không nhận tệp dán. */
3899
+ readonly pasteHint: _angular_core.InputSignal<boolean>;
3900
+ protected readonly shortcut: string;
3901
+ /** Bấm vào vùng (chuột hoặc bàn phím) — nơi gọi mở hộp chọn tệp. */
3902
+ readonly pick: _angular_core.OutputEmitterRef<void>;
3903
+ /** Tệp thả hoặc DÁN vào vùng. CHƯA kiểm định dạng — việc của nơi gọi. */
3904
+ readonly filesDropped: _angular_core.OutputEmitterRef<File[]>;
3905
+ protected readonly dragging: _angular_core.WritableSignal<boolean>;
3906
+ protected readonly zoneClass: _angular_core.Signal<string>;
3907
+ protected onDragOver(event: DragEvent): void;
3908
+ protected onDragLeave(event: DragEvent): void;
3909
+ /**
3910
+ * Dán khi tiêu điểm đang ở vùng. Có tệp thì nhận và `stopPropagation` — khung
3911
+ * cha (`ol-file-upload`) cũng nghe `paste`, không chặn là nhận hai lần. Không
3912
+ * có tệp (dán chữ) thì để yên cho trình duyệt.
3913
+ */
3914
+ protected onPaste(event: ClipboardEvent): void;
3915
+ protected onDrop(event: DragEvent): void;
3916
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFileDropzone, never>;
3917
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFileDropzone, "ol-file-dropzone", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "ctaLabel": { "alias": "ctaLabel"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "compact": { "alias": "compact"; "required": false; "isSignal": true; }; "describedBy": { "alias": "describedBy"; "required": false; "isSignal": true; }; "pasteHint": { "alias": "pasteHint"; "required": false; "isSignal": true; }; }, { "pick": "pick"; "filesDropped": "filesDropped"; }, never, never, true, never>;
3918
+ }
3919
+
3920
+ /**
3921
+ * Toán phóng/kéo ảnh — hàm thuần, không đụng DOM, để test chính xác từng px.
3922
+ *
3923
+ * Ảnh vẽ bằng `transform: translate(x, y) scale(s)`, gốc biến đổi ở TÂM ảnh.
3924
+ * Một điểm `p` của ảnh (tính từ tâm, đơn vị px lúc chưa phóng) hiện ra ở
3925
+ * `q = t + s·p` so với tâm khung. Phóng quanh điểm `q` (chỗ bấm / con trỏ /
3926
+ * giữa hai ngón) nghĩa là giữ `q` đứng yên: `p = (q − t) / s`, `t' = q − s'·p`.
3927
+ */
3928
+ interface ZoomState {
3929
+ scale: number;
3930
+ x: number;
3931
+ y: number;
3932
+ }
3933
+
3934
+ /** Một ảnh trong trình xem. */
3935
+ interface OlPreviewImage {
3936
+ src: string;
3937
+ /** Chữ thay thế — cũng hiện làm chú thích dưới ảnh. */
3938
+ alt?: string;
3939
+ }
3940
+ /**
3941
+ * Xem ảnh phóng to trên lớp phủ toàn màn (lightbox) — một ảnh hoặc lướt qua
3942
+ * nhiều ảnh.
3943
+ *
3944
+ * - Nhiều ảnh: nút ‹ › hai bên, số thứ tự "2 / 5", phím ← → chuyển ảnh, vòng quanh.
3945
+ * - Phóng to/thu nhỏ (1×–4×): bấm ảnh để phóng 2× đúng chỗ bấm, bấm lại để về;
3946
+ * nút − / + trên thanh, phím + − 0; cuộn chuột / chụm trackpad quanh con
3947
+ * trỏ; chụm hai ngón trên điện thoại. Đang phóng thì kéo để xem chỗ khác.
3948
+ * - Escape, nút ✕ hoặc bấm ra nền tối để đóng. Tiêu điểm bị giữ trong lớp phủ
3949
+ * và trả về chỗ cũ khi đóng; nền trang khoá cuộn (`OlFocusTrapService`).
3950
+ * - Escape đi qua ngăn xếp `OlOverlayContainer`: mở trình xem trong modal rồi
3951
+ * nhấn Escape chỉ đóng trình xem, modal còn nguyên.
3952
+ *
3953
+ * Dùng riêng được (`[(open)]` + `[images]` + `[(index)]`); `ol-file-upload`
3954
+ * chế độ `image`/`images` dùng nó để xem ảnh vừa chọn.
3955
+ */
3956
+ declare class OlImagePreview {
3957
+ readonly images: _angular_core.InputSignal<OlPreviewImage[]>;
3958
+ readonly open: _angular_core.ModelSignal<boolean>;
3959
+ /** Ảnh đang xem, tính từ 0. */
3960
+ readonly index: _angular_core.ModelSignal<number>;
3961
+ readonly closed: _angular_core.OutputEmitterRef<void>;
3962
+ private readonly panel;
3963
+ private readonly thumbStrip;
3964
+ private readonly imageEl;
3965
+ protected readonly zoom: _angular_core.WritableSignal<ZoomState>;
3966
+ protected readonly dragging: _angular_core.WritableSignal<boolean>;
3967
+ protected readonly zoomPercent: _angular_core.Signal<number>;
3968
+ protected readonly canZoomIn: _angular_core.Signal<boolean>;
3969
+ protected readonly canZoomOut: _angular_core.Signal<boolean>;
3970
+ protected readonly imageStyle: _angular_core.Signal<string>;
3971
+ protected readonly imageClasses: _angular_core.Signal<string>;
3972
+ /** Ngón tay / chuột đang ấn trên ảnh, theo pointerId. */
3973
+ private readonly pointers;
3974
+ private pinchStart;
3975
+ private moved;
3976
+ /** Vừa kéo xong: cú `click` theo sau không được tính là bấm (phóng / đóng). */
3977
+ private suppressClick;
3978
+ private readonly focusTrap;
3979
+ private readonly overlayContainer;
3980
+ private releaseEscape;
3981
+ protected readonly count: _angular_core.Signal<number>;
3982
+ /** Kẹp chỉ số: danh sách ảnh có thể ngắn lại (vừa xoá ảnh) trong lúc đang mở. */
3983
+ protected readonly current: _angular_core.Signal<number>;
3984
+ protected readonly image: _angular_core.Signal<OlPreviewImage>;
3985
+ constructor();
3986
+ /** Ô ảnh nhỏ: ảnh đang xem rõ + viền trắng; ảnh khác mờ, rê chuột thì rõ. */
3987
+ protected thumbClasses(i: number): string;
3988
+ close(): void;
3989
+ next(): void;
3990
+ prev(): void;
3991
+ private go;
3992
+ protected onKeydown(event: KeyboardEvent): void;
3993
+ /** Bấm đúng nền tối (không phải ảnh hay nút) thì đóng. */
3994
+ protected onBackdropClick(event: MouseEvent): void;
3995
+ zoomIn(): void;
3996
+ zoomOut(): void;
3997
+ /** Bàn phím trên ảnh (Enter / Space): phóng 2× quanh tâm, hoặc về cỡ gốc. */
3998
+ toggleZoom(): void;
3999
+ resetZoom(): void;
4000
+ /** Kích thước ảnh khi CHƯA phóng (offsetWidth không tính transform). */
4001
+ private imageSize;
4002
+ /**
4003
+ * Điểm màn hình → toạ độ tính từ TÂM ảnh lúc chưa phóng. Hộp đã biến đổi có
4004
+ * tâm = tâm gốc + (x, y) — phóng quanh tâm không dời tâm.
4005
+ */
4006
+ private toCenter;
4007
+ private zoomAround;
4008
+ /** Bấm ảnh: đang ở cỡ gốc thì phóng 2× đúng chỗ bấm, đang phóng thì về. */
4009
+ protected onImageClick(event: MouseEvent): void;
4010
+ /** Cuộn chuột / chụm trackpad (trình duyệt gửi wheel + ctrlKey): phóng quanh con trỏ. */
4011
+ protected onWheel(event: WheelEvent): void;
4012
+ protected onPointerDown(event: PointerEvent): void;
4013
+ protected onPointerMove(event: PointerEvent): void;
4014
+ protected onPointerUp(event: PointerEvent): void;
4015
+ private pinchDistance;
4016
+ private releaseTraps;
4017
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlImagePreview, never>;
4018
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlImagePreview, "ol-image-preview", never, { "images": { "alias": "images"; "required": false; "isSignal": true; }; "open": { "alias": "open"; "required": false; "isSignal": true; }; "index": { "alias": "index"; "required": false; "isSignal": true; }; }, { "open": "openChange"; "index": "indexChange"; "closed": "closed"; }, never, never, true, never>;
4019
+ }
4020
+
4021
+ /**
4022
+ * - `single` — một tệp; đã có thì thẻ tệp + nút "Thay đổi"
4023
+ * - `multiple` — nhiều tệp dạng chip (👁 + tên + ✕), cuộn khi dài
4024
+ * - `image` — một ảnh bìa phủ kín khung; bấm ảnh để xem phóng to
4025
+ * - `images` — nhiều ảnh dạng lưới ô vuông; bấm ô để xem, lướt ‹ › qua các ảnh
4026
+ * - `list` — nhiều tệp dạng DÒNG có thanh tiến trình — Figma không có,
4027
+ * dùng khi tải tệp lớn cần báo tiến trình từng tệp
4028
+ */
4029
+ type FileUploadMode = 'single' | 'multiple' | 'image' | 'images' | 'list';
4030
+ /**
4031
+ * Khung tải tệp có tiêu đề — Figma `ol-file-input-form` "Button header + …"
4032
+ * (3587:6834 · 3592:6990 · 3603:2606 · 3611:3494).
4033
+ *
4034
+ * ## Component KHÔNG tự tải lên máy chủ
4035
+ *
4036
+ * Nó nhận tệp, kiểm định dạng/dung lượng/số lượng, thêm vào `files` với
4037
+ * `status: 'idle'` rồi phát `(filesAdded)`. Nơi gọi gửi HTTP và cập nhật
4038
+ * `status`/`progress` của từng tệp qua `[(files)]`. Để component tự gọi HTTP
4039
+ * thì nó phải biết URL, header xác thực, dạng phản hồi — ba thứ khác nhau ở
4040
+ * mỗi màn.
4041
+ */
4042
+ declare class OlFileUpload {
3051
4043
  readonly title: _angular_core.InputSignal<string>;
3052
- readonly description: _angular_core.InputSignal<string>;
3053
- readonly size: _angular_core.InputSignal<FileInputSize>;
3054
- /** Cỡ đã quy bí danh: `'md'` -> `'base'`. Template so với cái này. */
3055
- protected readonly resolvedSize: _angular_core.Signal<FileInputSize>;
4044
+ readonly required: _angular_core.InputSignal<boolean>;
4045
+ readonly mode: _angular_core.InputSignal<FileUploadMode>;
4046
+ /** Chuỗi `accept` của `<input type="file">` — cũng dùng để KIỂM tệp kéo thả. */
4047
+ readonly accept: _angular_core.InputSignal<string>;
4048
+ /** Byte. 0 → không giới hạn. */
4049
+ readonly maxSize: _angular_core.InputSignal<number>;
4050
+ /** Chỉ cho `multiple`/`list`. 0 → không giới hạn. */
4051
+ readonly maxFiles: _angular_core.InputSignal<number>;
4052
+ /** Nhãn nút ở đầu khung. Rỗng → tự chọn theo chế độ và trạng thái. */
4053
+ readonly buttonLabel: _angular_core.InputSignal<string>;
4054
+ /** Hiện nút 👁 trên từng tệp. */
4055
+ readonly previewable: _angular_core.InputSignal<boolean>;
4056
+ readonly disabled: _angular_core.InputSignal<boolean>;
3056
4057
  /**
3057
- * Padding theo size. Không dùng [class.p-1.5] trên template vì Angular
3058
- * cắt phần sau dấu chấm — nó render ra "p-1" (4px thay vì 6px).
4058
+ * Nhận tệp dán (Ctrl+V / ⌘V) ở BẤT KỲ ĐÂU trên trang, không chỉ khi tiêu
4059
+ * điểm ở trong khung. Chỉ bật khi màn có MỘT khung tải — hai khung cùng bật
4060
+ * thì một lần dán vào cả hai. Đang gõ trong ô nhập chữ thì bỏ qua.
3059
4061
  */
3060
- readonly paddingClass: _angular_core.Signal<"p-1" | "p-1.5" | "p-2">;
3061
- readonly accept: _angular_core.InputSignal<string>;
3062
- readonly multiple: _angular_core.InputSignal<boolean>;
3063
- readonly id: _angular_core.InputSignal<string>;
3064
- readonly selectedFiles: _angular_core.WritableSignal<File[]>;
3065
- readonly isDragging: _angular_core.WritableSignal<boolean>;
3066
- readonly filesChanged: _angular_core.OutputEmitterRef<File[]>;
3067
- onFileSelected(event: Event): void;
3068
- onDragOver(event: DragEvent): void;
3069
- onDragLeave(event: DragEvent): void;
3070
- onDrop(event: DragEvent): void;
3071
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFileInput, never>;
3072
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFileInput, "ol-file-input", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "dropzone": { "alias": "dropzone"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; }, { "filesChanged": "filesChanged"; }, never, never, true, never>;
4062
+ readonly pasteFromPage: _angular_core.InputSignal<boolean>;
4063
+ /** Dòng gợi ý dưới khung, ví dụ định dạng cho phép. */
4064
+ readonly helperText: _angular_core.InputSignal<string>;
4065
+ readonly files: _angular_core.ModelSignal<OlUploadFile[]>;
4066
+ /** Tệp VỪA nhận, đã qua kiểm — nơi gọi bắt đầu tải lên từ đây. */
4067
+ readonly filesAdded: _angular_core.OutputEmitterRef<File[]>;
4068
+ readonly fileRemove: _angular_core.OutputEmitterRef<OlUploadFile>;
4069
+ readonly filePreview: _angular_core.OutputEmitterRef<OlUploadFile>;
4070
+ readonly fileRetry: _angular_core.OutputEmitterRef<OlUploadFile>;
4071
+ /** Tệp bị loại kèm lý do — component đã tự hiện câu lỗi dưới khung. */
4072
+ readonly rejected: _angular_core.OutputEmitterRef<FileRejection[]>;
4073
+ private readonly fileInput;
4074
+ private readonly host;
4075
+ protected readonly uid: string;
4076
+ protected readonly errors: _angular_core.WritableSignal<string[]>;
4077
+ protected readonly isMultiple: _angular_core.Signal<boolean>;
4078
+ protected readonly isImageMode: _angular_core.Signal<boolean>;
4079
+ protected readonly hasFiles: _angular_core.Signal<boolean>;
4080
+ protected readonly limitReached: _angular_core.Signal<boolean>;
4081
+ protected readonly resolvedButtonLabel: _angular_core.Signal<string>;
4082
+ protected readonly hintText: _angular_core.Signal<string>;
4083
+ protected readonly describedBy: _angular_core.Signal<string>;
4084
+ /**
4085
+ * Ảnh xem trước cho chế độ `image`/`images`, theo id tệp: `url` có sẵn (ảnh
4086
+ * đã trên máy chủ) hoặc object URL tạo từ `File`.
4087
+ *
4088
+ * Object URL phải THU HỒI khi tệp bị xoá / component huỷ, không thì mỗi ảnh
4089
+ * rò một khối bộ nhớ bằng cả ảnh. Giữ theo id để thêm một ảnh không tạo lại
4090
+ * URL của các ảnh cũ (ảnh cũ sẽ nháy vì `src` đổi).
4091
+ */
4092
+ protected readonly imageUrls: _angular_core.WritableSignal<Record<string, string>>;
4093
+ private readonly objectUrls;
4094
+ /** Ảnh cho trình xem — chỉ tệp đã có URL. */
4095
+ protected readonly previewImages: _angular_core.Signal<OlPreviewImage[]>;
4096
+ protected readonly previewOpen: _angular_core.WritableSignal<boolean>;
4097
+ protected readonly previewIndex: _angular_core.WritableSignal<number>;
4098
+ constructor();
4099
+ /** Tạo URL cho ảnh mới, thu hồi URL của ảnh đã bị xoá. */
4100
+ private syncImageUrls;
4101
+ /** Mở trình xem tại ảnh của tệp này, và phát `(filePreview)`. */
4102
+ openPreview(file: OlUploadFile): void;
4103
+ openPicker(): void;
4104
+ protected onInputChange(event: Event): void;
4105
+ /** Nhận tệp từ hộp chọn hoặc kéo thả: kiểm, thêm vào `files`, phát sự kiện. */
4106
+ addFiles(incoming: File[]): void;
4107
+ /** Dán tệp khi tiêu điểm trong khung (nút, chip, dòng tệp). */
4108
+ protected onPaste(event: ClipboardEvent): void;
4109
+ /** Thả thêm tệp vào vùng chip đã có tệp. */
4110
+ protected onListDrop(event: DragEvent): void;
4111
+ protected removeFile(file: OlUploadFile): void;
4112
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFileUpload, never>;
4113
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFileUpload, "ol-file-upload", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "buttonLabel": { "alias": "buttonLabel"; "required": false; "isSignal": true; }; "previewable": { "alias": "previewable"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "pasteFromPage": { "alias": "pasteFromPage"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "files": { "alias": "files"; "required": false; "isSignal": true; }; }, { "files": "filesChange"; "filesAdded": "filesAdded"; "fileRemove": "fileRemove"; "filePreview": "filePreview"; "fileRetry": "fileRetry"; "rejected": "rejected"; }, never, ["[actions]"], true, never>;
3073
4114
  }
3074
4115
 
3075
4116
  declare class OlRange implements ControlValueAccessor {
@@ -3137,66 +4178,6 @@ declare class OlRange implements ControlValueAccessor {
3137
4178
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlRange, "ol-range", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "minValue": { "alias": "minValue"; "required": false; "isSignal": true; }; "maxValue": { "alias": "maxValue"; "required": false; "isSignal": true; }; "dualRange": { "alias": "dualRange"; "required": false; "isSignal": true; }; "min": { "alias": "min"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "step": { "alias": "step"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "showValue": { "alias": "showValue"; "required": false; "isSignal": true; }; "showTooltip": { "alias": "showTooltip"; "required": false; "isSignal": true; }; "showMinMaxLabels": { "alias": "showMinMaxLabels"; "required": false; "isSignal": true; }; "unit": { "alias": "unit"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "minValue": "minValueChange"; "maxValue": "maxValueChange"; "disabled": "disabledChange"; }, never, ["[slot=prefix]", "[slot=suffix]"], true, never>;
3138
4179
  }
3139
4180
 
3140
- /**
3141
- * Figma `ol-floating-label` #1149:50454 trục `Type` chỉ có
3142
- * `Default | With background | Bordered` — ánh xạ sang `outlined`/`filled`.
3143
- * `standard` không có trong thiết kế và template cũng **không có nhánh nào**
3144
- * cho nó (grep ra 0 lượt), nên truyền vào là ra y hệt `outlined`. Đã xoá.
3145
- */
3146
- type FloatingLabelVariant = 'outlined' | 'filled';
3147
- /**
3148
- * Figma `ol-floating-label` (#1149:50454) có `Size=[sm|base|lg|xl]`; code
3149
- * trước đây thiếu `xl`. Đo trên Figma, `xl` giống hệt `lg` trừ chiều cao
3150
- * (48 -> 52px) và padding dọc (12 -> 14px): cùng gap 8, cùng icon 20px,
3151
- * cùng cỡ chữ 16px, cùng bo góc 12px.
3152
- */
3153
- type FloatingLabelSize = 'sm' | 'base' | 'lg' | 'xl';
3154
- type FloatingLabelState = 'default' | 'error' | 'success';
3155
- declare class OlFloatingLabel implements ControlValueAccessor {
3156
- readonly value: _angular_core.ModelSignal<string>;
3157
- readonly label: _angular_core.InputSignal<string>;
3158
- readonly type: _angular_core.InputSignal<string>;
3159
- readonly variant: _angular_core.InputSignal<FloatingLabelVariant>;
3160
- readonly size: _angular_core.InputSignal<FloatingLabelSize>;
3161
- readonly state: _angular_core.InputSignal<FloatingLabelState>;
3162
- /**
3163
- * Khai bằng `model()` thay vì `input()` để Reactive Forms khoá được component:
3164
- * `setDisabledState()` cần `set()` giá trị, mà `input()` chỉ đọc. Cách dùng từ
3165
- * ngoài không đổi — `[disabled]="true"` và `disabled()` trong template vẫn như cũ.
3166
- */
3167
- readonly disabled: _angular_core.ModelSignal<boolean>;
3168
- readonly readonly: _angular_core.InputSignal<boolean>;
3169
- readonly helperText: _angular_core.InputSignal<string>;
3170
- readonly errorText: _angular_core.InputSignal<string>;
3171
- readonly id: _angular_core.InputSignal<string>;
3172
- readonly autocomplete: _angular_core.InputSignal<string>;
3173
- readonly labelBg: _angular_core.InputSignal<string>;
3174
- readonly prefixIcon: _angular_core.InputSignal<string>;
3175
- readonly suffixIcon: _angular_core.InputSignal<string>;
3176
- readonly prefixVariant: _angular_core.InputSignal<string>;
3177
- readonly suffixVariant: _angular_core.InputSignal<string>;
3178
- readonly hasPrefix: _angular_core.Signal<boolean>;
3179
- readonly hasSuffix: _angular_core.Signal<boolean>;
3180
- readonly isFocused: _angular_core.WritableSignal<boolean>;
3181
- readonly isFloated: _angular_core.Signal<boolean>;
3182
- private readonly generatedId;
3183
- protected readonly inputId: _angular_core.Signal<string>;
3184
- readonly helperId: _angular_core.Signal<string>;
3185
- readonly describedBy: _angular_core.Signal<string | null>;
3186
- readonly ariaInvalid: _angular_core.Signal<"true" | null>;
3187
- private onChange;
3188
- private onTouched;
3189
- onFocus(): void;
3190
- onBlur(): void;
3191
- onModelChange(val: string): void;
3192
- writeValue(val: string): void;
3193
- registerOnChange(fn: any): void;
3194
- registerOnTouched(fn: any): void;
3195
- setDisabledState(isDisabled: boolean): void;
3196
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlFloatingLabel, never>;
3197
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlFloatingLabel, "ol-floating-label", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "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; }; "autocomplete": { "alias": "autocomplete"; "required": false; "isSignal": true; }; "labelBg": { "alias": "labelBg"; "required": false; "isSignal": true; }; "prefixIcon": { "alias": "prefixIcon"; "required": false; "isSignal": true; }; "suffixIcon": { "alias": "suffixIcon"; "required": false; "isSignal": true; }; "prefixVariant": { "alias": "prefixVariant"; "required": false; "isSignal": true; }; "suffixVariant": { "alias": "suffixVariant"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; }, never, never, true, never>;
3198
- }
3199
-
3200
4181
  /**
3201
4182
  * Thang cỡ theo Figma: `sm | base | lg`. `'md'` là BÍ DANH của `'base'`,
3202
4183
  * giữ cho mã cũ chạy tiếp và sẽ bỏ ở bản sau — dùng `'base'` cho mã mới.
@@ -3367,6 +4348,26 @@ declare class OlTagInput implements ControlValueAccessor {
3367
4348
  readonly maxTags: _angular_core.InputSignal<number | undefined>;
3368
4349
  readonly id: _angular_core.InputSignal<string>;
3369
4350
  readonly size: _angular_core.InputSignal<TagInputSize>;
4351
+ /**
4352
+ * Nhãn nổi: `label` nằm TRONG khung ở dòng đầu, nổi lên khi focus, đang gõ
4353
+ * hoặc đã có thẻ. Khung giãn nhiều dòng — nhãn nổi không đẩy thẻ.
4354
+ */
4355
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
4356
+ /**
4357
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
4358
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
4359
+ */
4360
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
4361
+ /**
4362
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
4363
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
4364
+ * mép ô thẳng hàng.
4365
+ */
4366
+ readonly labelWidth: _angular_core.InputSignal<string>;
4367
+ protected readonly labelLayout: _angular_core.Signal<string>;
4368
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
4369
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
4370
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
3370
4371
  /**
3371
4372
  * Gom chiều cao + padding vào MỘT chuỗi thay vì nhiều `[class.X]`: hai class
3372
4373
  * cùng thuộc tính đều nằm trên thẻ thì trình duyệt chọn theo thứ tự luật CSS
@@ -3403,7 +4404,7 @@ declare class OlTagInput implements ControlValueAccessor {
3403
4404
  */
3404
4405
  protected markTouched(): void;
3405
4406
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTagInput, never>;
3406
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTagInput, "ol-tag-input", never, { "tags": { "alias": "tags"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "maxTags": { "alias": "maxTags"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, { "tags": "tagsChange"; "disabled": "disabledChange"; }, never, never, true, never>;
4407
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTagInput, "ol-tag-input", never, { "tags": { "alias": "tags"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "maxTags": { "alias": "maxTags"; "required": false; "isSignal": true; }; "id": { "alias": "id"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "required": false; "isSignal": true; }; }, { "tags": "tagsChange"; "disabled": "disabledChange"; }, never, never, true, never>;
3407
4408
  }
3408
4409
 
3409
4410
  declare class OlRichTextEditor implements ControlValueAccessor {
@@ -3476,6 +4477,26 @@ declare class OlTimepicker implements ControlValueAccessor {
3476
4477
  * `aria-labelledby` đã lo — không cần truyền cái này.
3477
4478
  */
3478
4479
  readonly ariaLabel: _angular_core.InputSignal<string>;
4480
+ /**
4481
+ * Nhãn nổi: `label` nằm TRONG khung (sau icon đồng hồ), nổi lên khi focus,
4482
+ * mở bảng chọn hoặc đã có giờ. Có addon thì nhãn nằm trong ô, không trùm addon.
4483
+ */
4484
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
4485
+ /**
4486
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
4487
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
4488
+ */
4489
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
4490
+ /**
4491
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
4492
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
4493
+ * mép ô thẳng hàng.
4494
+ */
4495
+ readonly labelWidth: _angular_core.InputSignal<string>;
4496
+ protected readonly labelLayout: _angular_core.Signal<string>;
4497
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
4498
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
4499
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
3479
4500
  readonly placeholder: _angular_core.InputSignal<string>;
3480
4501
  readonly format: _angular_core.InputSignal<TimepickerFormat>;
3481
4502
  readonly size: _angular_core.InputSignal<TimepickerSize>;
@@ -3587,7 +4608,7 @@ declare class OlTimepicker implements ControlValueAccessor {
3587
4608
  registerOnTouched(fn: any): void;
3588
4609
  setDisabledState(isDisabled: boolean): void;
3589
4610
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlTimepicker, never>;
3590
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTimepicker, "ol-timepicker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "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; }; "minuteStep": { "alias": "minuteStep"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "showPresets": { "alias": "showPresets"; "required": false; "isSignal": true; }; "addonType": { "alias": "addonType"; "required": false; "isSignal": true; }; "addonText": { "alias": "addonText"; "required": false; "isSignal": true; }; "addonIcon": { "alias": "addonIcon"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "timeChange": "timeChange"; "timestampChange": "timestampChange"; "isoChange": "isoChange"; "timeObjectChange": "timeObjectChange"; }, never, never, true, never>;
4611
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlTimepicker, "ol-timepicker", never, { "value": { "alias": "value"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "state": { "alias": "state"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "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; }; "minuteStep": { "alias": "minuteStep"; "required": false; "isSignal": true; }; "appendTo": { "alias": "appendTo"; "required": false; "isSignal": true; }; "showPresets": { "alias": "showPresets"; "required": false; "isSignal": true; }; "addonType": { "alias": "addonType"; "required": false; "isSignal": true; }; "addonText": { "alias": "addonText"; "required": false; "isSignal": true; }; "addonIcon": { "alias": "addonIcon"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "disabled": "disabledChange"; "timeChange": "timeChange"; "timestampChange": "timestampChange"; "isoChange": "isoChange"; "timeObjectChange": "timeObjectChange"; }, never, never, true, never>;
3591
4612
  }
3592
4613
 
3593
4614
  /**
@@ -3611,6 +4632,28 @@ declare class OlAutocomplete implements ControlValueAccessor {
3611
4632
  readonly value: _angular_core.ModelSignal<any>;
3612
4633
  readonly options: _angular_core.InputSignal<any[]>;
3613
4634
  readonly label: _angular_core.InputSignal<string>;
4635
+ /**
4636
+ * Nhãn nổi: `label` nằm TRONG khung (sau icon kính lúp), nổi lên khi focus,
4637
+ * đang gõ, mở danh sách hoặc đã chọn. Đa chọn: nổi lên không đẩy thẻ.
4638
+ */
4639
+ readonly floatingLabel: _angular_core.InputSignal<boolean>;
4640
+ /**
4641
+ * Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
4642
+ * (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
4643
+ */
4644
+ readonly labelPosition: _angular_core.InputSignal<LabelPosition>;
4645
+ /**
4646
+ * Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
4647
+ * `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
4648
+ * mép ô thẳng hàng.
4649
+ */
4650
+ readonly labelWidth: _angular_core.InputSignal<string>;
4651
+ protected readonly labelLayout: _angular_core.Signal<string>;
4652
+ protected readonly labelBeside: _angular_core.Signal<boolean>;
4653
+ /** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
4654
+ readonly floatingVariant: _angular_core.InputSignal<FloatingVariant>;
4655
+ /** Có giá trị để nhãn nổi: đã chọn (một hay nhiều) hoặc đang có chữ gõ. */
4656
+ protected readonly hasFloatValue: _angular_core.Signal<boolean>;
3614
4657
  readonly placeholder: _angular_core.InputSignal<string>;
3615
4658
  readonly labelKey: _angular_core.InputSignal<string>;
3616
4659
  readonly valueKey: _angular_core.InputSignal<string>;
@@ -3628,7 +4671,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3628
4671
  protected readonly resolvedSize: _angular_core.Signal<FieldSize>;
3629
4672
  /**
3630
4673
  * Đệm ngang theo BẢNG CHUẨN họ form. Chỉ dùng khi KHÔNG `multiple` —
3631
- * chế độ nhiều thẻ có đệm riêng (`px-1.5 py-1`) vì các thẻ tự mang đệm.
4674
+ * chế độ nhiều thẻ dùng `boxPaddingClass`.
3632
4675
  *
3633
4676
  * Ba lỗi cũ gộp làm một chỗ này (đo /autocomplete 2026-09-24):
3634
4677
  * sm `[class.px-2.5]` -> Angular CẮT ở dấu chấm, render `px-2` = 8px
@@ -3637,6 +4680,26 @@ declare class OlAutocomplete implements ControlValueAccessor {
3637
4680
  * xl có `px-4` nhưng nằm ở `[class.px-4]` rời, dễ lệch khỏi ba cỡ kia.
3638
4681
  */
3639
4682
  protected readonly fieldPaddingClass: _angular_core.Signal<string>;
4683
+ /**
4684
+ * Đệm khung. Chọn nhiều: icon kính lúp vẫn là phần tử đầu nên lề đầu theo
4685
+ * cỡ như ô đơn (bản cũ `ps-1.5` để icon cách mép 6px, ô đơn 12px — hai ô
4686
+ * cạnh nhau lệch nhau rõ). Lề cuối chừa chỗ cụm nút absolute: lề cỡ + icon
4687
+ * 16px + `FIELD_ICON_GAP` 8px.
4688
+ */
4689
+ protected readonly boxPaddingClass: _angular_core.Signal<string>;
4690
+ /**
4691
+ * Cụm nút cuối (xoá / chevron / spinner). Rộng icon 16px + `ps-0.5` để chữ
4692
+ * ↔ icon đúng 8px (`gap-1.5` của khung + 2px) — bản cũ `w-7` ra 18px.
4693
+ * `justify-end`: spinner 12px vẫn neo mép phải như icon.
4694
+ */
4695
+ protected readonly controlClass: _angular_core.Signal<string>;
4696
+ /**
4697
+ * Đã có thẻ (chọn nhiều): khi KHÔNG gõ, ô gõ co về 0 (`min-w-0`, vẫn
4698
+ * `flex-1`) nên luôn ở cùng dòng chip cuối — không đẻ ra một dòng trống.
4699
+ * Focus để gõ thì mới cần chỗ (`focus:min-w-16`), lúc đó xuống dòng là đúng.
4700
+ * Đo 2026-09-29: dòng chip cuối còn 43px, `min-w-12` (48px) vẫn rớt.
4701
+ */
4702
+ protected readonly hasChips: _angular_core.Signal<boolean>;
3640
4703
  /**
3641
4704
  * Cỡ chữ theo BẢNG CHUẨN. Trước đây `[class.text-base]` chỉ khai cho `lg`,
3642
4705
  * `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
@@ -3816,7 +4879,7 @@ declare class OlAutocomplete implements ControlValueAccessor {
3816
4879
  registerOnTouched(fn: any): void;
3817
4880
  setDisabledState(isDisabled: boolean): void;
3818
4881
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlAutocomplete, never>;
3819
- 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>;
4882
+ 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; }; "floatingLabel": { "alias": "floatingLabel"; "required": false; "isSignal": true; }; "labelPosition": { "alias": "labelPosition"; "required": false; "isSignal": true; }; "labelWidth": { "alias": "labelWidth"; "required": false; "isSignal": true; }; "floatingVariant": { "alias": "floatingVariant"; "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>;
3820
4883
  }
3821
4884
 
3822
4885
  declare class OlKbd {
@@ -4082,6 +5145,30 @@ declare class OlModalFooter {
4082
5145
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlModalFooter, "ol-modal-footer", never, { "layout": { "alias": "layout"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
4083
5146
  }
4084
5147
 
5148
+ /**
5149
+ * Mốc gập menu vào ☰, theo bề rộng THANH. `xl` (78rem ≈ 1248px) là mặc định cũ.
5150
+ * `md` (48rem = 768px) / `lg` (64rem = 1024px) cho thanh muốn giữ menu lâu hơn —
5151
+ * thường kèm `compactItems`, vì menu đầy đủ chữ hiếm khi vừa 768px.
5152
+ */
5153
+ type NavbarCollapseAt = 'md' | 'lg' | 'xl';
5154
+ /**
5155
+ * Ba nấc hiển thị khi `compactItems` bật — thanh ĐO chỗ trống thật và chọn nấc
5156
+ * đầy đủ nhất còn vừa, ghi vào `data-density` trên gốc `<nav>`:
5157
+ *
5158
+ * - `full` — đủ hết.
5159
+ * - `medium` — vẫn ĐỦ CHỮ và icon menu; bỏ phần PHỤ của nơi gọi.
5160
+ * - `text` — đủ chữ, bỏ icon đầu mục (−24px mỗi mục).
5161
+ * - `compact` — mục có icon chỉ còn icon (chữ `sr-only`, có `title`).
5162
+ *
5163
+ * Từ `medium` trở xuống gốc có thêm `data-condensed`: nơi gọi ẩn phần phụ
5164
+ * (dòng phụ dưới logo, nhãn "Năm học:"…) bằng MỘT class
5165
+ * `group-data-[condensed]/navbar:hidden`, không phải liệt kê từng nấc.
5166
+ *
5167
+ * Bản trước đổi sang icon ở mốc CỨNG 78rem: thanh 1180px chỉ thiếu 3px là vừa
5168
+ * đủ chữ mà vẫn thành icon, để trống một khoảng lớn giữa menu và nút (báo
5169
+ * 2026-09-28 kèm ảnh).
5170
+ */
5171
+ type NavbarDensity = 'full' | 'medium' | 'text' | 'compact';
4085
5172
  declare class OlNavbar {
4086
5173
  readonly brandLogo: _angular_core.InputSignal<string>;
4087
5174
  readonly brandName: _angular_core.InputSignal<string>;
@@ -4090,19 +5177,71 @@ declare class OlNavbar {
4090
5177
  readonly fluid: _angular_core.InputSignal<boolean>;
4091
5178
  readonly showSearch: _angular_core.InputSignal<boolean>;
4092
5179
  readonly searchPlaceholder: _angular_core.InputSignal<string>;
5180
+ /** Thanh hẹp hơn mốc này thì mục menu gập vào ☰. Xem `NavbarCollapseAt`. */
5181
+ readonly collapseAt: _angular_core.InputSignal<NavbarCollapseAt>;
5182
+ /**
5183
+ * Tự co theo chỗ trống thật (`full` → `medium` → `compact`, xem
5184
+ * `NavbarDensity`) thay vì gập menu. Thường đi kèm `collapseAt="md"`/`"lg"`.
5185
+ */
5186
+ readonly compactItems: _angular_core.InputSignal<boolean>;
4093
5187
  readonly isCollapsed: _angular_core.ModelSignal<boolean>;
4094
5188
  readonly onSearch: _angular_core.OutputEmitterRef<string>;
4095
5189
  readonly onToggleMobileMenu: _angular_core.OutputEmitterRef<boolean>;
4096
5190
  searchQuery: string;
4097
5191
  private readonly elementRef;
5192
+ private readonly mobilePanel;
5193
+ private readonly navWrap;
5194
+ private readonly destroyRef;
5195
+ /** Nơi gọi có khai `slot="mobile-links"` (hoặc `slot="mobile"`) riêng. */
5196
+ readonly hasMobileLinks: _angular_core.WritableSignal<boolean>;
5197
+ /** Panel ☰ có gì để hiện — không thì đừng vẽ một dải trống có padding. */
5198
+ readonly hasPanelContent: _angular_core.WritableSignal<boolean>;
5199
+ constructor();
5200
+ /** Chiều cao khối mục xếp dọc (0 khi không xếp dọc) → `--ol-nav-stack-h`. */
5201
+ private observeStackHeight;
5202
+ private observeDensity;
5203
+ /**
5204
+ * Thử từ nấc đầy đủ nhất xuống, dừng ở nấc đầu tiên vừa hàng. Đặt thuộc tính
5205
+ * rồi đọc kích thước NGAY (ép trình duyệt tính lại) nên không nháy khung nào
5206
+ * — trạng thái cuối được gán trước khi vẽ.
5207
+ *
5208
+ * Đo bằng `scrollWidth`: logo có `min-w-0 truncate`, bị ép co thì
5209
+ * `offsetWidth` nhỏ đi và phép đo báo "vừa" sai.
5210
+ */
5211
+ updateDensity(): void;
5212
+ private isStacked;
5213
+ /**
5214
+ * Đo lại mỗi lần mở ☰, không chỉ lúc đầu: nội dung chiếu vào có thể nằm
5215
+ * trong @if của nơi gọi và xuất hiện muộn.
5216
+ */
5217
+ private detectMobileSlots;
5218
+ /**
5219
+ * Khối mục menu. Khung ≥ 78rem: nằm ngang cạnh logo.
5220
+ *
5221
+ * Khung hẹp mà ☰ đang mở và KHÔNG có menu di động riêng: xuống dòng
5222
+ * (`col-span-full row-start-2`) và xếp dọc — chính các mục desktop thành menu
5223
+ * di động. Bản trước chỉ ẩn chúng, nên 5/7 thanh trên /navbar và /blocks bấm
5224
+ * ☰ ra bảng trống (đo iPhone 13 giả lập, 2026-09-28).
5225
+ */
5226
+ protected readonly bp: _angular_core.Signal<{
5227
+ row: string;
5228
+ wide: string;
5229
+ stackReset: string;
5230
+ hide: string;
5231
+ }>;
5232
+ /** Hàng trên: lưới ở khung hẹp (logo | nút), flex từ mốc gập trở lên. */
5233
+ readonly rowClasses: _angular_core.Signal<string>;
5234
+ readonly toggleClasses: _angular_core.Signal<string>;
5235
+ readonly navWrapClasses: _angular_core.Signal<string>;
5236
+ readonly mobilePanelClasses: _angular_core.Signal<string>;
4098
5237
  readonly navbarClasses: _angular_core.Signal<string>;
4099
- readonly containerClasses: _angular_core.Signal<"w-full px-4 sm:px-6" | "max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">;
5238
+ readonly containerClasses: _angular_core.Signal<"w-full px-4 @[40rem]:px-6" | "max-w-7xl mx-auto px-4 @[40rem]:px-6 @5xl:px-8">;
4100
5239
  toggleMobileMenu(): void;
4101
5240
  handleSearchSubmit(): void;
4102
5241
  handleSearchInput(event: Event): void;
4103
5242
  handleGlobalKeydown(event: KeyboardEvent): void;
4104
5243
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlNavbar, never>;
4105
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlNavbar, "ol-navbar", never, { "brandLogo": { "alias": "brandLogo"; "required": false; "isSignal": true; }; "brandName": { "alias": "brandName"; "required": false; "isSignal": true; }; "sticky": { "alias": "sticky"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "fluid": { "alias": "fluid"; "required": false; "isSignal": true; }; "showSearch": { "alias": "showSearch"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "isCollapsed": { "alias": "isCollapsed"; "required": false; "isSignal": true; }; }, { "isCollapsed": "isCollapsedChange"; "onSearch": "onSearch"; "onToggleMobileMenu": "onToggleMobileMenu"; }, never, ["[slot=brand], [brand]", "[slot=navigation], [navigation], ol-navbar-item, ol-nav-dropdown", "[slot=actions], [actions]", "[slot=mobile-user]", "[slot=mobile], [slot=mobile-links]", "[slot=mobile-actions]", "[slot=mobile-footer]"], true, never>;
5244
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlNavbar, "ol-navbar", never, { "brandLogo": { "alias": "brandLogo"; "required": false; "isSignal": true; }; "brandName": { "alias": "brandName"; "required": false; "isSignal": true; }; "sticky": { "alias": "sticky"; "required": false; "isSignal": true; }; "bordered": { "alias": "bordered"; "required": false; "isSignal": true; }; "fluid": { "alias": "fluid"; "required": false; "isSignal": true; }; "showSearch": { "alias": "showSearch"; "required": false; "isSignal": true; }; "searchPlaceholder": { "alias": "searchPlaceholder"; "required": false; "isSignal": true; }; "collapseAt": { "alias": "collapseAt"; "required": false; "isSignal": true; }; "compactItems": { "alias": "compactItems"; "required": false; "isSignal": true; }; "isCollapsed": { "alias": "isCollapsed"; "required": false; "isSignal": true; }; }, { "isCollapsed": "isCollapsedChange"; "onSearch": "onSearch"; "onToggleMobileMenu": "onToggleMobileMenu"; }, never, ["[slot=brand], [brand]", "[slot=navigation], [navigation], ol-navbar-item, ol-nav-dropdown", "[slot=actions], [actions]", "[slot=mobile-user]", "[slot=mobile], [slot=mobile-links]", "[slot=mobile-actions]", "[slot=mobile-footer]"], true, never>;
4106
5245
  }
4107
5246
 
4108
5247
  declare class OlNavbarItem {
@@ -4125,7 +5264,40 @@ declare class OlNavDropdown {
4125
5264
  readonly label: _angular_core.InputSignal<string>;
4126
5265
  readonly icon: _angular_core.InputSignal<string | null>;
4127
5266
  readonly alignRight: _angular_core.InputSignal<boolean>;
5267
+ /**
5268
+ * Đang ở một trang CON của menu này (ví dụ "Lớp học" khi đang xem 10A1).
5269
+ * Cùng hình với `ol-navbar-item` active — nền brand nhạt, chữ brand đậm —
5270
+ * để thanh điều hướng luôn chỉ ra người dùng đang ở phân hệ nào.
5271
+ */
5272
+ readonly active: _angular_core.InputSignal<boolean>;
5273
+ protected readonly triggerClasses: _angular_core.Signal<string>;
5274
+ /**
5275
+ * Số cột của panel. `1` (mặc định) là menu thường; từ `2` là mega-menu.
5276
+ *
5277
+ * Mega-menu dùng cho menu có nhiều nhóm mục — thay vì một cột dài phải cuộn,
5278
+ * chia thành cột đứng cạnh nhau và người dùng quét mắt một lượt là thấy hết.
5279
+ *
5280
+ * Dưới `md` LUÔN về một cột bất kể giá trị: hai cột trên màn 390px là mỗi
5281
+ * cột 150px, chữ vỡ dòng giữa từ. Ngưỡng theo breakpoint chứ không theo bề
5282
+ * ngang panel vì panel treo tự do, không có container để đo.
5283
+ */
5284
+ readonly columns: _angular_core.InputSignal<1 | 2 | 3 | 4>;
5285
+ /**
5286
+ * Panel rộng bằng cả navbar thay vì bám mép nút.
5287
+ *
5288
+ * Mega-menu nhiều cột mà vẫn bám mép nút thì nửa panel tràn ra ngoài màn
5289
+ * hình khi nút nằm bên phải. Trải hết chiều ngang thì luôn nằm trong khung.
5290
+ */
5291
+ readonly fullWidth: _angular_core.InputSignal<boolean>;
4128
5292
  readonly isOpen: _angular_core.WritableSignal<boolean>;
5293
+ /**
5294
+ * Class của panel.
5295
+ *
5296
+ * Gom vào MỘT chuỗi thay vì nhiều `[class.x]` rời: hai class cùng thuộc tính
5297
+ * thì trình duyệt chọn theo thứ tự luật trong file CSS chứ không theo thứ tự
5298
+ * viết, kết quả đổi theo bản build.
5299
+ */
5300
+ protected readonly panelClasses: _angular_core.Signal<string>;
4129
5301
  private readonly elementRef;
4130
5302
  private readonly overlayContainer;
4131
5303
  constructor();
@@ -4133,9 +5305,120 @@ declare class OlNavDropdown {
4133
5305
  close(): void;
4134
5306
  handleClickOutside(event: MouseEvent): void;
4135
5307
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlNavDropdown, never>;
4136
- 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>;
5308
+ 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; }; "active": { "alias": "active"; "required": false; "isSignal": true; }; "columns": { "alias": "columns"; "required": false; "isSignal": true; }; "fullWidth": { "alias": "fullWidth"; "required": false; "isSignal": true; }; }, {}, never, ["[slot=trigger], [trigger]", "*"], true, never>;
5309
+ }
5310
+
5311
+ /** Một mục trong menu thả xuống. */
5312
+ interface OlMenubarItem {
5313
+ id: string;
5314
+ label: string;
5315
+ /** Chữ phụ bên phải: phím tắt, ví dụ "⌘S". */
5316
+ shortcut?: string;
5317
+ disabled?: boolean;
5318
+ /** Kẻ ngăn NGAY TRƯỚC mục này. */
5319
+ separatorBefore?: boolean;
5320
+ }
5321
+ /** Một menu trên thanh: nhãn trên thanh + danh sách mục. */
5322
+ interface OlMenubarMenu {
5323
+ id: string;
5324
+ label: string;
5325
+ items: OlMenubarItem[];
5326
+ disabled?: boolean;
5327
+ }
5328
+ /**
5329
+ * Thanh menu kiểu ứng dụng để bàn — Tệp · Sửa · Xem.
5330
+ *
5331
+ * Chưa có node Figma — dựng theo design system, dùng lại cách trình bày của
5332
+ * `ol-dropdown-item`.
5333
+ *
5334
+ * ## Khác `ol-dropdown` ở ba điểm, và đó là lý do có component riêng
5335
+ *
5336
+ * 1. **Mở một menu rồi RÊ sang menu kế là mở luôn**, không phải bấm lại. Đây
5337
+ * là hành vi người dùng đã quen từ phần mềm để bàn; thiếu nó thì thanh menu
5338
+ * chỉ là mấy cái dropdown đặt cạnh nhau.
5339
+ * 2. **Mũi tên trái/phải đi giữa các menu**, lên/xuống đi trong menu đang mở.
5340
+ * WAI-ARIA quy định `role="menubar"` phải làm vậy.
5341
+ * 3. **Một menu mở thì menu khác đóng** — không bao giờ có hai panel cùng lúc.
5342
+ *
5343
+ * ## Vì sao KHÔNG treo panel ra `<body>`
5344
+ *
5345
+ * Thanh menu thường nằm ngay dưới tiêu đề cửa sổ, không bị `overflow: hidden`
5346
+ * cắt như dropdown trong bảng. Giữ panel trong cây DOM thì Tab đi vào được tự
5347
+ * nhiên, không phải xử lý riêng như `ol-dropdown` đã phải làm.
5348
+ *
5349
+ * ## Lưu ý khi viết test
5350
+ *
5351
+ * Rê chuột giữa các menu cần bố cục thật để tính vị trí. jsdom không có, nên
5352
+ * spec khoá phần bàn phím và trạng thái mở/đóng; hành vi rê nghiệm thu trên
5353
+ * trình duyệt.
5354
+ */
5355
+ declare class OlMenubar {
5356
+ private readonly elementRef;
5357
+ private readonly destroyRef;
5358
+ private readonly overlayContainer;
5359
+ readonly menus: _angular_core.InputSignal<OlMenubarMenu[]>;
5360
+ readonly ariaLabel: _angular_core.InputSignal<string>;
5361
+ /** `id` của menu đang mở. Rỗng nghĩa là không menu nào mở. */
5362
+ private readonly openId;
5363
+ readonly openMenuId: _angular_core.Signal<string>;
5364
+ readonly itemSelect: _angular_core.OutputEmitterRef<{
5365
+ menu: OlMenubarMenu;
5366
+ item: OlMenubarItem;
5367
+ }>;
5368
+ private releaseEscape;
5369
+ constructor();
5370
+ protected toggle(menu: OlMenubarMenu): void;
5371
+ /**
5372
+ * Rê sang menu khác khi ĐANG có menu mở thì mở luôn menu đó.
5373
+ *
5374
+ * Không mở khi chưa menu nào mở — nếu không, lướt chuột ngang thanh là bung
5375
+ * ra một panel người dùng không định mở.
5376
+ */
5377
+ protected onMenuHover(menu: OlMenubarMenu): void;
5378
+ private open;
5379
+ private closeAll;
5380
+ protected onItemClick(menu: OlMenubarMenu, item: OlMenubarItem): void;
5381
+ /**
5382
+ * Mũi tên trái/phải đi giữa các menu — WAI-ARIA quy định cho `role="menubar"`.
5383
+ *
5384
+ * Vòng lại đầu khi tới cuối: thanh menu là một dải kín, không phải danh sách
5385
+ * có điểm dừng. Người dùng nhấn phải liên tục thì quay về menu đầu.
5386
+ *
5387
+ * Dùng `(keydown)` trần rồi tự lọc phím vì thẻ đã có `(click)` — hai listener
5388
+ * khác kiểu sự kiện trên cùng thẻ thì Angular suy `$event` theo cái đầu.
5389
+ */
5390
+ protected onTriggerKeydown(event: KeyboardEvent, index: number): void;
5391
+ /**
5392
+ * Menu kế tiếp **còn dùng được** theo hướng `step`, vòng lại khi tới biên.
5393
+ *
5394
+ * Phải bỏ qua menu `disabled`: nút gốc có thuộc tính `disabled` nên
5395
+ * `.focus()` trên nó KHÔNG có tác dụng nào — tiêu điểm đứng nguyên chỗ cũ và
5396
+ * người dùng tưởng bàn phím hỏng. Spec bắt đúng lỗi này khi menu cuối bị vô
5397
+ * hiệu: nhấn End là tiêu điểm rơi về `<body>`.
5398
+ *
5399
+ * Trả `null` khi KHÔNG có menu nào dùng được — lúc đó không `preventDefault`,
5400
+ * để phím về lại hành vi mặc định của trình duyệt.
5401
+ *
5402
+ * Nhận `from` ngoài dải (-1 hoặc `length`) để Home/End dùng chung một phép:
5403
+ * Home là "từ trước mục đầu, đi sang phải", End là "từ sau mục cuối, đi sang
5404
+ * trái".
5405
+ */
5406
+ private nextEnabled;
5407
+ protected triggerClasses(menu: OlMenubarMenu): string;
5408
+ protected itemClasses(item: OlMenubarItem): string;
5409
+ protected readonly hasMenus: _angular_core.Signal<boolean>;
5410
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlMenubar, never>;
5411
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlMenubar, "ol-menubar", never, { "menus": { "alias": "menus"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "itemSelect": "itemSelect"; }, never, never, true, never>;
4137
5412
  }
4138
5413
 
5414
+ /**
5415
+ * Bố cục thanh phân trang (Figma `ol-pagination` #1149:81850).
5416
+ *
5417
+ * `with-dropdown` là BÍ DANH: ≡ `text-buttons` + `[withDropdown]="true"`. Ô
5418
+ * chọn cỡ trang nay là cờ riêng gắn được vào MỌI type; giữ giá trị này vì gói
5419
+ * đã publish. (Chú thích đặt ở đây, không chen giữa các giá trị: guard
5420
+ * `check:gia-tri-ma-mau` đọc union sẽ đứt ở khối chú thích.)
5421
+ */
4139
5422
  type PaginationType = 'default' | 'arrows' | 'dropdown-buttons' | 'text-buttons' | 'two-buttons' | 'buttons-helper' | 'with-dropdown' | 'with-input' | 'simple-input'
4140
5423
  /**
4141
5424
  * `grouped` và `separated` trước chỉ có ở `PaginationVariant`. Thêm vào đây
@@ -4180,6 +5463,17 @@ declare class OlPagination {
4180
5463
  readonly showText: _angular_core.InputSignal<boolean>;
4181
5464
  readonly text: _angular_core.InputSignal<string>;
4182
5465
  readonly showInfo: _angular_core.InputSignal<boolean>;
5466
+ /**
5467
+ * Thêm ô chọn số dòng mỗi trang ("10 mỗi trang") vào BẤT KỲ type nào — cạnh
5468
+ * dãy số, cạnh hai nút, cạnh ô nhập trang… Trước đây nó chỉ có ở một type
5469
+ * riêng (`with-dropdown`), muốn nó cạnh `arrows` hay `two-buttons` là không
5470
+ * được. Danh sách lấy từ `pageSizeOptions`, đổi thì phát `(pageChange)` về 1.
5471
+ */
5472
+ readonly withDropdown: _angular_core.InputSignal<boolean>;
5473
+ /**
5474
+ * @deprecated Dùng `withDropdown`. Bí danh: bật là thêm ô chọn cỡ trang. Khi
5475
+ * không đặt `type`, nó vẫn chọn bố cục `with-dropdown` như trước.
5476
+ */
4183
5477
  readonly showSizeChanger: _angular_core.InputSignal<boolean>;
4184
5478
  readonly showQuickJumper: _angular_core.InputSignal<boolean>;
4185
5479
  readonly pageChange: _angular_core.OutputEmitterRef<number>;
@@ -4193,6 +5487,10 @@ declare class OlPagination {
4193
5487
  * xảy ra. `type` đặt tường minh vẫn thắng; cờ chỉ điền vào chỗ trống.
4194
5488
  */
4195
5489
  readonly effectiveType: _angular_core.Signal<PaginationType>;
5490
+ /** Bố cục vẽ ra: `with-dropdown` là bí danh nên quy về `text-buttons`. */
5491
+ readonly layoutType: _angular_core.Signal<PaginationType>;
5492
+ /** Có vẽ ô chọn cỡ trang không — cờ riêng, độc lập với type. */
5493
+ readonly hasPageSizeDropdown: _angular_core.Signal<boolean>;
4196
5494
  /**
4197
5495
  * Trang đang xem — gộp `pageIndex` và bí danh `currentPage`.
4198
5496
  *
@@ -4264,7 +5562,7 @@ declare class OlPagination {
4264
5562
  onPageSizeSelect(event: Event): void;
4265
5563
  handleJump(): void;
4266
5564
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlPagination, never>;
4267
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlPagination, "ol-pagination", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "total": { "alias": "total"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "pageIndex": { "alias": "pageIndex"; "required": false; "isSignal": true; }; "currentPage": { "alias": "currentPage"; "required": false; "isSignal": true; }; "totalPagesInput": { "alias": "totalPages"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "showText": { "alias": "showText"; "required": false; "isSignal": true; }; "text": { "alias": "text"; "required": false; "isSignal": true; }; "showInfo": { "alias": "showInfo"; "required": false; "isSignal": true; }; "showSizeChanger": { "alias": "showSizeChanger"; "required": false; "isSignal": true; }; "showQuickJumper": { "alias": "showQuickJumper"; "required": false; "isSignal": true; }; }, { "pageSize": "pageSizeChange"; "pageIndex": "pageIndexChange"; "currentPage": "currentPageChange"; "pageChange": "pageChange"; }, never, never, true, never>;
5565
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlPagination, "ol-pagination", never, { "type": { "alias": "type"; "required": false; "isSignal": true; }; "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "total": { "alias": "total"; "required": false; "isSignal": true; }; "pageSize": { "alias": "pageSize"; "required": false; "isSignal": true; }; "pageIndex": { "alias": "pageIndex"; "required": false; "isSignal": true; }; "currentPage": { "alias": "currentPage"; "required": false; "isSignal": true; }; "totalPagesInput": { "alias": "totalPages"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "showText": { "alias": "showText"; "required": false; "isSignal": true; }; "text": { "alias": "text"; "required": false; "isSignal": true; }; "showInfo": { "alias": "showInfo"; "required": false; "isSignal": true; }; "withDropdown": { "alias": "withDropdown"; "required": false; "isSignal": true; }; "showSizeChanger": { "alias": "showSizeChanger"; "required": false; "isSignal": true; }; "showQuickJumper": { "alias": "showQuickJumper"; "required": false; "isSignal": true; }; }, { "pageSize": "pageSizeChange"; "pageIndex": "pageIndexChange"; "currentPage": "currentPageChange"; "pageChange": "pageChange"; }, never, never, true, never>;
4268
5566
  }
4269
5567
 
4270
5568
  type PopoverPlacement = 'top' | 'bottom' | 'left' | 'right';
@@ -4320,6 +5618,29 @@ declare class OlPopover {
4320
5618
  private cleanupAutoUpdate;
4321
5619
  /** Hàm trả panel về chỗ cũ khi đóng. */
4322
5620
  private detachPanel;
5621
+ /**
5622
+ * Selector cho phần tử nhận được tiêu điểm.
5623
+ *
5624
+ * `tabindex="-1"` bị loại: đó là dấu hiệu tác giả cố ý đưa phần tử ra khỏi
5625
+ * đường Tab. `:disabled` cũng loại — trình duyệt không cho nó nhận tiêu điểm.
5626
+ */
5627
+ private static readonly FOCUSABLE;
5628
+ /**
5629
+ * Phần tử nhận tiêu điểm bên trong `root`, theo thứ tự nhìn thấy.
5630
+ *
5631
+ * Lọc phần ẩn bằng `[hidden]`/`[aria-hidden]` chứ KHÔNG `offsetParent`:
5632
+ * `offsetParent` là `null` với mọi phần tử trong `position: fixed` — mà panel
5633
+ * nổi hay dùng đúng kiểu định vị đó — nên danh sách sẽ rỗng và toàn bộ xử lý
5634
+ * Tab lặng lẽ không chạy.
5635
+ */
5636
+ private focusablesIn;
5637
+ /**
5638
+ * Phần tử nhận tiêu điểm đứng ngay sau `element` trong thứ tự Tab của trang.
5639
+ *
5640
+ * Loại chính panel ra: nó treo cuối `<body>` nên nếu tính cả thì "phần tử sau
5641
+ * nút trigger" lại rơi vào panel vừa đóng.
5642
+ */
5643
+ private nextFocusableAfter;
4323
5644
  constructor();
4324
5645
  toggle(): void;
4325
5646
  open(): void;
@@ -4607,6 +5928,99 @@ declare class OlRating {
4607
5928
  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>;
4608
5929
  }
4609
5930
 
5931
+ type OlResizableDirection = 'horizontal' | 'vertical';
5932
+ /**
5933
+ * Hai khung chia nhau một vùng, kéo vạch giữa để đổi tỉ lệ.
5934
+ *
5935
+ * Chưa có node Figma — dựng theo design system: vạch kéo dùng `bg-default`,
5936
+ * sáng lên `bg-brand` khi rê chuột hoặc đang kéo.
5937
+ *
5938
+ * ## Vì sao tỉ lệ phần trăm chứ không pixel
5939
+ *
5940
+ * Người dùng đổi cỡ cửa sổ thì hai khung phải giãn theo. Lưu pixel thì khung
5941
+ * trái giữ nguyên còn khung phải nuốt toàn bộ phần thêm — tỉ lệ người dùng
5942
+ * chọn biến mất.
5943
+ *
5944
+ * ## Bắt chuột trên `document`, không trên vạch
5945
+ *
5946
+ * Kéo nhanh thì con trỏ rời khỏi vạch trước khi trình duyệt kịp gửi sự kiện
5947
+ * tiếp theo; nghe trên chính vạch thì kéo đứt đoạn. Nghe trên `document` lúc
5948
+ * đang kéo và gỡ ngay khi thả.
5949
+ *
5950
+ * `setPointerCapture` giải quyết đúng việc này nhưng không có trong jsdom, nên
5951
+ * spec sẽ không chạy được nhánh kéo — dùng `document` thì cả hai môi trường
5952
+ * cùng một đường mã.
5953
+ *
5954
+ * ## Bàn phím
5955
+ *
5956
+ * Vạch là `role="separator"` có `tabindex`, mũi tên đổi 2% mỗi lần, Home/End
5957
+ * về hai biên. Không có phần này thì người dùng bàn phím không đổi được tỉ lệ
5958
+ * — và một khung chia đôi không kéo được thì chỉ là hai `<div>` cạnh nhau.
5959
+ *
5960
+ * ## Lưu ý khi viết test
5961
+ *
5962
+ * jsdom không tính bố cục: `getBoundingClientRect()` trả 0 cho mọi thứ, nên
5963
+ * phép quy đổi toạ độ chuột sang phần trăm luôn ra `NaN` hoặc 0. Spec khoá
5964
+ * phần bàn phím và ràng buộc min/max (tính thuần số); việc kéo chuột nghiệm
5965
+ * thu trên trình duyệt.
5966
+ */
5967
+ declare class OlResizable {
5968
+ private readonly elementRef;
5969
+ private readonly destroyRef;
5970
+ readonly direction: _angular_core.InputSignal<OlResizableDirection>;
5971
+ /** Tỉ lệ khung ĐẦU, tính bằng phần trăm. Hai chiều để nơi dùng lưu lại được. */
5972
+ readonly ratio: _angular_core.ModelSignal<number>;
5973
+ /** Chặn dưới và chặn trên, phần trăm. Khung nào cũng không nhỏ hơn `minRatio`. */
5974
+ readonly minRatio: _angular_core.InputSignal<number>;
5975
+ readonly maxRatio: _angular_core.InputSignal<number>;
5976
+ /** Bước đổi mỗi lần nhấn mũi tên, phần trăm. */
5977
+ readonly keyboardStep: _angular_core.InputSignal<number>;
5978
+ readonly disabled: _angular_core.InputSignal<boolean>;
5979
+ readonly ariaLabel: _angular_core.InputSignal<string>;
5980
+ /**
5981
+ * Bắn khi thả chuột hoặc nhấn phím — KHÔNG bắn liên tục lúc đang kéo.
5982
+ *
5983
+ * Tên là `resizeEnd` chứ không `ratioChange`: `model()` đã tự sinh
5984
+ * `ratioChange` cho binding hai chiều, khai thêm output trùng tên là lỗi
5985
+ * biên dịch NG1054. Hai thứ cũng khác nghĩa — `ratioChange` bắn mỗi lần giá
5986
+ * trị đổi (kể cả từng nhịp kéo), còn `resizeEnd` chỉ bắn khi người dùng đã
5987
+ * thả tay, đúng lúc nên lưu tỉ lệ xuống máy chủ.
5988
+ */
5989
+ readonly resizeEnd: _angular_core.OutputEmitterRef<number>;
5990
+ private readonly dragging;
5991
+ private releaseDrag;
5992
+ constructor();
5993
+ /** Ép tỉ lệ vào khoảng cho phép. Tách ra để spec khoá được mà không cần DOM. */
5994
+ clamp(value: number): number;
5995
+ protected onPointerDown(event: PointerEvent): void;
5996
+ private stopDrag;
5997
+ private applyFromPointer;
5998
+ protected onKeydown(event: KeyboardEvent): void;
5999
+ protected readonly rootClasses: _angular_core.Signal<"flex h-full w-full" | "flex h-full w-full flex-col">;
6000
+ /** Kích thước khung đầu — `flex-basis` phần trăm, khung sau lấp phần còn lại. */
6001
+ protected readonly firstStyle: _angular_core.Signal<string>;
6002
+ /**
6003
+ * Vạch chia.
6004
+ *
6005
+ * ## Vùng bấm rộng hơn vạch nhìn thấy
6006
+ *
6007
+ * Vạch chỉ dày 6px thì đúng thẩm mỹ nhưng vùng bấm 6px vi phạm WCAG 2.5.8
6008
+ * (ngưỡng 24px) — đo trên trình duyệt ra `6×188px`. Nên vạch nhận
6009
+ * `w-6`/`h-6` để vùng bấm đủ 24px, còn phần MÀU nằm ở `::before` chỉ
6010
+ * 6px ở giữa. Người dùng thấy vạch mảnh, tay và con trỏ thì có 24px để bắt.
6011
+ *
6012
+ * `::before` khai trong `handleBarClasses()` và đặt qua một `<span>` con chứ
6013
+ * không dùng biến thể `before:` của Tailwind: `before:` cần nhiều class cho
6014
+ * cùng một hình và khó đọc hơn một phần tử thật.
6015
+ */
6016
+ protected readonly handleClasses: _angular_core.Signal<string>;
6017
+ /** Phần MÀU của vạch — mảnh 6px, nằm giữa vùng bấm 24px. */
6018
+ protected readonly handleBarClasses: _angular_core.Signal<string>;
6019
+ protected readonly ariaOrientation: _angular_core.Signal<"vertical" | "horizontal">;
6020
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlResizable, never>;
6021
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlResizable, "ol-resizable", never, { "direction": { "alias": "direction"; "required": false; "isSignal": true; }; "ratio": { "alias": "ratio"; "required": false; "isSignal": true; }; "minRatio": { "alias": "minRatio"; "required": false; "isSignal": true; }; "maxRatio": { "alias": "maxRatio"; "required": false; "isSignal": true; }; "keyboardStep": { "alias": "keyboardStep"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, { "ratio": "ratioChange"; "resizeEnd": "resizeEnd"; }, never, ["[first]", "[second]"], true, never>;
6022
+ }
6023
+
4610
6024
  /**
4611
6025
  * Kiểu dữ liệu cho `ol-app-nav` — menu điều hướng Ôn Luyện.
4612
6026
  *
@@ -5402,13 +6816,13 @@ declare class OlToastContainer {
5402
6816
  * cùng một union, tách thành hai kiểu riêng là để người dùng phải nhớ chúng
5403
6817
  * bằng nhau.
5404
6818
  */
5405
- type OlTooltipTheme = 'dark' | 'light' | 'brand';
6819
+ type OlTooltipTheme = 'subtle' | 'dark' | 'light' | 'brand';
5406
6820
  /** Hướng hiện tooltip so với phần tử đích. */
5407
6821
  type OlTooltipPosition = 'top' | 'bottom' | 'left' | 'right';
5408
6822
  declare class OlTooltip {
5409
6823
  /** Tooltip content text */
5410
6824
  readonly content: _angular_core.InputSignal<string>;
5411
- /** Theme variant */
6825
+ /** Theme variant — mặc định `subtle` (nền Primary 50). */
5412
6826
  readonly theme: _angular_core.InputSignal<OlTooltipTheme>;
5413
6827
  /** Tooltip ID for ARIA */
5414
6828
  readonly tooltipId: _angular_core.InputSignal<string>;
@@ -5585,6 +6999,134 @@ declare class OlIconService {
5585
6999
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
5586
7000
  }
5587
7001
 
7002
+ type OlIconStackSize = 'sm' | 'base' | 'lg';
7003
+ /**
7004
+ * Chồng nhiều icon hoặc logo lên nhau, kèm số đếm "+N".
7005
+ *
7006
+ * Chưa có node Figma — dựng theo `ol-avatar-group`, component cùng cơ chế đã
7007
+ * có trong thư viện. Khác ở chỗ: avatar là ảnh người nên luôn tròn, còn icon
7008
+ * thường là biểu tượng dịch vụ hay môn học nên mặc định bo vừa (`rounded-lg`)
7009
+ * và có nền để icon một màu vẫn tách khỏi icon bên dưới.
7010
+ *
7011
+ * ## Vòng viền là thứ làm nên hiệu ứng chồng
7012
+ *
7013
+ * Mỗi ô có `ring-2 ring-buffer` — vòng viền màu nền trang, không phải viền
7014
+ * màu. Nhờ nó hai ô chồng nhau vẫn thấy rõ ranh giới. Bỏ vòng này thì các ô
7015
+ * dính thành một khối nhoè.
7016
+ *
7017
+ * `ring-buffer` chứ không `ring-white`: ở chế độ tối nền không phải trắng, và
7018
+ * viền trắng trên nền đen là đường kẻ sáng chói quanh mỗi icon.
7019
+ *
7020
+ * ## Thứ tự chồng
7021
+ *
7022
+ * Mặc định ô ĐẦU nằm trên — giống xếp bài. `reverse` lật lại cho ô cuối nằm
7023
+ * trên, dùng khi ô cuối là thứ cần nhấn (icon "thêm", trạng thái mới nhất).
7024
+ *
7025
+ * Làm bằng `flex-row-reverse` cộng đảo thứ tự dữ liệu chứ không bằng
7026
+ * `z-index`: `z-index` cần stacking context và sẽ hỏng khi nơi gọi đặt
7027
+ * component vào một phần tử có `transform` hay `opacity`.
7028
+ */
7029
+ declare class OlIconStack {
7030
+ readonly size: _angular_core.InputSignal<OlIconStackSize>;
7031
+ /** Số đếm "+N" ở cuối. `null` thì không vẽ. */
7032
+ readonly counter: _angular_core.InputSignal<string | number | null>;
7033
+ readonly counterAriaLabel: _angular_core.InputSignal<string>;
7034
+ /** Lật thứ tự chồng: ô CUỐI nằm trên thay vì ô đầu. */
7035
+ readonly reverse: _angular_core.InputSignal<boolean>;
7036
+ /**
7037
+ * Bo góc của từng ô. `full` cho logo tròn, `lg` cho biểu tượng vuông.
7038
+ *
7039
+ * Áp lên CON qua biến thể `*:` của Tailwind, nên nơi gọi chỉ việc đặt
7040
+ * `<ol-icon>` hay `<img>` vào là xong.
7041
+ */
7042
+ readonly rounded: _angular_core.InputSignal<"lg" | "full">;
7043
+ protected readonly rootClasses: _angular_core.Signal<string>;
7044
+ /**
7045
+ * Ô đếm "+N".
7046
+ *
7047
+ * `text-heading` chứ KHÔNG `text-body`: tổ hợp `bg-neutral-quaternary` +
7048
+ * `text-body` đo được 6.10 ở chế độ sáng nhưng chỉ 3.96 ở chế độ tối — dưới
7049
+ * ngưỡng WCAG 4.5. Đây là kết luận đã đo ở `ol-avatar-group` (BUG-016 mục 5),
7050
+ * theo tiền lệ sẵn có chứ không đoán lại.
7051
+ *
7052
+ * Chú thích để Ở ĐÂY chứ không trong template: template là một chuỗi
7053
+ * template literal, nên dấu backtick trong chú thích HTML đóng chuỗi sớm và
7054
+ * TS báo lỗi ở dòng chẳng liên quan (đã mắc khi viết file này).
7055
+ */
7056
+ protected readonly counterClasses: _angular_core.Signal<string>;
7057
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlIconStack, never>;
7058
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlIconStack, "ol-icon-stack", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "counter": { "alias": "counter"; "required": false; "isSignal": true; }; "counterAriaLabel": { "alias": "counterAriaLabel"; "required": false; "isSignal": true; }; "reverse": { "alias": "reverse"; "required": false; "isSignal": true; }; "rounded": { "alias": "rounded"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
7059
+ }
7060
+
7061
+ type OlItemSize = 'sm' | 'base' | 'lg';
7062
+ type OlItemAlign = 'start' | 'center';
7063
+ /**
7064
+ * Một dòng nội dung: phần dẫn đầu · tiêu đề + mô tả · phần hành động.
7065
+ *
7066
+ * Chưa có node Figma — dựng theo design system, lấy khoảng cách và cỡ chữ từ
7067
+ * `ol-checkbox-radio-form` (component gần nhất đang có cấu trúc này).
7068
+ *
7069
+ * ## Vì sao KHÔNG mở rộng `ol-list-group-item`
7070
+ *
7071
+ * Cái đó là `<li>` và phải nằm trong `<ul>` của `ol-list-group` — HTML quy
7072
+ * định vậy. Còn dòng nội dung thì hay đứng một mình trong thẻ, trong ngăn kéo,
7073
+ * hay trong kết quả tìm kiếm. Nhồi cấu trúc vào `<li>` là buộc mọi nơi dùng
7074
+ * phải bọc thêm `<ul>` không có ý nghĩa ngữ nghĩa nào.
7075
+ *
7076
+ * Hai component dùng chung cách xếp; `ol-item` lo phần bên trong, đặt ở đâu là
7077
+ * việc của nơi gọi.
7078
+ *
7079
+ * ## Ba khe, KHÔNG phải ba input
7080
+ *
7081
+ * `[lead]` và `[actions]` là khe cắm chứ không phải input nhận tên icon: phần
7082
+ * dẫn đầu có thể là icon, avatar, ảnh bìa hay số thứ tự; phần hành động có thể
7083
+ * là nút, huy hiệu, hay một dòng chữ. Nhận tên icon thì mỗi lần cần thứ khác
7084
+ * lại phải thêm một input.
7085
+ *
7086
+ * Riêng `icon` giữ lại làm lối tắt cho ca hay gặp nhất. Nó nằm TRONG cùng khe
7087
+ * `[lead]`, sau nội dung chiếu — nơi gọi truyền cả hai thì có hai phần dẫn đầu
7088
+ * cạnh nhau, nhưng đó là ca hiếm và không đáng thêm selector CSS để chặn.
7089
+ */
7090
+ declare class OlItem {
7091
+ readonly title: _angular_core.InputSignal<string>;
7092
+ readonly description: _angular_core.InputSignal<string>;
7093
+ /** Lối tắt cho phần dẫn đầu dạng icon. Khe `[lead]` đè lên nó. */
7094
+ readonly icon: _angular_core.InputSignal<string>;
7095
+ readonly size: _angular_core.InputSignal<OlItemSize>;
7096
+ /**
7097
+ * Căn phần dẫn đầu và hành động theo chiều dọc.
7098
+ *
7099
+ * `start` khi mô tả dài hai ba dòng — icon căn giữa một khối cao trông như
7100
+ * trôi lơ lửng. `center` khi chỉ có tiêu đề.
7101
+ */
7102
+ readonly align: _angular_core.InputSignal<OlItemAlign>;
7103
+ /** Bấm được: thêm hiệu ứng hover và con trỏ tay, phát `itemClick`. */
7104
+ readonly interactive: _angular_core.InputSignal<boolean>;
7105
+ readonly disabled: _angular_core.InputSignal<boolean>;
7106
+ /** Biến cả dòng thành liên kết. Bỏ qua khi `disabled`. */
7107
+ readonly href: _angular_core.InputSignal<string | null>;
7108
+ readonly itemClick: _angular_core.OutputEmitterRef<MouseEvent>;
7109
+ /**
7110
+ * Enter và Space kích hoạt dòng `interactive`, như một `<button>` thật.
7111
+ *
7112
+ * Dùng `(keydown)` trần rồi tự lọc phím, KHÔNG `(keydown.enter)`: hai
7113
+ * listener khác kiểu sự kiện trên cùng một thẻ thì Angular suy `$event` theo
7114
+ * cái đầu — `(click)` đứng trước nên `$event` thành `MouseEvent` và TS báo
7115
+ * lỗi ở dòng `keydown`.
7116
+ *
7117
+ * `preventDefault` cho Space, nếu không trang cuộn xuống một màn cùng lúc.
7118
+ */
7119
+ protected onKeyActivate(event: KeyboardEvent): void;
7120
+ protected onClick(event: MouseEvent): void;
7121
+ protected readonly rootClasses: _angular_core.Signal<string>;
7122
+ protected readonly titleClasses: _angular_core.Signal<string>;
7123
+ protected readonly descriptionClasses: _angular_core.Signal<string>;
7124
+ /** Cỡ icon lối tắt, theo cỡ dòng. */
7125
+ protected readonly iconSize: _angular_core.Signal<number>;
7126
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<OlItem, never>;
7127
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<OlItem, "ol-item", never, { "title": { "alias": "title"; "required": false; "isSignal": true; }; "description": { "alias": "description"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "align": { "alias": "align"; "required": false; "isSignal": true; }; "interactive": { "alias": "interactive"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "href": { "alias": "href"; "required": false; "isSignal": true; }; }, { "itemClick": "itemClick"; }, never, ["[lead]", "*", "[actions]"], true, never>;
7128
+ }
7129
+
5588
7130
  type OlBottomNavPosition = 'fixed' | 'relative';
5589
7131
  declare class OlBottomNav {
5590
7132
  readonly bordered: _angular_core.InputSignal<boolean>;
@@ -6423,6 +7965,24 @@ declare class OlFocusTrapService {
6423
7965
  * @param autoFocus Tự động focus vào element đầu tiên (default: true)
6424
7966
  */
6425
7967
  trap(element: HTMLElement, autoFocus?: boolean): void;
7968
+ /**
7969
+ * Chọn phần tử nhận focus khi overlay mở.
7970
+ *
7971
+ * Bản trước lấy phần tử focus được ĐẦU TIÊN theo thứ tự DOM. Header đứng
7972
+ * trước nội dung nên mở modal/drawer nào tiêu điểm cũng rơi vào nút ✕ —
7973
+ * người dùng phải Tab qua nút đóng mới tới ô nhập đầu tiên, và lỡ Enter là
7974
+ * đóng mất hộp thoại vừa mở (báo 2026-09-28).
7975
+ *
7976
+ * Thứ tự ưu tiên:
7977
+ * 1. phần tử nơi gọi đánh dấu `autofocus` / `data-autofocus` — trình duyệt
7978
+ * chỉ tôn trọng `autofocus` lúc tải trang, không phải lúc chèn hộp thoại,
7979
+ * nên service tự làm
7980
+ * 2. phần tử đầu tiên KHÔNG phải nút đóng (`[data-ol-dismiss]`) — thường là ô
7981
+ * nhập đầu tiên trong nội dung; nội dung không có gì bấm được thì tới nút
7982
+ * đầu tiên ở footer
7983
+ * 3. không còn gì khác mới về nút đóng, như cũ
7984
+ */
7985
+ private initialFocusTarget;
6426
7986
  /**
6427
7987
  * Release focus trap và restore focus về element trước đó
6428
7988
  */
@@ -6458,6 +8018,15 @@ declare class OlFocusTrapService {
6458
8018
  * Update danh sách focusable elements trong trap element
6459
8019
  */
6460
8020
  private updateFocusableElements;
8021
+ /**
8022
+ * Phần tử có đang hiện không.
8023
+ *
8024
+ * KHÔNG dùng `offsetParent !== null`: nó trả `null` cho chính phần tử
8025
+ * `position: fixed` (nút nổi trong drawer/modal) dù phần tử hiện rõ, và luôn
8026
+ * `null` trong jsdom — nên mọi phần tử bị lọc mất, service không focus được
8027
+ * gì và spec không viết được.
8028
+ */
8029
+ private isVisible;
6461
8030
  /**
6462
8031
  * Handle keyboard navigation trong trap
6463
8032
  */
@@ -6744,6 +8313,6 @@ declare class OlOverlayContainer {
6744
8313
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<any>;
6745
8314
  }
6746
8315
 
6747
- export { CHAT_BUBBLE_STATUS, FIELD_ASTERISK, FIELD_DISABLED, FIELD_HEIGHT, FIELD_HELPER, FIELD_HELPER_GAP, FIELD_LABEL, FIELD_LABEL_GAP, FIELD_MIN_HEIGHT, FIELD_PADDING_X, FIELD_RADIUS, FIELD_STATE, FIELD_TEXT, MONTHS_FULL_EN, MONTHS_FULL_VI, MONTHS_SHORT_VI, OlAccordion, OlAccordionItem, OlAlert, OlAlertDialog, OlAppNav, OlAspectRatio, OlAutocomplete, OlAvatar, OlAvatarGroup, OlBackdrop, OlBadge, OlBanner, OlBottomNav, OlBottomNavItem, OlBreadcrumb, OlBreadcrumbItem, OlButton, OlButtonGroup, OlCard, OlCarousel, OlCarouselSlide, OlChart, OlChatBubble, OlChatContainer, OlCheckbox, OlCheckboxRadioForm, OlCheckboxRadioGroup, OlClipboard, OlCollapse, OlCommand, OlDateRangePicker, OlDatepicker, OlDatepickerDirective, OlDatepickerFooter, OlDatepickerHeader, OlDatepickerPopup, OlDatepickerStateService, OlDayView, OlDeviceMockup, OlDownloadAppButton, OlDrawer, OlDrawerFooter, OlDrawerHeader, OlDropdown, OlDropdownHeader, OlDropdownItem, OlEmpty, OlFileInput, OlFloatingLabel, OlFocusTrapService, OlFooter, OlGallery, OlGalleryItem, OlHelper, OlIcon, OlIconService, OlIconShape, OlInput, OlInputCounter, OlJumbotron, OlKbd, OlLabel, OlListGroup, OlListGroupItem, OlLoadingOverlay, OlModal, OlModalFooter, OlModalHeader, OlMonthView, OlNavDropdown, OlNavbar, OlNavbarItem, OlOtpInput, OlOverlayContainer, OlPagination, OlPopover, OlPopoverContent, OlPositioningService, OlProgress, OlProgressBarService, OlProgressBarTop, OlRadio, OlRange, OlRating, OlRichTextEditor, OlScrollArea, OlSelect, OlSeparator, OlSidebar, OlSidebarGroup, OlSidebarItem, OlSidebarSubmenu, OlSkeleton, OlSpeedDial, OlSpeedDialItem, OlSpinner, OlStepItem, OlStepper, OlTabItem, OlTable, OlTableLoading, OlTableRow, OlTabs, OlTagInput, OlTextarea, OlTimeline, OlTimelineItem, OlTimepicker, OlToast, OlToastContainer, OlToastService, OlToggle, OlToggleGroup, OlToggleInput, OlTooltip, OlTooltipDirective, OlTree, OlTreeNodeItem, OlYearView, WEEKDAYS_SHORT_EN, WEEKDAYS_SHORT_VI, boDau, cloneDate, endOfDay, fieldSizeClasses, formatDate, generateMonthMatrix, generateMonthsMatrix, generateYearsMatrix, isBetween, isDateDisabled, isSameDay, isSameMonth, isSameYear, isToday, parseDate, startOfDay, stripDiacritics };
6748
- export type { AccordionVariant, AlertColor, AlertDialogTone, AlertLayout, AlertRounded, AlertType, AlertVariant, AppDevice, AppNavConfig, AppNavGroup, AppNavItem, AppNavUser, AppTheme, AutocompleteOption, AutocompleteSize, AutocompleteState, AvatarShape, AvatarSize, AvatarStatus, BadgeColor, BadgeShape, BadgeSize, BadgeTheme, BadgeType, BannerPosition, BannerVariant, BoxplotDataPoint, BreadcrumbItemStatus, BreadcrumbSeparator, BreadcrumbVariant, ButtonColor, ButtonGroupColor, ButtonGroupOrientation, ButtonGroupSize, ButtonSize, CardPadding, CardRadius, CardShadow, CardVariant, ChartDataPoint, ChartSeries, ChartType, ChatBubbleStatus, ChatSender, CheckboxSize, ChoiceControl, ChoiceControlPosition, ChoiceGroupLayout, ChoiceLayout, ChoiceShape, CommandGroup, CommandItem, DateCustomClass, DateRange, DatepickerPreset, DatepickerSelectionMode, DatepickerSize, DatepickerViewMode, DayCell, DeviceType, DrawerFooterLayout, DrawerPlacement, DrawerSize, DropdownAutoClose, DropdownPlacement, DropdownTrigger, EmptySize, FieldSize, FieldState, FileInputSize, FloatingLabelSize, FloatingLabelState, FloatingLabelVariant, HeatmapData, HelperFeature, HelperRule, HelperSize, HelperState, IconColor, IconShapeColor, IconShapeSize, IconShapeType, IconSize, IconVariant, InputCounterSize, InputSize, InputState, JumbotronVariant, ListGroupType, ModalFooterLayout, ModalPlacement, ModalSize, MonthCell, OlAvatarStatusPosition, OlBottomNavPosition, OlButtonType, OlCheckboxRadioFormAlign, OlCheckboxRadioGroupRole, OlDateRangePickerVariant, OlGalleryGap, OlGalleryRatio, OlHelperType, OlModalBackdrop, OlModalRole, OlSkeletonAnimation, OlSkeletonAspectRatio, OlSkeletonRounded, OlSkeletonType, OlSpeedDialTooltipPosition, OlTimepickerAddonType, OlToggleInputAlign, OlTreeNode, OlTreeNodeSelectionMode, OlTreeNodeToggleIcon, OlTreeSelectionMode, OlTreeToggleIcon, OtpInputSize, OverlayLayer, PaginationType, PaginationVariant, PopoverPlacement, PopoverTrigger, PositionedCoordinates, PositioningOptions, ProgressColor, ProgressLabelPosition, ProgressSize, RadarIndicator, RadioSize, RatingType, ScatterDataPoint, ScatterSeries, ScrollAreaAxis, ScrollAreaScrollbar, SelectOption, SelectSize, SelectState, SeparatorOrientation, SpeedDialAction, SpeedDialColor, SpeedDialDirection, SpeedDialTrigger, SpeedDialType, StepState, StepperBreakpoint, StepperOrientation, StepperVariant, TabsSize, TabsVariant, TagInputSize, TextareaState, TimeObject, TimepickerFormat, TimepickerSize, TimepickerState, Toast, ToastConfig, ToastPosition, ToggleColor, TogglePosition, ToggleSize, YearCell };
8316
+ export { CHAT_BUBBLE_STATUS, FIELD_ASTERISK, FIELD_CLEAR_RESERVE_END, FIELD_DISABLED, FIELD_HEIGHT, FIELD_HELPER, FIELD_HELPER_GAP, FIELD_ICON_GAP, FIELD_ICON_RESERVE_END, FIELD_ICON_RESERVE_START, FIELD_LABEL, FIELD_LABEL_BESIDE, FIELD_LABEL_GAP, FIELD_MIN_HEIGHT, FIELD_PADDING_END, FIELD_PADDING_START, FIELD_PADDING_X, FIELD_RADIUS, FIELD_STATE, FIELD_TEXT, FILE_KIND_META, FLOAT_LABEL_START, 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, OlDropdownSearch, OlDropdownState, OlEmpty, OlFileDropzone, OlFileInput, OlFileItem, OlFileUpload, OlFocusTrapService, OlFooter, OlFrame, OlGallery, OlGalleryItem, OlHelper, OlIcon, OlIconService, OlIconShape, OlIconStack, OlImagePreview, OlInput, OlInputCounter, OlItem, OlJumbotron, OlKbd, OlLabel, OlListGroup, OlListGroupItem, OlLoadingOverlay, OlMenubar, OlModal, OlModalFooter, OlModalHeader, OlMonthView, OlNavDropdown, OlNavbar, OlNavbarItem, OlOtpInput, OlOverlayContainer, OlPagination, OlPopover, OlPopoverContent, OlPositioningService, OlProgress, OlProgressBarService, OlProgressBarTop, OlRadio, OlRange, OlRating, OlResizable, OlRichTextEditor, OlScrollArea, OlScrollspy, OlSegmented, 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, PICKER_ICON_RESERVE_START, WEEKDAYS_SHORT_EN, WEEKDAYS_SHORT_VI, boDau, cloneDate, detectFileKind, endOfDay, fieldLabelLayout, fieldSizeClasses, fileExtension, filesFromClipboard, formatDate, formatFileSize, generateMonthMatrix, generateMonthsMatrix, generateYearsMatrix, isBetween, isDateDisabled, isEditableTarget, isSameDay, isSameMonth, isSameYear, isToday, matchesAccept, multiValuePadY, parseDate, pasteShortcutLabel, startOfDay, stripDiacritics, toUploadFile, validateFiles };
8317
+ 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, DropdownStateType, DropdownTrigger, EmptySize, FieldSize, FieldState, FileInputSize, FileItemVariant, FileKind, FileKindMeta, FileRejectReason, FileRejection, FileUploadMode, FileValidationResult, FileValidationRules, FloatingVariant, HeatmapData, HelperFeature, HelperRule, HelperSize, HelperState, IconColor, IconShapeColor, IconShapeSize, IconShapeType, IconSize, IconVariant, InputCounterSize, InputSize, InputState, JumbotronVariant, LabelPosition, ListGroupType, ModalFooterLayout, ModalPlacement, ModalSize, MonthCell, NavbarCollapseAt, NavbarDensity, OlAvatarStatusPosition, OlBottomNavPosition, OlButtonType, OlCheckboxRadioFormAlign, OlCheckboxRadioGroupRole, OlDateRangePickerVariant, OlFrameRounded, OlFrameSpacing, OlFrameVariant, OlGalleryGap, OlGalleryRatio, OlHelperType, OlIconStackSize, OlItemAlign, OlItemSize, OlMenubarItem, OlMenubarMenu, OlModalBackdrop, OlModalRole, OlPreviewImage, OlResizableDirection, OlScrollspyItem, OlScrollspyOrientation, OlSkeletonAnimation, OlSkeletonAspectRatio, OlSkeletonRounded, OlSkeletonType, OlSpeedDialTooltipPosition, OlTimepickerAddonType, OlToggleInputAlign, OlTreeNode, OlTreeNodeSelectionMode, OlTreeNodeToggleIcon, OlTreeSelectionMode, OlTreeToggleIcon, OlUploadFile, OtpInputSize, OverlayLayer, PaginationType, PaginationVariant, PopoverPlacement, PopoverTrigger, PositionedCoordinates, PositioningOptions, ProgressColor, ProgressLabelPosition, ProgressSize, RadarIndicator, RadioSize, RatingType, ScatterDataPoint, ScatterSeries, ScrollAreaAxis, ScrollAreaScrollbar, SegmentedColor, SegmentedOption, SegmentedSize, 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, UploadStatus, YearCell };
6749
8318
  //# sourceMappingURL=edmicro-olkit.d.ts.map