@edmicro/olkit 1.1.6 → 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.
- package/fesm2022/edmicro-olkit.mjs +1654 -576
- package/fesm2022/edmicro-olkit.mjs.map +1 -1
- package/olkit.css +1 -1
- package/package.json +1 -1
- package/types/edmicro-olkit.d.ts +595 -191
- package/types/edmicro-olkit.d.ts.map +1 -1
- package/version.txt +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { inject, Service, input, signal, computed, effect, ChangeDetectionStrategy, Component, forwardRef, model, output, contentChildren, HostListener, DOCUMENT, Injectable, DestroyRef, viewChild, ElementRef, Renderer2, ApplicationRef, EnvironmentInjector, createComponent, Directive, contentChild, ViewContainerRef, ContentChild, untracked, ViewChildren, ViewChild, afterNextRender } from '@angular/core';
|
|
2
|
+
import { inject, Service, input, signal, computed, effect, ChangeDetectionStrategy, Component, forwardRef, model, output, contentChildren, HostListener, DOCUMENT, Injectable, DestroyRef, viewChild, ElementRef, Renderer2, ApplicationRef, EnvironmentInjector, createComponent, Directive, contentChild, afterRenderEffect, ViewEncapsulation, ViewContainerRef, ContentChild, untracked, ViewChildren, ViewChild, afterNextRender } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/common';
|
|
4
4
|
import { CommonModule, NgTemplateOutlet, NgStyle } from '@angular/common';
|
|
5
5
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
@@ -1683,14 +1683,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
1683
1683
|
*
|
|
1684
1684
|
* Phải xử lý `đ`/`Đ` riêng: chúng KHÔNG phải `d` cộng dấu phụ nên `NFD` không
|
|
1685
1685
|
* tách ra được, `đăng nhập` sẽ không khớp `dang nhap` nếu bỏ bước này.
|
|
1686
|
+
*
|
|
1687
|
+
* Dùng chung cho mọi ô có tìm kiếm (`ol-command`, `ol-select`); `ol-command`
|
|
1688
|
+
* export lại để giữ API công khai từ 1.1.1.
|
|
1686
1689
|
*/
|
|
1687
1690
|
function stripDiacritics(text) {
|
|
1688
|
-
return text
|
|
1689
|
-
.toLowerCase()
|
|
1690
|
-
.normalize('NFD')
|
|
1691
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
1692
|
-
.replace(/đ/g, 'd');
|
|
1691
|
+
return text.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/đ/g, 'd');
|
|
1693
1692
|
}
|
|
1693
|
+
|
|
1694
1694
|
/**
|
|
1695
1695
|
* @deprecated Dùng `stripDiacritics`. Giữ lại vì hàm này ĐÃ publish trong
|
|
1696
1696
|
* `@edmicro/olkit@1.1.1` (kiểm bằng `npm pack` rồi đọc `types/*.d.ts` — có
|
|
@@ -5022,6 +5022,74 @@ const FIELD_PADDING_X = {
|
|
|
5022
5022
|
lg: 'px-3.5',
|
|
5023
5023
|
xl: 'px-4',
|
|
5024
5024
|
};
|
|
5025
|
+
/**
|
|
5026
|
+
* `FIELD_PADDING_X` tách hai đầu — cho ô có icon: phần tử ĐẦU TIÊN trong khung
|
|
5027
|
+
* (icon hoặc ô gõ) nhận lề đầu, phần tử CUỐI nhận lề cuối, ở giữa chỉ còn
|
|
5028
|
+
* `FIELD_ICON_GAP`.
|
|
5029
|
+
*/
|
|
5030
|
+
const FIELD_PADDING_START = {
|
|
5031
|
+
sm: 'ps-2.5',
|
|
5032
|
+
base: 'ps-3',
|
|
5033
|
+
lg: 'ps-3.5',
|
|
5034
|
+
xl: 'ps-4',
|
|
5035
|
+
};
|
|
5036
|
+
const FIELD_PADDING_END = {
|
|
5037
|
+
sm: 'pe-2.5',
|
|
5038
|
+
base: 'pe-3',
|
|
5039
|
+
lg: 'pe-3.5',
|
|
5040
|
+
xl: 'pe-4',
|
|
5041
|
+
};
|
|
5042
|
+
/**
|
|
5043
|
+
* Icon ↔ chữ trong khung: 8px, mọi cỡ — Figma `ol-input` (1149:46826) và
|
|
5044
|
+
* `ol-timepicker` (1149:52243) đều `itemSpacing: 8`.
|
|
5045
|
+
*
|
|
5046
|
+
* Chỉ một nguồn tạo khoảng này. Bản cũ của `ol-input` có cả `gap-2` ở khung
|
|
5047
|
+
* lẫn `ps-2.5`/`pe-2.5` ở ô gõ → icon cách chữ 18px (đo 2026-09-29).
|
|
5048
|
+
*/
|
|
5049
|
+
const FIELD_ICON_GAP = 'gap-2';
|
|
5050
|
+
/**
|
|
5051
|
+
* Đệm dọc của khung CHỌN NHIỀU (chip cao 24px). Nhãn nổi Bordered nằm vắt
|
|
5052
|
+
* qua viền trên, nửa dưới lấn vào khung ~10px — đệm 4px thường lệ để chip
|
|
5053
|
+
* nằm dưới chữ nhãn (đo 2026-09-29: đè 3px, 32/96 ô). Chừa 10px thì một
|
|
5054
|
+
* dòng chip vẫn cao đúng 40px: 1 + 10 + 24 + 4 + 1.
|
|
5055
|
+
*/
|
|
5056
|
+
function multiValuePadY(floatingOutlined) {
|
|
5057
|
+
return floatingOutlined ? 'pt-2.5 pb-1' : 'py-1';
|
|
5058
|
+
}
|
|
5059
|
+
/**
|
|
5060
|
+
* Chỗ dành cho icon 16px nằm `absolute` ở đầu/cuối khung: lề cỡ + 16px +
|
|
5061
|
+
* `FIELD_ICON_GAP` 8px. Dùng khi icon ra khỏi luồng (select, autocomplete
|
|
5062
|
+
* chọn nhiều) nên khung phải tự chừa chỗ.
|
|
5063
|
+
*/
|
|
5064
|
+
const FIELD_ICON_RESERVE_START = {
|
|
5065
|
+
sm: 'ps-8.5',
|
|
5066
|
+
base: 'ps-9',
|
|
5067
|
+
lg: 'ps-9.5',
|
|
5068
|
+
xl: 'ps-10',
|
|
5069
|
+
};
|
|
5070
|
+
const FIELD_ICON_RESERVE_END = {
|
|
5071
|
+
sm: 'pe-8.5',
|
|
5072
|
+
base: 'pe-9',
|
|
5073
|
+
lg: 'pe-9.5',
|
|
5074
|
+
xl: 'pe-10',
|
|
5075
|
+
};
|
|
5076
|
+
/** Chỗ cho nút xoá 14px (`close` cỡ xs) nằm `absolute` cuối ô: lề cỡ + 14 + 8. */
|
|
5077
|
+
const FIELD_CLEAR_RESERVE_END = {
|
|
5078
|
+
sm: 'pe-8',
|
|
5079
|
+
base: 'pe-8.5',
|
|
5080
|
+
lg: 'pe-9',
|
|
5081
|
+
xl: 'pe-9.5',
|
|
5082
|
+
};
|
|
5083
|
+
/**
|
|
5084
|
+
* Như `FIELD_ICON_RESERVE_START` nhưng cho icon lịch của datepicker, vốn to
|
|
5085
|
+
* theo cỡ ô (14 / 16 / 20 / 20px): lề cỡ + icon + 8px = 32 / 36 / 42 / 44.
|
|
5086
|
+
*/
|
|
5087
|
+
const PICKER_ICON_RESERVE_START = {
|
|
5088
|
+
sm: 'ps-8',
|
|
5089
|
+
base: 'ps-9',
|
|
5090
|
+
lg: 'ps-10.5',
|
|
5091
|
+
xl: 'ps-11',
|
|
5092
|
+
};
|
|
5025
5093
|
/**
|
|
5026
5094
|
* Cỡ chữ theo cỡ ô.
|
|
5027
5095
|
*
|
|
@@ -5067,6 +5135,24 @@ const FIELD_RADIUS = 'rounded-md';
|
|
|
5067
5135
|
const FIELD_LABEL = 'inline-flex items-center gap-1 text-sm font-medium text-heading';
|
|
5068
5136
|
/** Khoảng cách nhãn → ô. */
|
|
5069
5137
|
const FIELD_LABEL_GAP = 'mb-1.5';
|
|
5138
|
+
/**
|
|
5139
|
+
* Khung ngoài của host khi nhãn `left`: lưới 2 cột. Cột 1 = nhãn, rộng theo
|
|
5140
|
+
* `--ol-label-w` (input `labelWidth`, mặc định 8rem). Cột 2 = mọi thứ còn lại
|
|
5141
|
+
* — khung nhập, dòng gợi ý/lỗi — nên chữ lỗi thẳng mép ô, không chui xuống
|
|
5142
|
+
* dưới nhãn. `[&>ol-field-label]` cụ thể hơn `[&>*]` nên nhãn thắng cột.
|
|
5143
|
+
*/
|
|
5144
|
+
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';
|
|
5145
|
+
/** Lớp khung ngoài theo vị trí nhãn — rỗng khi nhãn trên hoặc nhãn nổi. */
|
|
5146
|
+
function fieldLabelLayout(position, floating) {
|
|
5147
|
+
if (floating)
|
|
5148
|
+
return '';
|
|
5149
|
+
switch (position) {
|
|
5150
|
+
case 'left':
|
|
5151
|
+
return FIELD_LABEL_BESIDE;
|
|
5152
|
+
case 'top':
|
|
5153
|
+
return '';
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5070
5156
|
/** Dấu sao bắt buộc. */
|
|
5071
5157
|
const FIELD_ASTERISK = 'text-fg-danger font-bold';
|
|
5072
5158
|
/**
|
|
@@ -5091,6 +5177,422 @@ const FIELD_HELPER_GAP = 'mt-1.5';
|
|
|
5091
5177
|
function fieldSizeClasses(size) {
|
|
5092
5178
|
return [FIELD_HEIGHT[size], FIELD_PADDING_X[size], FIELD_TEXT[size], FIELD_RADIUS].join(' ');
|
|
5093
5179
|
}
|
|
5180
|
+
/**
|
|
5181
|
+
* Vị trí ngang của nhãn KHI NỔI (px) = đệm ngang của ô (`FIELD_PADDING_X`) —
|
|
5182
|
+
* 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
|
|
5183
|
+
* cột icon). Phải là SỐ vì nhãn đặt bằng `style.left`, cùng hệ với vị trí nằm
|
|
5184
|
+
* yên (đo lúc chạy).
|
|
5185
|
+
*/
|
|
5186
|
+
const FLOAT_LABEL_START = { sm: 10, base: 12, lg: 14, xl: 16 };
|
|
5187
|
+
|
|
5188
|
+
/** `px-1` của nhãn, px. */
|
|
5189
|
+
const LABEL_PAD_X = 4;
|
|
5190
|
+
/**
|
|
5191
|
+
* Khung nhãn nổi DÙNG CHUNG cho mọi ô nhập — bọc quanh khung nhập của host.
|
|
5192
|
+
*
|
|
5193
|
+
* Host chỉ phải: (1) gắn class `ol-field-box` cho khung nhập, (2) báo
|
|
5194
|
+
* `hasValue`/`open`. Khung tự lo phần còn lại:
|
|
5195
|
+
*
|
|
5196
|
+
* - **Focus**: nghe `focusin`/`focusout` trên chính nó — kể cả combobox là `div`.
|
|
5197
|
+
* - **Vị trí nhãn nằm yên**: ĐO vị trí thật của ô chữ bên trong
|
|
5198
|
+
* (`input`/`textarea`/`[data-ol-field-text]`) thay vì mỗi host tự khai toạ độ.
|
|
5199
|
+
* Icon đầu, addon, chip, textarea nhiều dòng đều tự đúng.
|
|
5200
|
+
* - **Kiểu khung**: đè class của `.ol-field-box` theo kiểu (viền/gạch chân/nền).
|
|
5201
|
+
* - **Placeholder**: ẩn tới khi nhãn nổi — hai chữ cùng chỗ là chồng nhau.
|
|
5202
|
+
*
|
|
5203
|
+
* Tắt (`enabled = false`) thì khung trong suốt: chỉ chiếu nội dung, không nhãn,
|
|
5204
|
+
* không đè class — host giữ nguyên giao diện cũ.
|
|
5205
|
+
*
|
|
5206
|
+
* Số đo theo Figma `ol-floating-label` #1149:50454: nhãn luôn 14px/20px ở mọi
|
|
5207
|
+
* cỡ; Bordered nổi ở `top: -10px` (tâm nhãn trên viền); hai kiểu kia nổi thành
|
|
5208
|
+
* hàng 20px phía trên khung.
|
|
5209
|
+
*
|
|
5210
|
+
* LỆCH FIGMA CÓ CHỦ Ý: Figma cho `filled`/`standard` cao thêm 20px KHI focus
|
|
5211
|
+
* (khung Initial 40px, Focus 60px) — form nhảy xuống mỗi lần bấm vào ô. Ở đây
|
|
5212
|
+
* hàng nhãn luôn giữ sẵn (`pt-5`), khung không đổi cao.
|
|
5213
|
+
*/
|
|
5214
|
+
class OlFieldFrame {
|
|
5215
|
+
/** Bật nhãn nổi. Tắt thì khung trong suốt. */
|
|
5216
|
+
enabled = input(false, /* @ts-ignore */
|
|
5217
|
+
...(ngDevMode ? [{ debugName: "enabled" }] : /* istanbul ignore next */ []));
|
|
5218
|
+
label = input('', /* @ts-ignore */
|
|
5219
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
5220
|
+
/** Id của ô nhập — `<label for>`; combobox `div` thì bấm nhãn tự focus. */
|
|
5221
|
+
forId = input('', /* @ts-ignore */
|
|
5222
|
+
...(ngDevMode ? [{ debugName: "forId" }] : /* istanbul ignore next */ []));
|
|
5223
|
+
/** Id của nhãn — cho host nối `aria-labelledby` (combobox không nhận `for`). */
|
|
5224
|
+
labelId = input('', /* @ts-ignore */
|
|
5225
|
+
...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
5226
|
+
variant = input('outlined', /* @ts-ignore */
|
|
5227
|
+
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
5228
|
+
size = input('base', /* @ts-ignore */
|
|
5229
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
5230
|
+
state = input('default', /* @ts-ignore */
|
|
5231
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
5232
|
+
disabled = input(false, /* @ts-ignore */
|
|
5233
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
5234
|
+
required = input(false, /* @ts-ignore */
|
|
5235
|
+
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
5236
|
+
/** Ô có giá trị — nhãn nổi. **`0` là có giá trị**, host tự xét. */
|
|
5237
|
+
hasValue = input(false, /* @ts-ignore */
|
|
5238
|
+
...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
|
|
5239
|
+
/** Bảng chọn đang mở (select, picker) — nhãn nổi dù chưa chọn gì. */
|
|
5240
|
+
open = input(false, /* @ts-ignore */
|
|
5241
|
+
...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
5242
|
+
/**
|
|
5243
|
+
* Vị trí ngang của nhãn KHI NỔI (px), ghi đè bảng theo cỡ. Cho host có đệm
|
|
5244
|
+
* riêng ngoài thang `FIELD_PADDING_X` — `ol-textarea` không có cỡ, đệm 14px.
|
|
5245
|
+
*/
|
|
5246
|
+
floatStart = input(null, /* @ts-ignore */
|
|
5247
|
+
...(ngDevMode ? [{ debugName: "floatStart" }] : /* istanbul ignore next */ []));
|
|
5248
|
+
focused = signal(false, /* @ts-ignore */
|
|
5249
|
+
...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
5250
|
+
/** Trình duyệt vừa tự điền ô — xem chú thích `styles`. Người dùng gõ là tắt. */
|
|
5251
|
+
autofilled = signal(false, /* @ts-ignore */
|
|
5252
|
+
...(ngDevMode ? [{ debugName: "autofilled" }] : /* istanbul ignore next */ []));
|
|
5253
|
+
floated = computed(() => this.focused() || this.hasValue() || this.open() || this.autofilled(), /* @ts-ignore */
|
|
5254
|
+
...(ngDevMode ? [{ debugName: "floated" }] : /* istanbul ignore next */ []));
|
|
5255
|
+
/** Vị trí nhãn nằm yên, đo từ ô chữ bên trong (px, so với khung). */
|
|
5256
|
+
restX = signal(12, /* @ts-ignore */
|
|
5257
|
+
...(ngDevMode ? [{ debugName: "restX" }] : /* istanbul ignore next */ []));
|
|
5258
|
+
restY = signal(10, /* @ts-ignore */
|
|
5259
|
+
...(ngDevMode ? [{ debugName: "restY" }] : /* istanbul ignore next */ []));
|
|
5260
|
+
/**
|
|
5261
|
+
* Đã đo xong lần đầu. `transition` chỉ bật SAU đó: bật từ đầu thì mỗi lần
|
|
5262
|
+
* ô hiện ra (tải trang, mở modal, đổi tab) nhãn TRƯỢT từ vị trí mặc định tới
|
|
5263
|
+
* chỗ thật — e2e bắt được lệch 9–14px ngay sau khi vẽ.
|
|
5264
|
+
*/
|
|
5265
|
+
ready = signal(false, /* @ts-ignore */
|
|
5266
|
+
...(ngDevMode ? [{ debugName: "ready" }] : /* istanbul ignore next */ []));
|
|
5267
|
+
host = inject(ElementRef);
|
|
5268
|
+
constructor() {
|
|
5269
|
+
afterRenderEffect(() => {
|
|
5270
|
+
// Đo lại mỗi khi bật/tắt hoặc đổi nổi/yên: icon, chip đổi chỗ ô chữ.
|
|
5271
|
+
this.enabled();
|
|
5272
|
+
this.floated();
|
|
5273
|
+
this.size();
|
|
5274
|
+
this.variant();
|
|
5275
|
+
this.measure();
|
|
5276
|
+
});
|
|
5277
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
5278
|
+
const observer = new ResizeObserver(() => this.measure());
|
|
5279
|
+
observer.observe(this.host.nativeElement);
|
|
5280
|
+
inject(DestroyRef).onDestroy(() => observer.disconnect());
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
shellClasses = computed(() => {
|
|
5284
|
+
if (!this.enabled())
|
|
5285
|
+
return '';
|
|
5286
|
+
const variant = this.variant();
|
|
5287
|
+
// Đè class của khung host. `!` vì host đặt nền/viền bằng class thường và
|
|
5288
|
+
// hai bên cùng @layer utilities — thứ tự trong file CSS không đoán được.
|
|
5289
|
+
const box = '[&_.ol-field-box]:ring-0! [&_.ol-field-box]:shadow-none!';
|
|
5290
|
+
const placeholder = this.floated()
|
|
5291
|
+
? ''
|
|
5292
|
+
: ' [&_input]:placeholder:text-transparent [&_textarea]:placeholder:text-transparent [&_[data-ol-field-placeholder]]:invisible';
|
|
5293
|
+
const underline = '[&_.ol-field-box]:border-0! [&_.ol-field-box]:border-b-2! [&_.ol-field-box]:rounded-none!';
|
|
5294
|
+
switch (variant) {
|
|
5295
|
+
case 'outlined': {
|
|
5296
|
+
// Figma Bordered: nền trắng MỌI trạng thái, trạng thái chỉ đổi màu viền.
|
|
5297
|
+
const bg = this.disabled() ? '' : ' [&_.ol-field-box]:bg-neutral-primary!';
|
|
5298
|
+
return `relative ${box}${bg}${placeholder}`;
|
|
5299
|
+
}
|
|
5300
|
+
case 'filled': {
|
|
5301
|
+
// Nền phủ cả hàng nhãn KHI nổi; nằm yên thì chỉ phủ khung nhập — đúng
|
|
5302
|
+
// Figma Initial (một khối xám cao bằng ô) mà không đổi chiều cao.
|
|
5303
|
+
const fill = FILL_BG[this.state()];
|
|
5304
|
+
const where = this.floated()
|
|
5305
|
+
? ` ${fill.shell} rounded-t-lg [&_.ol-field-box]:bg-transparent!`
|
|
5306
|
+
: ` ${fill.box} [&_.ol-field-box]:rounded-t-lg!`;
|
|
5307
|
+
return `relative pt-5 transition-colors ${box} ${underline}${where}${placeholder}`;
|
|
5308
|
+
}
|
|
5309
|
+
case 'standard':
|
|
5310
|
+
// Chỉ gạch chân, không nền.
|
|
5311
|
+
return `relative pt-5 ${box} ${underline} [&_.ol-field-box]:bg-transparent!${placeholder}`;
|
|
5312
|
+
}
|
|
5313
|
+
}, /* @ts-ignore */
|
|
5314
|
+
...(ngDevMode ? [{ debugName: "shellClasses" }] : /* istanbul ignore next */ []));
|
|
5315
|
+
labelClasses = computed(() => {
|
|
5316
|
+
const motion = this.ready() ? ' transition-[top,left,color] duration-150 ease-out' : '';
|
|
5317
|
+
const base = `absolute z-10 px-1 max-w-[calc(100%-1.5rem)] truncate whitespace-nowrap text-sm leading-5 font-normal cursor-text select-none${motion}`;
|
|
5318
|
+
const color = this.disabled()
|
|
5319
|
+
? 'text-fg-disabled'
|
|
5320
|
+
: this.state() === 'error'
|
|
5321
|
+
? 'text-fg-danger'
|
|
5322
|
+
: this.state() === 'success'
|
|
5323
|
+
? 'text-fg-success'
|
|
5324
|
+
: this.floated() && this.focused()
|
|
5325
|
+
? 'text-fg-brand'
|
|
5326
|
+
: 'text-body-subtle';
|
|
5327
|
+
return `${base} ${color}`;
|
|
5328
|
+
}, /* @ts-ignore */
|
|
5329
|
+
...(ngDevMode ? [{ debugName: "labelClasses" }] : /* istanbul ignore next */ []));
|
|
5330
|
+
/**
|
|
5331
|
+
* Nền cắt viền của nhãn nổi Bordered. Nhãn nằm VẮT NGANG viền: nửa trên trên
|
|
5332
|
+
* nền trang, nửa dưới trên nền khung nhập — một màu duy nhất sai ở một nửa
|
|
5333
|
+
* khi hai nền khác nhau (ô khoá nền xám, ô đặt trên nền màu). Gradient cắt
|
|
5334
|
+
* đôi đúng giữa: trên = `--ol-field-surface` (nơi gọi báo), dưới = nền khung.
|
|
5335
|
+
*/
|
|
5336
|
+
cutout = computed(() => {
|
|
5337
|
+
if (this.variant() !== 'outlined' || !this.floated())
|
|
5338
|
+
return null;
|
|
5339
|
+
const page = 'var(--ol-field-surface, var(--color-neutral-primary))';
|
|
5340
|
+
// Khung khoá mang `opacity-60` → màu THẤY ĐƯỢC là xám khoá pha 60% với nền
|
|
5341
|
+
// trang, không phải `--color-disabled` nguyên màu (đo: lộ mảng xám đậm hơn).
|
|
5342
|
+
const box = this.disabled()
|
|
5343
|
+
? `color-mix(in srgb, var(--color-disabled) 60%, ${page})`
|
|
5344
|
+
: 'var(--color-neutral-primary)';
|
|
5345
|
+
return `linear-gradient(to bottom, ${page} 50%, ${box} 50%)`;
|
|
5346
|
+
}, /* @ts-ignore */
|
|
5347
|
+
...(ngDevMode ? [{ debugName: "cutout" }] : /* istanbul ignore next */ []));
|
|
5348
|
+
/**
|
|
5349
|
+
* Nhãn có `px-1` để nền cắt viền (Bordered) chừa 4px hai bên chữ — không có
|
|
5350
|
+
* thì đường viền chạm sát chữ. Lùi `left` đúng 4px để CHỮ vẫn thẳng mép chữ
|
|
5351
|
+
* trong ô. Đệm có ở mọi trạng thái (không bật/tắt theo `floated`) để chữ
|
|
5352
|
+
* không giật 4px lúc nhãn trượt lên.
|
|
5353
|
+
*/
|
|
5354
|
+
labelLeft = computed(() => (this.floated() ? this.floatX() : this.restX()) - LABEL_PAD_X, /* @ts-ignore */
|
|
5355
|
+
...(ngDevMode ? [{ debugName: "labelLeft" }] : /* istanbul ignore next */ []));
|
|
5356
|
+
/**
|
|
5357
|
+
* Vị trí nổi = đệm ô, tính từ mép TRONG viền. Bordered có viền trái 1px nên
|
|
5358
|
+
* cộng 1 — thiếu thì chữ nhãn lệch trái 1px so với chữ trong ô (đo
|
|
5359
|
+
* 2026-09-29: 20/20 ô). Filled/Standard không có viền hai bên.
|
|
5360
|
+
*/
|
|
5361
|
+
floatX = computed(() => (this.floatStart() ?? FLOAT_LABEL_START[this.size()]) +
|
|
5362
|
+
(this.variant() === 'outlined' ? 1 : 0), /* @ts-ignore */
|
|
5363
|
+
...(ngDevMode ? [{ debugName: "floatX" }] : /* istanbul ignore next */ []));
|
|
5364
|
+
labelTop = computed(() => {
|
|
5365
|
+
if (!this.floated())
|
|
5366
|
+
return this.restY();
|
|
5367
|
+
return this.variant() === 'outlined' ? -10 : 0;
|
|
5368
|
+
}, /* @ts-ignore */
|
|
5369
|
+
...(ngDevMode ? [{ debugName: "labelTop" }] : /* istanbul ignore next */ []));
|
|
5370
|
+
onAnimationStart(event) {
|
|
5371
|
+
if (event.animationName === 'ol-field-autofill')
|
|
5372
|
+
this.autofilled.set(true);
|
|
5373
|
+
}
|
|
5374
|
+
onFocusOut(event) {
|
|
5375
|
+
// Focus chuyển giữa hai phần tử TRONG khung (ô → nút xoá) thì vẫn là focus.
|
|
5376
|
+
const next = event.relatedTarget;
|
|
5377
|
+
if (next && this.host.nativeElement.contains(next))
|
|
5378
|
+
return;
|
|
5379
|
+
this.focused.set(false);
|
|
5380
|
+
}
|
|
5381
|
+
/** Combobox là `div` — `<label for>` không focus được nó, tự làm. */
|
|
5382
|
+
focusControl() {
|
|
5383
|
+
const id = this.forId();
|
|
5384
|
+
const target = id ? document.getElementById(id) : null;
|
|
5385
|
+
if (target && target !== document.activeElement)
|
|
5386
|
+
target.focus();
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* Nhãn nằm yên đặt đúng chỗ chữ sẽ hiện: mép trái = mép chữ của ô (tính cả
|
|
5390
|
+
* đệm trái), giữa theo chiều dọc = giữa DÒNG ĐẦU. Textarea: dòng đầu nằm
|
|
5391
|
+
* sau đệm trên, không phải giữa khung.
|
|
5392
|
+
*/
|
|
5393
|
+
measure() {
|
|
5394
|
+
if (!this.enabled())
|
|
5395
|
+
return;
|
|
5396
|
+
const root = this.host.nativeElement;
|
|
5397
|
+
const text = root.querySelector('[data-ol-field-text], input:not([type=hidden]):not([type=file]), textarea');
|
|
5398
|
+
if (!text)
|
|
5399
|
+
return;
|
|
5400
|
+
const frame = root.getBoundingClientRect();
|
|
5401
|
+
const r = text.getBoundingClientRect();
|
|
5402
|
+
if (!r.width && !r.height)
|
|
5403
|
+
return;
|
|
5404
|
+
const cs = getComputedStyle(text);
|
|
5405
|
+
const padLeft = parseFloat(cs.paddingLeft) || 0;
|
|
5406
|
+
const x = r.left - frame.left + padLeft;
|
|
5407
|
+
const lineMid = text.tagName === 'TEXTAREA'
|
|
5408
|
+
? r.top -
|
|
5409
|
+
frame.top +
|
|
5410
|
+
(parseFloat(cs.paddingTop) || 0) +
|
|
5411
|
+
(parseFloat(cs.lineHeight) || 20) / 2
|
|
5412
|
+
: r.top - frame.top + r.height / 2;
|
|
5413
|
+
const y = lineMid - 10; // nhãn cao 20px
|
|
5414
|
+
if (Math.abs(x - this.restX()) > 0.5)
|
|
5415
|
+
this.restX.set(Math.round(x));
|
|
5416
|
+
if (Math.abs(y - this.restY()) > 0.5)
|
|
5417
|
+
this.restY.set(Math.round(y));
|
|
5418
|
+
// Bật chuyển động ở khung hình SAU — để vị trí đo được áp ngay, không trượt.
|
|
5419
|
+
if (!this.ready())
|
|
5420
|
+
requestAnimationFrame(() => this.ready.set(true));
|
|
5421
|
+
}
|
|
5422
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFieldFrame, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5423
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlFieldFrame, isStandalone: true, selector: "ol-field-frame", inputs: { enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, forId: { classPropertyName: "forId", publicName: "forId", isSignal: true, isRequired: false, transformFunction: null }, labelId: { classPropertyName: "labelId", publicName: "labelId", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, hasValue: { classPropertyName: "hasValue", publicName: "hasValue", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, floatStart: { classPropertyName: "floatStart", publicName: "floatStart", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block w-full" }, ngImport: i0, template: `
|
|
5424
|
+
<div
|
|
5425
|
+
[class]="shellClasses()"
|
|
5426
|
+
[attr.data-floated]="enabled() && floated() ? '' : null"
|
|
5427
|
+
(focusin)="focused.set(true)"
|
|
5428
|
+
(focusout)="onFocusOut($event)"
|
|
5429
|
+
(animationstart)="onAnimationStart($event)"
|
|
5430
|
+
(input)="autofilled.set(false)"
|
|
5431
|
+
>
|
|
5432
|
+
<ng-content />
|
|
5433
|
+
@if (enabled() && label()) {
|
|
5434
|
+
<label
|
|
5435
|
+
[attr.for]="forId() || null"
|
|
5436
|
+
[attr.id]="labelId() || null"
|
|
5437
|
+
[class]="labelClasses()"
|
|
5438
|
+
[style.left.px]="labelLeft()"
|
|
5439
|
+
[style.top.px]="labelTop()"
|
|
5440
|
+
[style.background]="cutout()"
|
|
5441
|
+
(click)="focusControl()"
|
|
5442
|
+
>
|
|
5443
|
+
{{ label() }}
|
|
5444
|
+
@if (required()) {
|
|
5445
|
+
<span class="text-fg-danger" aria-hidden="true">*</span>
|
|
5446
|
+
}
|
|
5447
|
+
</label>
|
|
5448
|
+
}
|
|
5449
|
+
</div>
|
|
5450
|
+
`, isInline: true, styles: ["ol-field-frame input:-webkit-autofill,ol-field-frame input:autofill{animation:ol-field-autofill 1ms}@keyframes ol-field-autofill{0%{opacity:1}to{opacity:1}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
5451
|
+
}
|
|
5452
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFieldFrame, decorators: [{
|
|
5453
|
+
type: Component,
|
|
5454
|
+
args: [{ selector: 'ol-field-frame', standalone: true, template: `
|
|
5455
|
+
<div
|
|
5456
|
+
[class]="shellClasses()"
|
|
5457
|
+
[attr.data-floated]="enabled() && floated() ? '' : null"
|
|
5458
|
+
(focusin)="focused.set(true)"
|
|
5459
|
+
(focusout)="onFocusOut($event)"
|
|
5460
|
+
(animationstart)="onAnimationStart($event)"
|
|
5461
|
+
(input)="autofilled.set(false)"
|
|
5462
|
+
>
|
|
5463
|
+
<ng-content />
|
|
5464
|
+
@if (enabled() && label()) {
|
|
5465
|
+
<label
|
|
5466
|
+
[attr.for]="forId() || null"
|
|
5467
|
+
[attr.id]="labelId() || null"
|
|
5468
|
+
[class]="labelClasses()"
|
|
5469
|
+
[style.left.px]="labelLeft()"
|
|
5470
|
+
[style.top.px]="labelTop()"
|
|
5471
|
+
[style.background]="cutout()"
|
|
5472
|
+
(click)="focusControl()"
|
|
5473
|
+
>
|
|
5474
|
+
{{ label() }}
|
|
5475
|
+
@if (required()) {
|
|
5476
|
+
<span class="text-fg-danger" aria-hidden="true">*</span>
|
|
5477
|
+
}
|
|
5478
|
+
</label>
|
|
5479
|
+
}
|
|
5480
|
+
</div>
|
|
5481
|
+
`, encapsulation: ViewEncapsulation.None, host: { class: 'block w-full' }, changeDetection: ChangeDetectionStrategy.OnPush, styles: ["ol-field-frame input:-webkit-autofill,ol-field-frame input:autofill{animation:ol-field-autofill 1ms}@keyframes ol-field-autofill{0%{opacity:1}to{opacity:1}}\n"] }]
|
|
5482
|
+
}], ctorParameters: () => [], propDecorators: { enabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "enabled", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], forId: [{ type: i0.Input, args: [{ isSignal: true, alias: "forId", required: false }] }], labelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelId", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], hasValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasValue", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }], floatStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatStart", required: false }] }] } });
|
|
5483
|
+
/**
|
|
5484
|
+
* Nền kiểu `filled` theo trạng thái — Figma With background: lỗi nền hồng,
|
|
5485
|
+
* thành công nền xanh. Chuỗi viết nguyên văn để Tailwind sinh CSS.
|
|
5486
|
+
*/
|
|
5487
|
+
const FILL_BG = {
|
|
5488
|
+
default: {
|
|
5489
|
+
shell: 'bg-neutral-secondary-medium',
|
|
5490
|
+
box: '[&_.ol-field-box]:bg-neutral-secondary-medium!',
|
|
5491
|
+
},
|
|
5492
|
+
error: { shell: 'bg-danger-soft', box: '[&_.ol-field-box]:bg-danger-soft!' },
|
|
5493
|
+
success: { shell: 'bg-success-soft', box: '[&_.ol-field-box]:bg-success-soft!' },
|
|
5494
|
+
};
|
|
5495
|
+
|
|
5496
|
+
/**
|
|
5497
|
+
* Nhãn PHÍA TRÊN ô nhập — dùng chung cho mọi ô của họ form.
|
|
5498
|
+
*
|
|
5499
|
+
* Trước đây tám component tự chép tay cùng một đoạn markup (khối `div` +
|
|
5500
|
+
* `<label>` + dấu `*` + icon trợ giúp), và nó trôi thành ba phiên bản:
|
|
5501
|
+
* `inline-flex … mb-1.5` (input, textarea, select, autocomplete, timepicker),
|
|
5502
|
+
* `block mb-1.5` (tag-input), `flex` trong khung `gap-1.5` (datepicker, khoảng
|
|
5503
|
+
* ngày). Class lấy từ `FIELD_LABEL` / `FIELD_LABEL_GAP` / `FIELD_ASTERISK` —
|
|
5504
|
+
* ba token sinh ra để dùng chung nhưng chưa ô nào dùng.
|
|
5505
|
+
*
|
|
5506
|
+
* Nhãn NỔI (trong khung) là việc của `OlFieldFrame`; hai thứ loại trừ nhau.
|
|
5507
|
+
*
|
|
5508
|
+
* `beside`: nhãn đứng BÊN TRÁI ô (host đặt `labelPosition="left"`, khung
|
|
5509
|
+
* ngoài là lưới `FIELD_LABEL_BESIDE`). Hàng nhãn cao bằng ô theo cỡ để chữ
|
|
5510
|
+
* căn giữa dọc với ô một dòng; chữ dài xuống dòng trong cột nhãn.
|
|
5511
|
+
*/
|
|
5512
|
+
class OlFieldLabel {
|
|
5513
|
+
text = input.required(/* @ts-ignore */
|
|
5514
|
+
...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
5515
|
+
/** Id ô nhập — `<label for>`. */
|
|
5516
|
+
forId = input('', /* @ts-ignore */
|
|
5517
|
+
...(ngDevMode ? [{ debugName: "forId" }] : /* istanbul ignore next */ []));
|
|
5518
|
+
/** Id của nhãn — cho combobox `div` nối `aria-labelledby` (không nhận `for`). */
|
|
5519
|
+
labelId = input('', /* @ts-ignore */
|
|
5520
|
+
...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
5521
|
+
required = input(false, /* @ts-ignore */
|
|
5522
|
+
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
5523
|
+
/** Icon `?` cạnh nhãn, kèm `helpTooltip` khi rê chuột. */
|
|
5524
|
+
helpIcon = input(false, /* @ts-ignore */
|
|
5525
|
+
...(ngDevMode ? [{ debugName: "helpIcon" }] : /* istanbul ignore next */ []));
|
|
5526
|
+
helpTooltip = input('', /* @ts-ignore */
|
|
5527
|
+
...(ngDevMode ? [{ debugName: "helpTooltip" }] : /* istanbul ignore next */ []));
|
|
5528
|
+
/**
|
|
5529
|
+
* Bỏ lề dưới — cho host đặt nhãn trong khung `flex-col gap-1.5` (datepicker,
|
|
5530
|
+
* khoảng ngày): khung đã tạo khoảng cách, thêm `mb-1.5` là thành 12px.
|
|
5531
|
+
*/
|
|
5532
|
+
flush = input(false, /* @ts-ignore */
|
|
5533
|
+
...(ngDevMode ? [{ debugName: "flush" }] : /* istanbul ignore next */ []));
|
|
5534
|
+
/** Nhãn bên trái ô, cùng hàng. */
|
|
5535
|
+
beside = input(false, /* @ts-ignore */
|
|
5536
|
+
...(ngDevMode ? [{ debugName: "beside" }] : /* istanbul ignore next */ []));
|
|
5537
|
+
/** Cỡ ô — chỉ dùng khi `beside`, để hàng nhãn cao bằng ô. */
|
|
5538
|
+
size = input('base', /* @ts-ignore */
|
|
5539
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
5540
|
+
labelClasses = FIELD_LABEL;
|
|
5541
|
+
asteriskClasses = FIELD_ASTERISK;
|
|
5542
|
+
rowClasses = computed(() => {
|
|
5543
|
+
if (this.beside())
|
|
5544
|
+
return `flex items-center ${FIELD_MIN_HEIGHT[this.size()]}`;
|
|
5545
|
+
return `flex items-center justify-between${this.flush() ? '' : ` ${FIELD_LABEL_GAP}`}`;
|
|
5546
|
+
}, /* @ts-ignore */
|
|
5547
|
+
...(ngDevMode ? [{ debugName: "rowClasses" }] : /* istanbul ignore next */ []));
|
|
5548
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFieldLabel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5549
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlFieldLabel, isStandalone: true, selector: "ol-field-label", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: true, transformFunction: null }, forId: { classPropertyName: "forId", publicName: "forId", isSignal: true, isRequired: false, transformFunction: null }, labelId: { classPropertyName: "labelId", publicName: "labelId", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, helpIcon: { classPropertyName: "helpIcon", publicName: "helpIcon", isSignal: true, isRequired: false, transformFunction: null }, helpTooltip: { classPropertyName: "helpTooltip", publicName: "helpTooltip", isSignal: true, isRequired: false, transformFunction: null }, flush: { classPropertyName: "flush", publicName: "flush", isSignal: true, isRequired: false, transformFunction: null }, beside: { classPropertyName: "beside", publicName: "beside", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
5550
|
+
<div [class]="rowClasses()">
|
|
5551
|
+
<label [attr.for]="forId() || null" [attr.id]="labelId() || null" [class]="labelClasses">
|
|
5552
|
+
<span>{{ text() }}</span>
|
|
5553
|
+
@if (required()) {
|
|
5554
|
+
<span [class]="asteriskClasses">*</span>
|
|
5555
|
+
}
|
|
5556
|
+
@if (helpIcon()) {
|
|
5557
|
+
<span
|
|
5558
|
+
class="inline-flex cursor-help items-center text-body-subtle hover:text-heading"
|
|
5559
|
+
[title]="helpTooltip() || 'Trợ giúp'"
|
|
5560
|
+
>
|
|
5561
|
+
<ol-icon name="question-circle" size="xs" />
|
|
5562
|
+
</span>
|
|
5563
|
+
}
|
|
5564
|
+
</label>
|
|
5565
|
+
</div>
|
|
5566
|
+
`, isInline: true, dependencies: [{ kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5567
|
+
}
|
|
5568
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFieldLabel, decorators: [{
|
|
5569
|
+
type: Component,
|
|
5570
|
+
args: [{
|
|
5571
|
+
selector: 'ol-field-label',
|
|
5572
|
+
standalone: true,
|
|
5573
|
+
imports: [OlIcon],
|
|
5574
|
+
template: `
|
|
5575
|
+
<div [class]="rowClasses()">
|
|
5576
|
+
<label [attr.for]="forId() || null" [attr.id]="labelId() || null" [class]="labelClasses">
|
|
5577
|
+
<span>{{ text() }}</span>
|
|
5578
|
+
@if (required()) {
|
|
5579
|
+
<span [class]="asteriskClasses">*</span>
|
|
5580
|
+
}
|
|
5581
|
+
@if (helpIcon()) {
|
|
5582
|
+
<span
|
|
5583
|
+
class="inline-flex cursor-help items-center text-body-subtle hover:text-heading"
|
|
5584
|
+
[title]="helpTooltip() || 'Trợ giúp'"
|
|
5585
|
+
>
|
|
5586
|
+
<ol-icon name="question-circle" size="xs" />
|
|
5587
|
+
</span>
|
|
5588
|
+
}
|
|
5589
|
+
</label>
|
|
5590
|
+
</div>
|
|
5591
|
+
`,
|
|
5592
|
+
host: { class: 'block' },
|
|
5593
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
5594
|
+
}]
|
|
5595
|
+
}], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: true }] }], forId: [{ type: i0.Input, args: [{ isSignal: true, alias: "forId", required: false }] }], labelId: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelId", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], helpIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpIcon", required: false }] }], helpTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "helpTooltip", required: false }] }], flush: [{ type: i0.Input, args: [{ isSignal: true, alias: "flush", required: false }] }], beside: [{ type: i0.Input, args: [{ isSignal: true, alias: "beside", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
|
|
5094
5596
|
|
|
5095
5597
|
/**
|
|
5096
5598
|
* Giao diện datepicker dùng bản **_VI** theo Figma #1149:51169 (`CN`·`T2`…`T7`,
|
|
@@ -6122,7 +6624,11 @@ function trackPopupMaxHeight(anchorEl, panelEl) {
|
|
|
6122
6624
|
|
|
6123
6625
|
let uniqueDatepickerId = 0;
|
|
6124
6626
|
class OlDatepicker {
|
|
6125
|
-
|
|
6627
|
+
/**
|
|
6628
|
+
* Trạng thái lịch (ngày đang xem, khoảng đã chọn…). Tên cũ `state` nhường
|
|
6629
|
+
* cho input `state` (lỗi / thành công) — cùng tên với mọi ô nhập khác.
|
|
6630
|
+
*/
|
|
6631
|
+
pickerState = inject(OlDatepickerStateService);
|
|
6126
6632
|
hostEl = inject(ElementRef);
|
|
6127
6633
|
/** Id do nơi gọi truyền; trống thì dùng id tự sinh bên dưới. */
|
|
6128
6634
|
id = input('', /* @ts-ignore */
|
|
@@ -6142,6 +6648,41 @@ class OlDatepicker {
|
|
|
6142
6648
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
6143
6649
|
label = input('', /* @ts-ignore */
|
|
6144
6650
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
6651
|
+
/**
|
|
6652
|
+
* Trạng thái hiển thị — lỗi / thành công, cùng thang `FIELD_STATE` với mọi ô
|
|
6653
|
+
* nhập khác. Trước đây datepicker không có (tên `state` bị service chiếm).
|
|
6654
|
+
*/
|
|
6655
|
+
state = input('default', /* @ts-ignore */
|
|
6656
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
6657
|
+
/**
|
|
6658
|
+
* Nhãn nổi: `label` nằm TRONG khung (sau icon lịch), nổi lên khi focus, mở
|
|
6659
|
+
* lịch hoặc đã có ngày (kể cả chữ đang gõ dở).
|
|
6660
|
+
*/
|
|
6661
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
6662
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
6663
|
+
/**
|
|
6664
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
6665
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
6666
|
+
*/
|
|
6667
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
6668
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
6669
|
+
/**
|
|
6670
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
6671
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
6672
|
+
* mép ô thẳng hàng.
|
|
6673
|
+
*/
|
|
6674
|
+
labelWidth = input('', /* @ts-ignore */
|
|
6675
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
6676
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
6677
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
6678
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
6679
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
6680
|
+
/** Khung nhãn + ô: cột dọc (nhãn trên) hoặc lưới 2 cột (nhãn trái). */
|
|
6681
|
+
stackClasses = computed(() => this.labelBeside() ? `${this.labelLayout()} gap-y-1.5 w-full` : 'flex flex-col gap-1.5 w-full', /* @ts-ignore */
|
|
6682
|
+
...(ngDevMode ? [{ debugName: "stackClasses" }] : /* istanbul ignore next */ []));
|
|
6683
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
6684
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
6685
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
6145
6686
|
helperText = input('', /* @ts-ignore */
|
|
6146
6687
|
...(ngDevMode ? [{ debugName: "helperText" }] : /* istanbul ignore next */ []));
|
|
6147
6688
|
required = input(false, /* @ts-ignore */
|
|
@@ -6271,16 +6812,16 @@ class OlDatepicker {
|
|
|
6271
6812
|
});
|
|
6272
6813
|
// Sync external constraints into state
|
|
6273
6814
|
effect(() => {
|
|
6274
|
-
this.
|
|
6275
|
-
this.
|
|
6276
|
-
this.
|
|
6277
|
-
this.
|
|
6278
|
-
this.
|
|
6815
|
+
this.pickerState.minDate.set(this.minDate());
|
|
6816
|
+
this.pickerState.maxDate.set(this.maxDate());
|
|
6817
|
+
this.pickerState.daysDisabled.set(this.daysDisabled());
|
|
6818
|
+
this.pickerState.datesDisabled.set(this.datesDisabled());
|
|
6819
|
+
this.pickerState.datesEnabled.set(this.datesEnabled());
|
|
6279
6820
|
});
|
|
6280
6821
|
// Sync value changes to state and input string
|
|
6281
6822
|
effect(() => {
|
|
6282
6823
|
const val = this.value();
|
|
6283
|
-
this.
|
|
6824
|
+
this.pickerState.initSingle(val);
|
|
6284
6825
|
this.inputValue.set(val ? formatDate(val, this.format()) : '');
|
|
6285
6826
|
});
|
|
6286
6827
|
}
|
|
@@ -6306,27 +6847,30 @@ class OlDatepicker {
|
|
|
6306
6847
|
* Bỏ `py-*`: chiều cao đã cố định thì đệm dọc chỉ ép chữ, giữ `px-*` cho đệm
|
|
6307
6848
|
* ngang và `ps-*` cho chỗ icon lịch.
|
|
6308
6849
|
*/
|
|
6309
|
-
inputSizeClasses = computed(() => {
|
|
6310
|
-
switch (this.size()) {
|
|
6311
|
-
case 'sm':
|
|
6312
|
-
// Đệm và cỡ chữ lấy từ bảng chuẩn (`forms/shared/field-tokens.ts`).
|
|
6313
|
-
// Đo trước khi chuẩn hoá: datepicker dùng `px-3/3.5/4` và `text-xs`
|
|
6314
|
-
// trong khi `ol-input` dùng `px-2.5/3/3.5` và `text-sm` — lệch 2px cả
|
|
6315
|
-
// đệm lẫn cỡ chữ, đặt cạnh nhau trong một hàng là thấy ngay.
|
|
6316
|
-
//
|
|
6317
|
-
// `ps-*` thay `px-*` ở phía trái để chừa chỗ cho icon lịch; phần bên
|
|
6318
|
-
// phải vẫn theo bảng chuẩn.
|
|
6319
|
-
return `${FIELD_HEIGHT.sm} ${FIELD_PADDING_X.sm} ps-9 ${FIELD_TEXT.sm} ${FIELD_RADIUS}`;
|
|
6320
|
-
case 'lg':
|
|
6321
|
-
return `${FIELD_HEIGHT.lg} ${FIELD_PADDING_X.lg} ps-11 ${FIELD_TEXT.lg} ${FIELD_RADIUS}`;
|
|
6322
|
-
case 'xl':
|
|
6323
|
-
return `${FIELD_HEIGHT.xl} ${FIELD_PADDING_X.xl} ps-11 ${FIELD_TEXT.xl} ${FIELD_RADIUS}`;
|
|
6324
|
-
case 'base':
|
|
6325
|
-
default:
|
|
6326
|
-
return `${FIELD_HEIGHT.base} ${FIELD_PADDING_X.base} ps-10 ${FIELD_TEXT.base} ${FIELD_RADIUS}`;
|
|
6327
|
-
}
|
|
6328
|
-
}, /* @ts-ignore */
|
|
6850
|
+
inputSizeClasses = computed(() => `${this.sizeClasses()} ${FIELD_STATE[this.state()]}`, /* @ts-ignore */
|
|
6329
6851
|
...(ngDevMode ? [{ debugName: "inputSizeClasses" }] : /* istanbul ignore next */ []));
|
|
6852
|
+
sizeClasses = computed(() => {
|
|
6853
|
+
const size = this.size();
|
|
6854
|
+
// Icon lịch ở lề theo cỡ, chữ cách icon 8px (`PICKER_ICON_RESERVE_START`).
|
|
6855
|
+
// Có nút xoá thì lề phải chừa chỗ cho nó — nút nằm `absolute`, không chừa
|
|
6856
|
+
// là chữ dài chạy xuống dưới nút.
|
|
6857
|
+
const end = this.showClear() ? FIELD_CLEAR_RESERVE_END[size] : FIELD_PADDING_END[size];
|
|
6858
|
+
return `${FIELD_HEIGHT[size]} ${PICKER_ICON_RESERVE_START[size]} ${end} ${FIELD_TEXT[size]} ${FIELD_RADIUS}`;
|
|
6859
|
+
}, /* @ts-ignore */
|
|
6860
|
+
...(ngDevMode ? [{ debugName: "sizeClasses" }] : /* istanbul ignore next */ []));
|
|
6861
|
+
showClear = computed(() => this.clearable() && this.hasContent() && !this.disabled(), /* @ts-ignore */
|
|
6862
|
+
...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
6863
|
+
/**
|
|
6864
|
+
* `ms-px`/`me-px`: div bọc icon tính từ mép NGOÀI viền 1px của ô, còn đệm
|
|
6865
|
+
* ô tính từ mép trong. Nhãn nổi `filled`/`standard` không có viền hai bên
|
|
6866
|
+
* (chỉ gạch dưới) thì không bù.
|
|
6867
|
+
*/
|
|
6868
|
+
sideBorder = computed(() => !this.floatingLabel() || this.floatingVariant() === 'outlined', /* @ts-ignore */
|
|
6869
|
+
...(ngDevMode ? [{ debugName: "sideBorder" }] : /* istanbul ignore next */ []));
|
|
6870
|
+
leadPadClass = computed(() => `${this.sideBorder() ? 'ms-px ' : ''}${FIELD_PADDING_START[this.size()]}`, /* @ts-ignore */
|
|
6871
|
+
...(ngDevMode ? [{ debugName: "leadPadClass" }] : /* istanbul ignore next */ []));
|
|
6872
|
+
trailPadClass = computed(() => `${this.sideBorder() ? 'me-px ' : ''}${FIELD_PADDING_END[this.size()]}`, /* @ts-ignore */
|
|
6873
|
+
...(ngDevMode ? [{ debugName: "trailPadClass" }] : /* istanbul ignore next */ []));
|
|
6330
6874
|
/**
|
|
6331
6875
|
* Cỡ icon lịch theo `size` của ô nhập.
|
|
6332
6876
|
*
|
|
@@ -6379,7 +6923,7 @@ class OlDatepicker {
|
|
|
6379
6923
|
return;
|
|
6380
6924
|
this.isOpen.update((v) => !v);
|
|
6381
6925
|
if (this.isOpen()) {
|
|
6382
|
-
this.
|
|
6926
|
+
this.pickerState.initSingle(this.value());
|
|
6383
6927
|
}
|
|
6384
6928
|
else {
|
|
6385
6929
|
this.onTouched();
|
|
@@ -6417,13 +6961,13 @@ class OlDatepicker {
|
|
|
6417
6961
|
this.value.set(parsed);
|
|
6418
6962
|
this.onChange(parsed);
|
|
6419
6963
|
this.emitAllChanges(parsed);
|
|
6420
|
-
this.
|
|
6964
|
+
this.pickerState.initSingle(parsed);
|
|
6421
6965
|
}
|
|
6422
6966
|
else if (!val.trim()) {
|
|
6423
6967
|
this.value.set(null);
|
|
6424
6968
|
this.onChange(null);
|
|
6425
6969
|
this.emitAllChanges(null);
|
|
6426
|
-
this.
|
|
6970
|
+
this.pickerState.initSingle(null);
|
|
6427
6971
|
}
|
|
6428
6972
|
}
|
|
6429
6973
|
clearInput(event) {
|
|
@@ -6432,7 +6976,7 @@ class OlDatepicker {
|
|
|
6432
6976
|
this.inputValue.set('');
|
|
6433
6977
|
this.onChange(null);
|
|
6434
6978
|
this.emitAllChanges(null);
|
|
6435
|
-
this.
|
|
6979
|
+
this.pickerState.clear();
|
|
6436
6980
|
}
|
|
6437
6981
|
/** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
|
|
6438
6982
|
detachPanelNow() {
|
|
@@ -6501,18 +7045,18 @@ class OlDatepicker {
|
|
|
6501
7045
|
this.detachPanelNow();
|
|
6502
7046
|
}
|
|
6503
7047
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDatepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6504
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlDatepicker, isStandalone: true, selector: "ol-datepicker", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, daysDisabled: { classPropertyName: "daysDisabled", publicName: "daysDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesDisabled: { classPropertyName: "datesDisabled", publicName: "datesDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesEnabled: { classPropertyName: "datesEnabled", publicName: "datesEnabled", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", dateChange: "dateChange", timestampChange: "timestampChange", isoChange: "isoChange", formattedChange: "formattedChange" }, host: { properties: { "class.block": "!inline()", "class.w-full": "!inline()", "class.inline-block": "inline()", "class.z-dropdown": "isOpen()" }, classAttribute: "relative" }, providers: [
|
|
7048
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlDatepicker, isStandalone: true, selector: "ol-datepicker", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, daysDisabled: { classPropertyName: "daysDisabled", publicName: "daysDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesDisabled: { classPropertyName: "datesDisabled", publicName: "datesDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesEnabled: { classPropertyName: "datesEnabled", publicName: "datesEnabled", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", dateChange: "dateChange", timestampChange: "timestampChange", isoChange: "isoChange", formattedChange: "formattedChange" }, host: { properties: { "class.block": "!inline()", "class.w-full": "!inline()", "class.inline-block": "inline()", "class.z-dropdown": "isOpen()" }, classAttribute: "relative" }, providers: [
|
|
6505
7049
|
OlDatepickerStateService,
|
|
6506
7050
|
{
|
|
6507
7051
|
provide: NG_VALUE_ACCESSOR,
|
|
6508
7052
|
useExisting: forwardRef(() => OlDatepicker),
|
|
6509
7053
|
multi: true,
|
|
6510
7054
|
},
|
|
6511
|
-
], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n KH\u00D4NG ghim `max-w-sm`: `ol-input`, `ol-select`, `ol-textarea` \u0111\u1EC1u \u0111\u1EC3 n\u01A1i g\u1ECDi\n quy\u1EBFt \u0111\u1ECBnh b\u1EC1 r\u1ED9ng, ch\u1EC9 `ol-datepicker` t\u1EF1 gi\u1EDBi h\u1EA1n 384px. \u0110\u1EB7t b\u1ED1n \u00F4 c\u1EA1nh\n nhau trong m\u1ED9t modal th\u00EC \u00F4 ng\u00E0y h\u1EB9p h\u01A1n h\u1EB3n \u2014 \u0111o /modal: ba \u00F4 kia 646px, \u00F4\n ng\u00E0y 345px. Mu\u1ED1n h\u1EB9p th\u00EC b\u1ECDc ngo\u00E0i, nh\u01B0 m\u1ECDi \u00F4 nh\u1EADp kh\u00E1c trong h\u1ECD.\n-->\n<div class=\"relative inline-block\" [class.w-full]=\"!inline()\" [class.z-dropdown]=\"isOpen()\">\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div class=\"
|
|
7055
|
+
], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\n KH\u00D4NG ghim `max-w-sm`: `ol-input`, `ol-select`, `ol-textarea` \u0111\u1EC1u \u0111\u1EC3 n\u01A1i g\u1ECDi\n quy\u1EBFt \u0111\u1ECBnh b\u1EC1 r\u1ED9ng, ch\u1EC9 `ol-datepicker` t\u1EF1 gi\u1EDBi h\u1EA1n 384px. \u0110\u1EB7t b\u1ED1n \u00F4 c\u1EA1nh\n nhau trong m\u1ED9t modal th\u00EC \u00F4 ng\u00E0y h\u1EB9p h\u01A1n h\u1EB3n \u2014 \u0111o /modal: ba \u00F4 kia 646px, \u00F4\n ng\u00E0y 345px. Mu\u1ED1n h\u1EB9p th\u00EC b\u1ECDc ngo\u00E0i, nh\u01B0 m\u1ECDi \u00F4 nh\u1EADp kh\u00E1c trong h\u1ECD.\n-->\n<div class=\"relative inline-block\" [class.w-full]=\"!inline()\" [class.z-dropdown]=\"isOpen()\">\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div [class]=\"stackClasses()\" [style.--ol-label-w]=\"labelWidth() || null\">\n <!-- Label -->\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required()\"\n [flush]=\"true\"\n />\n }\n\n <!-- Input Trigger Field -->\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"hasContent()\"\n [open]=\"isOpen()\"\n >\n <div class=\"relative w-full\">\n <!-- Calendar Icon Prefix -->\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"leadPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n\n <input\n [id]=\"inputId()\"\n type=\"text\"\n [value]=\"inputValue()\"\n (input)=\"onInputChange($any($event.target).value)\"\n (click)=\"togglePopup($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [class]=\"inputSizeClasses()\"\n class=\"ol-field-box block w-full border text-heading placeholder:text-body-subtle focus:outline-hidden shadow-xs transition-colors cursor-pointer\"\n />\n\n <!-- Clear Button or Arrow Dropdown -->\n <div class=\"absolute inset-y-0 inset-e-0 flex items-center\" [class]=\"trailPadClass()\">\n @if (showClear()) {\n <button\n type=\"button\"\n (click)=\"clearInput($event)\"\n class=\"-me-0.5 text-body-subtle hover:text-heading p-0.5 rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer\"\n aria-label=\"Xo\u00E1 ng\u00E0y \u0111\u00E3 ch\u1ECDn\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </div>\n </ol-field-frame>\n\n <!-- Helper Text -->\n @if (helperText()) {\n <p class=\"text-xs text-body-subtle\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Dropdown Popup -->\n @if (isOpen()) {\n <!--\n `rounded-base` PH\u1EA2I kh\u1EDBp bo g\u00F3c c\u1EE7a `ol-datepicker-popup` b\u00EAn trong.\n\n Khung n\u00E0y mang `shadow-2xl` nh\u01B0ng tr\u01B0\u1EDBc \u0111\u00E2y kh\u00F4ng c\u00F3 `rounded` n\u00E0o,\n n\u00EAn b\u00F3ng \u0111\u1ED5 v\u1EBD theo h\u00ECnh CH\u1EEE NH\u1EACT VU\u00D4NG trong khi popup b\u00EAn trong bo\n 12px \u2014 hai g\u00F3c d\u01B0\u1EDBi l\u1ED9 ra hai v\u1EC7t vu\u00F4ng x\u00E1m. \u0110o ng\u00E0y 2026-09-24: hai\n khung c\u00F9ng 294\u00D7374, khung ngo\u00E0i `radius: 0px`, khung trong\n `radius: 12px`.\n -->\n <div\n #panel\n class=\"absolute z-dropdown animate-in fade-in zoom-in-95 duration-100 rounded-base shadow-2xl\"\n >\n <ol-datepicker-popup\n [showFooter]=\"showFooter()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n />\n </div>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: OlDatepickerPopup, selector: "ol-datepicker-popup", inputs: ["showFooter", "dualMonth", "presets", "inline"], outputs: ["valueChange", "clearClick", "applyClick", "cancelClick"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6512
7056
|
}
|
|
6513
7057
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDatepicker, decorators: [{
|
|
6514
7058
|
type: Component,
|
|
6515
|
-
args: [{ selector: 'ol-datepicker', standalone: true, imports: [CommonModule, FormsModule, OlDatepickerPopup, OlIcon], providers: [
|
|
7059
|
+
args: [{ selector: 'ol-datepicker', standalone: true, imports: [CommonModule, FormsModule, OlDatepickerPopup, OlIcon, OlFieldFrame, OlFieldLabel], providers: [
|
|
6516
7060
|
OlDatepickerStateService,
|
|
6517
7061
|
{
|
|
6518
7062
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -6548,11 +7092,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
6548
7092
|
'[class.w-full]': '!inline()',
|
|
6549
7093
|
'[class.inline-block]': 'inline()',
|
|
6550
7094
|
'[class.z-dropdown]': 'isOpen()',
|
|
6551
|
-
}, template: "<!--\n KH\u00D4NG ghim `max-w-sm`: `ol-input`, `ol-select`, `ol-textarea` \u0111\u1EC1u \u0111\u1EC3 n\u01A1i g\u1ECDi\n quy\u1EBFt \u0111\u1ECBnh b\u1EC1 r\u1ED9ng, ch\u1EC9 `ol-datepicker` t\u1EF1 gi\u1EDBi h\u1EA1n 384px. \u0110\u1EB7t b\u1ED1n \u00F4 c\u1EA1nh\n nhau trong m\u1ED9t modal th\u00EC \u00F4 ng\u00E0y h\u1EB9p h\u01A1n h\u1EB3n \u2014 \u0111o /modal: ba \u00F4 kia 646px, \u00F4\n ng\u00E0y 345px. Mu\u1ED1n h\u1EB9p th\u00EC b\u1ECDc ngo\u00E0i, nh\u01B0 m\u1ECDi \u00F4 nh\u1EADp kh\u00E1c trong h\u1ECD.\n-->\n<div class=\"relative inline-block\" [class.w-full]=\"!inline()\" [class.z-dropdown]=\"isOpen()\">\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div class=\"
|
|
6552
|
-
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], daysDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "daysDisabled", required: false }] }], datesDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesDisabled", required: false }] }], datesEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesEnabled", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], timestampChange: [{ type: i0.Output, args: ["timestampChange"] }], isoChange: [{ type: i0.Output, args: ["isoChange"] }], formattedChange: [{ type: i0.Output, args: ["formattedChange"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
7095
|
+
}, template: "<!--\n KH\u00D4NG ghim `max-w-sm`: `ol-input`, `ol-select`, `ol-textarea` \u0111\u1EC1u \u0111\u1EC3 n\u01A1i g\u1ECDi\n quy\u1EBFt \u0111\u1ECBnh b\u1EC1 r\u1ED9ng, ch\u1EC9 `ol-datepicker` t\u1EF1 gi\u1EDBi h\u1EA1n 384px. \u0110\u1EB7t b\u1ED1n \u00F4 c\u1EA1nh\n nhau trong m\u1ED9t modal th\u00EC \u00F4 ng\u00E0y h\u1EB9p h\u01A1n h\u1EB3n \u2014 \u0111o /modal: ba \u00F4 kia 646px, \u00F4\n ng\u00E0y 345px. Mu\u1ED1n h\u1EB9p th\u00EC b\u1ECDc ngo\u00E0i, nh\u01B0 m\u1ECDi \u00F4 nh\u1EADp kh\u00E1c trong h\u1ECD.\n-->\n<div class=\"relative inline-block\" [class.w-full]=\"!inline()\" [class.z-dropdown]=\"isOpen()\">\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div [class]=\"stackClasses()\" [style.--ol-label-w]=\"labelWidth() || null\">\n <!-- Label -->\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required()\"\n [flush]=\"true\"\n />\n }\n\n <!-- Input Trigger Field -->\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"hasContent()\"\n [open]=\"isOpen()\"\n >\n <div class=\"relative w-full\">\n <!-- Calendar Icon Prefix -->\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"leadPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n\n <input\n [id]=\"inputId()\"\n type=\"text\"\n [value]=\"inputValue()\"\n (input)=\"onInputChange($any($event.target).value)\"\n (click)=\"togglePopup($event)\"\n [placeholder]=\"placeholder()\"\n [disabled]=\"disabled()\"\n [class]=\"inputSizeClasses()\"\n class=\"ol-field-box block w-full border text-heading placeholder:text-body-subtle focus:outline-hidden shadow-xs transition-colors cursor-pointer\"\n />\n\n <!-- Clear Button or Arrow Dropdown -->\n <div class=\"absolute inset-y-0 inset-e-0 flex items-center\" [class]=\"trailPadClass()\">\n @if (showClear()) {\n <button\n type=\"button\"\n (click)=\"clearInput($event)\"\n class=\"-me-0.5 text-body-subtle hover:text-heading p-0.5 rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer\"\n aria-label=\"Xo\u00E1 ng\u00E0y \u0111\u00E3 ch\u1ECDn\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </div>\n </ol-field-frame>\n\n <!-- Helper Text -->\n @if (helperText()) {\n <p class=\"text-xs text-body-subtle\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Dropdown Popup -->\n @if (isOpen()) {\n <!--\n `rounded-base` PH\u1EA2I kh\u1EDBp bo g\u00F3c c\u1EE7a `ol-datepicker-popup` b\u00EAn trong.\n\n Khung n\u00E0y mang `shadow-2xl` nh\u01B0ng tr\u01B0\u1EDBc \u0111\u00E2y kh\u00F4ng c\u00F3 `rounded` n\u00E0o,\n n\u00EAn b\u00F3ng \u0111\u1ED5 v\u1EBD theo h\u00ECnh CH\u1EEE NH\u1EACT VU\u00D4NG trong khi popup b\u00EAn trong bo\n 12px \u2014 hai g\u00F3c d\u01B0\u1EDBi l\u1ED9 ra hai v\u1EC7t vu\u00F4ng x\u00E1m. \u0110o ng\u00E0y 2026-09-24: hai\n khung c\u00F9ng 294\u00D7374, khung ngo\u00E0i `radius: 0px`, khung trong\n `radius: 12px`.\n -->\n <div\n #panel\n class=\"absolute z-dropdown animate-in fade-in zoom-in-95 duration-100 rounded-base shadow-2xl\"\n >\n <ol-datepicker-popup\n [showFooter]=\"showFooter()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n />\n </div>\n }\n </div>\n }\n</div>\n" }]
|
|
7096
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], daysDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "daysDisabled", required: false }] }], datesDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesDisabled", required: false }] }], datesEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesEnabled", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], dateChange: [{ type: i0.Output, args: ["dateChange"] }], timestampChange: [{ type: i0.Output, args: ["timestampChange"] }], isoChange: [{ type: i0.Output, args: ["isoChange"] }], formattedChange: [{ type: i0.Output, args: ["formattedChange"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
6553
7097
|
|
|
7098
|
+
let nextRangeId = 0;
|
|
7099
|
+
/** `joined`: lề cỡ − 4px `p-1` của khung (icon) và + icon + 8px (chữ). */
|
|
7100
|
+
const JOINED_ICON_PAD = {
|
|
7101
|
+
sm: 'ps-1.5',
|
|
7102
|
+
base: 'ps-2',
|
|
7103
|
+
lg: 'ps-2.5',
|
|
7104
|
+
xl: 'ps-3',
|
|
7105
|
+
};
|
|
7106
|
+
const JOINED_TEXT_PAD = {
|
|
7107
|
+
sm: 'ps-7',
|
|
7108
|
+
base: 'ps-8',
|
|
7109
|
+
lg: 'ps-9.5',
|
|
7110
|
+
xl: 'ps-10',
|
|
7111
|
+
};
|
|
6554
7112
|
class OlDateRangePicker {
|
|
6555
|
-
|
|
7113
|
+
/**
|
|
7114
|
+
* Trạng thái lịch (ngày đang xem, khoảng đã chọn…). Tên cũ `state` nhường
|
|
7115
|
+
* cho input `state` (lỗi / thành công) — cùng tên với mọi ô nhập khác.
|
|
7116
|
+
*/
|
|
7117
|
+
pickerState = inject(OlDatepickerStateService);
|
|
6556
7118
|
hostEl = inject(ElementRef);
|
|
6557
7119
|
// Inputs & Models
|
|
6558
7120
|
value = model(null, /* @ts-ignore */
|
|
@@ -6563,6 +7125,53 @@ class OlDateRangePicker {
|
|
|
6563
7125
|
...(ngDevMode ? [{ debugName: "endPlaceholder" }] : /* istanbul ignore next */ []));
|
|
6564
7126
|
label = input('', /* @ts-ignore */
|
|
6565
7127
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
7128
|
+
/** Lỗi / thành công — cùng thang `FIELD_STATE` với mọi ô nhập khác. */
|
|
7129
|
+
state = input('default', /* @ts-ignore */
|
|
7130
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
7131
|
+
/**
|
|
7132
|
+
* Nhãn nổi. `joined`: một nhãn (`label`) cho cả khung. `dual-inputs`: mỗi ô
|
|
7133
|
+
* một nhãn (`fromLabel` / `toLabel`), nổi riêng theo ô đó có ngày hay không.
|
|
7134
|
+
*/
|
|
7135
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
7136
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
7137
|
+
/**
|
|
7138
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
7139
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
7140
|
+
*/
|
|
7141
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
7142
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
7143
|
+
/**
|
|
7144
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
7145
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
7146
|
+
* mép ô thẳng hàng.
|
|
7147
|
+
*/
|
|
7148
|
+
labelWidth = input('', /* @ts-ignore */
|
|
7149
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
7150
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
7151
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
7152
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
7153
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
7154
|
+
/**
|
|
7155
|
+
* `joined`: một nhãn cho cả khung → khung ngoài thành lưới. `dual-inputs`:
|
|
7156
|
+
* mỗi ô một nhãn ("Từ" / "Đến") → khung ngoài giữ cột dọc, từng CỘT thành lưới.
|
|
7157
|
+
*/
|
|
7158
|
+
stackClasses = computed(() => this.labelBeside() && this.variant() !== 'dual-inputs'
|
|
7159
|
+
? `${this.labelLayout()} gap-y-1.5 w-full`
|
|
7160
|
+
: 'flex flex-col gap-1.5 w-full', /* @ts-ignore */
|
|
7161
|
+
...(ngDevMode ? [{ debugName: "stackClasses" }] : /* istanbul ignore next */ []));
|
|
7162
|
+
columnClasses = computed(() => this.labelBeside() ? `${this.labelLayout()} gap-y-1.5` : 'flex flex-col gap-1.5', /* @ts-ignore */
|
|
7163
|
+
...(ngDevMode ? [{ debugName: "columnClasses" }] : /* istanbul ignore next */ []));
|
|
7164
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
7165
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
7166
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
7167
|
+
/**
|
|
7168
|
+
* Id hai ô `dual-inputs` — để `<label for>` nối được. Bản cũ hai nhãn
|
|
7169
|
+
* "Từ"/"Đến" KHÔNG có `for`, ô không có id: bấm nhãn không focus, trình đọc
|
|
7170
|
+
* màn hình đọc hai ô không tên.
|
|
7171
|
+
*/
|
|
7172
|
+
uid = `ol-date-range-${nextRangeId++}`;
|
|
7173
|
+
startInputId = `${this.uid}-start`;
|
|
7174
|
+
endInputId = `${this.uid}-end`;
|
|
6566
7175
|
fromLabel = input('Từ', /* @ts-ignore */
|
|
6567
7176
|
...(ngDevMode ? [{ debugName: "fromLabel" }] : /* istanbul ignore next */ []));
|
|
6568
7177
|
toLabel = input('Đến', /* @ts-ignore */
|
|
@@ -6690,16 +7299,16 @@ class OlDateRangePicker {
|
|
|
6690
7299
|
});
|
|
6691
7300
|
// Sync external constraints into state
|
|
6692
7301
|
effect(() => {
|
|
6693
|
-
this.
|
|
6694
|
-
this.
|
|
6695
|
-
this.
|
|
6696
|
-
this.
|
|
6697
|
-
this.
|
|
7302
|
+
this.pickerState.minDate.set(this.minDate());
|
|
7303
|
+
this.pickerState.maxDate.set(this.maxDate());
|
|
7304
|
+
this.pickerState.daysDisabled.set(this.daysDisabled());
|
|
7305
|
+
this.pickerState.datesDisabled.set(this.datesDisabled());
|
|
7306
|
+
this.pickerState.datesEnabled.set(this.datesEnabled());
|
|
6698
7307
|
});
|
|
6699
7308
|
// Sync value changes to state and inputs
|
|
6700
7309
|
effect(() => {
|
|
6701
7310
|
const val = this.value();
|
|
6702
|
-
this.
|
|
7311
|
+
this.pickerState.initRange(val);
|
|
6703
7312
|
const start = val && val[0] ? formatDate(val[0], this.format()) : '';
|
|
6704
7313
|
const end = val && val[1] ? formatDate(val[1], this.format()) : '';
|
|
6705
7314
|
this.startInputValue.set(start);
|
|
@@ -6732,23 +7341,38 @@ class OlDateRangePicker {
|
|
|
6732
7341
|
* 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à
|
|
6733
7342
|
* để lại đúng lỗi đó ở chỗ khác.
|
|
6734
7343
|
*/
|
|
6735
|
-
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
// lẫn cỡ chữ so với `ol-input`.
|
|
6741
|
-
return `${FIELD_HEIGHT.sm} ${FIELD_PADDING_X.sm} ${FIELD_TEXT.sm} ${FIELD_RADIUS}`;
|
|
6742
|
-
case 'lg':
|
|
6743
|
-
return `${FIELD_HEIGHT.lg} ${FIELD_PADDING_X.lg} ${FIELD_TEXT.lg} ${FIELD_RADIUS}`;
|
|
6744
|
-
case 'xl':
|
|
6745
|
-
return `${FIELD_HEIGHT.xl} ${FIELD_PADDING_X.xl} ${FIELD_TEXT.xl} ${FIELD_RADIUS}`;
|
|
6746
|
-
case 'base':
|
|
6747
|
-
default:
|
|
6748
|
-
return `${FIELD_HEIGHT.base} ${FIELD_PADDING_X.base} ${FIELD_TEXT.base} ${FIELD_RADIUS}`;
|
|
6749
|
-
}
|
|
6750
|
-
}, /* @ts-ignore */
|
|
7344
|
+
/**
|
|
7345
|
+
* Hai ô của dạng `dual-inputs`: icon lịch ở lề theo cỡ, chữ cách icon 8px.
|
|
7346
|
+
* Ô "Đến" mang nút xoá `absolute` nên lề phải chừa chỗ cho nó.
|
|
7347
|
+
*/
|
|
7348
|
+
inputSizeClasses = computed(() => this.inputClasses(false), /* @ts-ignore */
|
|
6751
7349
|
...(ngDevMode ? [{ debugName: "inputSizeClasses" }] : /* istanbul ignore next */ []));
|
|
7350
|
+
endInputClasses = computed(() => this.inputClasses(this.showClear()), /* @ts-ignore */
|
|
7351
|
+
...(ngDevMode ? [{ debugName: "endInputClasses" }] : /* istanbul ignore next */ []));
|
|
7352
|
+
inputClasses(withClear) {
|
|
7353
|
+
const size = this.size();
|
|
7354
|
+
const end = withClear ? FIELD_CLEAR_RESERVE_END[size] : FIELD_PADDING_END[size];
|
|
7355
|
+
return `${FIELD_HEIGHT[size]} ${PICKER_ICON_RESERVE_START[size]} ${end} ${FIELD_TEXT[size]} ${FIELD_RADIUS} ${FIELD_STATE[this.state()]}`;
|
|
7356
|
+
}
|
|
7357
|
+
showClear = computed(() => this.clearable() && this.hasContent() && !this.disabled(), /* @ts-ignore */
|
|
7358
|
+
...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
7359
|
+
/**
|
|
7360
|
+
* `ms-px`/`me-px`: div bọc icon tính từ mép NGOÀI viền 1px của ô, còn đệm
|
|
7361
|
+
* ô tính từ mép trong. Nhãn nổi `filled`/`standard` không có viền hai bên
|
|
7362
|
+
* (chỉ gạch dưới) thì không bù.
|
|
7363
|
+
*/
|
|
7364
|
+
sideBorder = computed(() => !this.floatingLabel() || this.floatingVariant() === 'outlined', /* @ts-ignore */
|
|
7365
|
+
...(ngDevMode ? [{ debugName: "sideBorder" }] : /* istanbul ignore next */ []));
|
|
7366
|
+
leadPadClass = computed(() => `${this.sideBorder() ? 'ms-px ' : ''}${FIELD_PADDING_START[this.size()]}`, /* @ts-ignore */
|
|
7367
|
+
...(ngDevMode ? [{ debugName: "leadPadClass" }] : /* istanbul ignore next */ []));
|
|
7368
|
+
trailPadClass = computed(() => `${this.sideBorder() ? 'me-px ' : ''}${FIELD_PADDING_END[this.size()]}`, /* @ts-ignore */
|
|
7369
|
+
...(ngDevMode ? [{ debugName: "trailPadClass" }] : /* istanbul ignore next */ []));
|
|
7370
|
+
/**
|
|
7371
|
+
* Dạng `joined`: khung có `p-1` (4px), nên icon lùi thêm lề cỡ − 4px để
|
|
7372
|
+
* mép icon cách viền đúng lề cỡ như mọi ô; chữ cách icon 8px.
|
|
7373
|
+
*/
|
|
7374
|
+
joinedIconPadClass = computed(() => JOINED_ICON_PAD[this.size()], /* @ts-ignore */
|
|
7375
|
+
...(ngDevMode ? [{ debugName: "joinedIconPadClass" }] : /* istanbul ignore next */ []));
|
|
6752
7376
|
/**
|
|
6753
7377
|
* Cỡ cho biến thể `joined` — khung bọc mang viền, hai input bên trong trong
|
|
6754
7378
|
* suốt. Phải tách khỏi `inputSizeClasses()`: ở đây chiều cao thuộc về KHUNG,
|
|
@@ -6763,7 +7387,9 @@ class OlDateRangePicker {
|
|
|
6763
7387
|
* Khung có `p-1` = 4px mỗi cạnh, nên input con lùi 8px để TỔNG chiều cao
|
|
6764
7388
|
* khớp thang: h-9 → h-7, h-10 → h-8, h-12 → h-10, h-13 → h-11.
|
|
6765
7389
|
*/
|
|
6766
|
-
joinedWrapperClasses = computed(() => {
|
|
7390
|
+
joinedWrapperClasses = computed(() => `${this.joinedHeight()} ${FIELD_STATE[this.state()]}`, /* @ts-ignore */
|
|
7391
|
+
...(ngDevMode ? [{ debugName: "joinedWrapperClasses" }] : /* istanbul ignore next */ []));
|
|
7392
|
+
joinedHeight = computed(() => {
|
|
6767
7393
|
switch (this.size()) {
|
|
6768
7394
|
case 'sm':
|
|
6769
7395
|
return 'h-9';
|
|
@@ -6776,19 +7402,20 @@ class OlDateRangePicker {
|
|
|
6776
7402
|
return 'h-10';
|
|
6777
7403
|
}
|
|
6778
7404
|
}, /* @ts-ignore */
|
|
6779
|
-
...(ngDevMode ? [{ debugName: "
|
|
7405
|
+
...(ngDevMode ? [{ debugName: "joinedHeight" }] : /* istanbul ignore next */ []));
|
|
6780
7406
|
/** Input bên trong khung `joined` — trừ đi `p-1` của khung. */
|
|
6781
7407
|
joinedInputClasses = computed(() => {
|
|
7408
|
+
const pad = JOINED_TEXT_PAD[this.size()];
|
|
6782
7409
|
switch (this.size()) {
|
|
6783
7410
|
case 'sm':
|
|
6784
|
-
return
|
|
7411
|
+
return `h-7 text-xs ${pad}`;
|
|
6785
7412
|
case 'lg':
|
|
6786
|
-
return
|
|
7413
|
+
return `h-10 text-base ${pad}`;
|
|
6787
7414
|
case 'xl':
|
|
6788
|
-
return
|
|
7415
|
+
return `h-11 text-base ${pad}`;
|
|
6789
7416
|
case 'base':
|
|
6790
7417
|
default:
|
|
6791
|
-
return
|
|
7418
|
+
return `h-8 text-sm ${pad}`;
|
|
6792
7419
|
}
|
|
6793
7420
|
}, /* @ts-ignore */
|
|
6794
7421
|
...(ngDevMode ? [{ debugName: "joinedInputClasses" }] : /* istanbul ignore next */ []));
|
|
@@ -6817,7 +7444,7 @@ class OlDateRangePicker {
|
|
|
6817
7444
|
return;
|
|
6818
7445
|
this.isOpen.update((v) => !v);
|
|
6819
7446
|
if (this.isOpen()) {
|
|
6820
|
-
this.
|
|
7447
|
+
this.pickerState.initRange(this.value());
|
|
6821
7448
|
}
|
|
6822
7449
|
else {
|
|
6823
7450
|
this.onTouched();
|
|
@@ -6850,8 +7477,8 @@ class OlDateRangePicker {
|
|
|
6850
7477
|
}
|
|
6851
7478
|
}
|
|
6852
7479
|
onPopupApply() {
|
|
6853
|
-
const start = this.
|
|
6854
|
-
const end = this.
|
|
7480
|
+
const start = this.pickerState.rangeStart();
|
|
7481
|
+
const end = this.pickerState.rangeEnd();
|
|
6855
7482
|
if (start && end) {
|
|
6856
7483
|
const newRange = [start, end];
|
|
6857
7484
|
this.value.set(newRange);
|
|
@@ -6867,7 +7494,7 @@ class OlDateRangePicker {
|
|
|
6867
7494
|
onPopupCancel() {
|
|
6868
7495
|
if (!this.inline()) {
|
|
6869
7496
|
// Khôi phục lại state về giá trị cũ đã lưu trước đó
|
|
6870
|
-
this.
|
|
7497
|
+
this.pickerState.initRange(this.value());
|
|
6871
7498
|
const val = this.value();
|
|
6872
7499
|
this.startInputValue.set(val && val[0] ? formatDate(val[0], this.format()) : '');
|
|
6873
7500
|
this.endInputValue.set(val && val[1] ? formatDate(val[1], this.format()) : '');
|
|
@@ -6880,7 +7507,7 @@ class OlDateRangePicker {
|
|
|
6880
7507
|
this.endInputValue.set('');
|
|
6881
7508
|
this.onChange(null);
|
|
6882
7509
|
this.rangeChange.emit(null);
|
|
6883
|
-
this.
|
|
7510
|
+
this.pickerState.clear();
|
|
6884
7511
|
}
|
|
6885
7512
|
onStartInputChange(val) {
|
|
6886
7513
|
this.startInputValue.set(val);
|
|
@@ -6891,7 +7518,7 @@ class OlDateRangePicker {
|
|
|
6891
7518
|
this.value.set(range);
|
|
6892
7519
|
this.onChange(range);
|
|
6893
7520
|
this.rangeChange.emit(range);
|
|
6894
|
-
this.
|
|
7521
|
+
this.pickerState.initRange(range);
|
|
6895
7522
|
}
|
|
6896
7523
|
}
|
|
6897
7524
|
onEndInputChange(val) {
|
|
@@ -6903,7 +7530,7 @@ class OlDateRangePicker {
|
|
|
6903
7530
|
this.value.set(range);
|
|
6904
7531
|
this.onChange(range);
|
|
6905
7532
|
this.rangeChange.emit(range);
|
|
6906
|
-
this.
|
|
7533
|
+
this.pickerState.initRange(range);
|
|
6907
7534
|
}
|
|
6908
7535
|
}
|
|
6909
7536
|
clearRange(event) {
|
|
@@ -6913,7 +7540,7 @@ class OlDateRangePicker {
|
|
|
6913
7540
|
this.endInputValue.set('');
|
|
6914
7541
|
this.onChange(null);
|
|
6915
7542
|
this.rangeChange.emit(null);
|
|
6916
|
-
this.
|
|
7543
|
+
this.pickerState.clear();
|
|
6917
7544
|
}
|
|
6918
7545
|
/** Ngắt theo dõi vị trí và trả panel về chỗ cũ. Gọi được nhiều lần. */
|
|
6919
7546
|
detachPanelNow() {
|
|
@@ -6973,18 +7600,18 @@ class OlDateRangePicker {
|
|
|
6973
7600
|
this.detachPanelNow();
|
|
6974
7601
|
}
|
|
6975
7602
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDateRangePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6976
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlDateRangePicker, isStandalone: true, selector: "ol-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, startPlaceholder: { classPropertyName: "startPlaceholder", publicName: "startPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, endPlaceholder: { classPropertyName: "endPlaceholder", publicName: "endPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, fromLabel: { classPropertyName: "fromLabel", publicName: "fromLabel", isSignal: true, isRequired: false, transformFunction: null }, toLabel: { classPropertyName: "toLabel", publicName: "toLabel", isSignal: true, isRequired: false, transformFunction: null }, showInfoIcon: { classPropertyName: "showInfoIcon", publicName: "showInfoIcon", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dualMonth: { classPropertyName: "dualMonth", publicName: "dualMonth", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, daysDisabled: { classPropertyName: "daysDisabled", publicName: "daysDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesDisabled: { classPropertyName: "datesDisabled", publicName: "datesDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesEnabled: { classPropertyName: "datesEnabled", publicName: "datesEnabled", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", rangeChange: "rangeChange" }, host: { properties: { "class.z-dropdown": "isOpen()" }, classAttribute: "relative inline-block" }, providers: [
|
|
7603
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlDateRangePicker, isStandalone: true, selector: "ol-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, startPlaceholder: { classPropertyName: "startPlaceholder", publicName: "startPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, endPlaceholder: { classPropertyName: "endPlaceholder", publicName: "endPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null }, fromLabel: { classPropertyName: "fromLabel", publicName: "fromLabel", isSignal: true, isRequired: false, transformFunction: null }, toLabel: { classPropertyName: "toLabel", publicName: "toLabel", isSignal: true, isRequired: false, transformFunction: null }, showInfoIcon: { classPropertyName: "showInfoIcon", publicName: "showInfoIcon", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, dualMonth: { classPropertyName: "dualMonth", publicName: "dualMonth", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, daysDisabled: { classPropertyName: "daysDisabled", publicName: "daysDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesDisabled: { classPropertyName: "datesDisabled", publicName: "datesDisabled", isSignal: true, isRequired: false, transformFunction: null }, datesEnabled: { classPropertyName: "datesEnabled", publicName: "datesEnabled", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, showFooter: { classPropertyName: "showFooter", publicName: "showFooter", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", rangeChange: "rangeChange" }, host: { properties: { "class.z-dropdown": "isOpen()" }, classAttribute: "relative inline-block" }, providers: [
|
|
6977
7604
|
OlDatepickerStateService,
|
|
6978
7605
|
{
|
|
6979
7606
|
provide: NG_VALUE_ACCESSOR,
|
|
6980
7607
|
useExisting: forwardRef(() => OlDateRangePicker),
|
|
6981
7608
|
multi: true,
|
|
6982
7609
|
},
|
|
6983
|
-
], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"relative inline-block\"\n [class.w-full]=\"!inline()\"\n [class.max-w-xl]=\"!inline()\"\n [class.z-dropdown]=\"isOpen()\"\n>\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div class=\"flex flex-col gap-1.5 w-full\">\n @if (variant() === 'dual-inputs') {\n <!-- Figma Variant: 2 Separate Inputs (From & To) -->\n <div\n class=\"grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 w-full\"\n (click)=\"togglePopup($event)\"\n >\n <!-- From Input -->\n <div class=\"flex flex-col gap-1.5\">\n <label class=\"flex items-center gap-1 text-sm font-medium text-heading select-none\">\n <span>{{ fromLabel() }}</span>\n @if (required()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n @if (showInfoIcon()) {\n <ol-icon name=\"question-circle\" size=\"xs\" />\n }\n </label>\n\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none ps-3.5\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n type=\"text\"\n [value]=\"startInputValue()\"\n (input)=\"onStartInputChange($any($event.target).value)\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-neutral-secondary-medium border border-default-medium text-heading placeholder:text-body-subtle focus:outline-hidden focus:ring-2 focus:ring-brand-subtle focus:border-brand dark:focus:border-brand-subtle shadow-xs cursor-pointer transition-colors ps-10 pe-3\"\n [class]=\"inputSizeClasses()\"\n />\n </div>\n </div>\n\n <!-- To Input -->\n <div class=\"flex flex-col gap-1.5\">\n <label class=\"flex items-center gap-1 text-sm font-medium text-heading select-none\">\n <span>{{ toLabel() }}</span>\n @if (required()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n @if (showInfoIcon()) {\n <ol-icon name=\"question-circle\" size=\"xs\" />\n }\n </label>\n\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none ps-3.5\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n type=\"text\"\n [value]=\"endInputValue()\"\n (input)=\"onEndInputChange($any($event.target).value)\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-neutral-secondary-medium border border-default-medium text-heading placeholder:text-body-subtle focus:outline-hidden focus:ring-2 focus:ring-brand-subtle focus:border-brand dark:focus:border-brand-subtle shadow-xs cursor-pointer transition-colors ps-10 pe-8\"\n [class]=\"inputSizeClasses()\"\n />\n\n <!-- Clear button on the right input -->\n @if (clearable() && hasContent() && !disabled()) {\n <button\n type=\"button\"\n (click)=\"clearRange($event)\"\n class=\"absolute inset-y-0 inset-e-0 flex items-center pe-2.5 text-body-subtle hover:text-heading transition-colors cursor-pointer\"\n aria-label=\"Xo\u00E1 kho\u1EA3ng ng\u00E0y\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </div>\n </div>\n } @else {\n <!-- Joined Single Container Variant -->\n @if (label()) {\n <label class=\"flex items-center gap-1 text-sm font-medium text-heading\">\n <span>{{ label() }}</span>\n @if (required()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n }\n\n <div\n class=\"flex items-center gap-2 bg-neutral-secondary-medium border border-default-medium rounded-md p-1 shadow-xs focus-within:ring-2 focus-within:ring-brand-subtle focus-within:border-brand dark:focus-within:border-brand-subtle transition-all cursor-pointer\"\n [class]=\"joinedWrapperClasses()\"\n (click)=\"togglePopup($event)\"\n >\n <!-- Start Date Input -->\n <div class=\"relative flex-1 flex items-center\">\n <div class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none ps-3\">\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n type=\"text\"\n [value]=\"startInputValue()\"\n (input)=\"onStartInputChange($any($event.target).value)\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-transparent text-heading placeholder:text-body-subtle ps-9 pe-2 focus:outline-hidden cursor-pointer\"\n [class]=\"joinedInputClasses()\"\n />\n </div>\n\n <!-- Separator Arrow -->\n <span class=\"text-fg-disabled text-xs font-medium px-1 select-none\"> \u0111\u1EBFn </span>\n\n <!-- End Date Input -->\n <div class=\"relative flex-1 flex items-center\">\n <div class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none ps-3\">\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n type=\"text\"\n [value]=\"endInputValue()\"\n (input)=\"onEndInputChange($any($event.target).value)\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-transparent text-heading placeholder:text-body-subtle ps-9 pe-2 focus:outline-hidden cursor-pointer\"\n [class]=\"joinedInputClasses()\"\n />\n </div>\n\n <!-- Clear Button -->\n @if (clearable() && hasContent() && !disabled()) {\n <button\n type=\"button\"\n (click)=\"clearRange($event)\"\n class=\"text-body-subtle hover:text-heading p-1 mr-1 rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer\"\n aria-label=\"Xo\u00E1 kho\u1EA3ng ng\u00E0y \u0111\u00E3 ch\u1ECDn\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n }\n\n <!-- Helper Text -->\n @if (helperText()) {\n <p class=\"text-xs text-body-subtle\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Dropdown Popup -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown animate-in fade-in zoom-in-95 duration-100 rounded-base shadow-2xl w-max max-w-[calc(100vw-2rem)]\"\n >\n <ol-datepicker-popup\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n </div>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: OlDatepickerPopup, selector: "ol-datepicker-popup", inputs: ["showFooter", "dualMonth", "presets", "inline"], outputs: ["valueChange", "clearClick", "applyClick", "cancelClick"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
7610
|
+
], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"relative inline-block\"\n [class.w-full]=\"!inline()\"\n [class.max-w-xl]=\"!inline()\"\n [class.z-dropdown]=\"isOpen()\"\n>\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div [class]=\"stackClasses()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (variant() === 'dual-inputs') {\n <!-- Figma Variant: 2 Separate Inputs (From & To) -->\n <div\n class=\"grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 w-full\"\n (click)=\"togglePopup($event)\"\n >\n <!-- From Input -->\n <div [class]=\"columnClasses()\">\n @if (!floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"fromLabel()\"\n [forId]=\"startInputId\"\n [required]=\"required()\"\n [helpIcon]=\"showInfoIcon()\"\n [flush]=\"true\"\n />\n }\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"fromLabel()\"\n [forId]=\"startInputId\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"!!startInputValue()\"\n [open]=\"isOpen()\"\n >\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"leadPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"startInputId\"\n type=\"text\"\n [value]=\"startInputValue()\"\n (input)=\"onStartInputChange($any($event.target).value)\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"ol-field-box block w-full border text-heading placeholder:text-body-subtle focus:outline-hidden shadow-xs cursor-pointer transition-colors\"\n [class]=\"inputSizeClasses()\"\n />\n </div>\n </ol-field-frame>\n </div>\n\n <!-- To Input -->\n <div [class]=\"columnClasses()\">\n @if (!floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"toLabel()\"\n [forId]=\"endInputId\"\n [required]=\"required()\"\n [helpIcon]=\"showInfoIcon()\"\n [flush]=\"true\"\n />\n }\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"toLabel()\"\n [forId]=\"endInputId\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"!!endInputValue()\"\n [open]=\"isOpen()\"\n >\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"leadPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"endInputId\"\n type=\"text\"\n [value]=\"endInputValue()\"\n (input)=\"onEndInputChange($any($event.target).value)\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"ol-field-box block w-full border text-heading placeholder:text-body-subtle focus:outline-hidden shadow-xs cursor-pointer transition-colors\"\n [class]=\"endInputClasses()\"\n />\n\n <!-- Clear button on the right input -->\n @if (showClear()) {\n <button\n type=\"button\"\n (click)=\"clearRange($event)\"\n class=\"absolute inset-y-0 inset-e-0 flex items-center text-body-subtle hover:text-heading transition-colors cursor-pointer\"\n [class]=\"trailPadClass()\"\n aria-label=\"Xo\u00E1 kho\u1EA3ng ng\u00E0y\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </ol-field-frame>\n </div>\n </div>\n } @else {\n <!-- Joined Single Container Variant -->\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"startInputId\"\n [required]=\"required()\"\n [flush]=\"true\"\n />\n }\n\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"startInputId\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"hasContent()\"\n [open]=\"isOpen()\"\n >\n <div\n class=\"ol-field-box flex items-center gap-2 border rounded-md p-1 shadow-xs transition-all cursor-pointer\"\n [class]=\"joinedWrapperClasses()\"\n (click)=\"togglePopup($event)\"\n >\n <!-- Start Date Input -->\n <div class=\"relative flex-1 flex items-center\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"joinedIconPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"startInputId\"\n type=\"text\"\n [value]=\"startInputValue()\"\n (input)=\"onStartInputChange($any($event.target).value)\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-transparent text-heading placeholder:text-body-subtle pe-2 focus:outline-hidden cursor-pointer\"\n [class]=\"joinedInputClasses()\"\n />\n </div>\n\n <!-- Separator Arrow -->\n <span class=\"text-fg-disabled text-xs font-medium px-1 select-none\"> \u0111\u1EBFn </span>\n\n <!-- End Date Input -->\n <div class=\"relative flex-1 flex items-center\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"joinedIconPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"endInputId\"\n type=\"text\"\n [value]=\"endInputValue()\"\n (input)=\"onEndInputChange($any($event.target).value)\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-transparent text-heading placeholder:text-body-subtle pe-2 focus:outline-hidden cursor-pointer\"\n [class]=\"joinedInputClasses()\"\n />\n </div>\n\n <!-- Clear Button -->\n @if (clearable() && hasContent() && !disabled()) {\n <button\n type=\"button\"\n (click)=\"clearRange($event)\"\n class=\"text-body-subtle hover:text-heading p-1 mr-1 rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer\"\n aria-label=\"Xo\u00E1 kho\u1EA3ng ng\u00E0y \u0111\u00E3 ch\u1ECDn\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </ol-field-frame>\n }\n\n <!-- Helper Text -->\n @if (helperText()) {\n <p class=\"text-xs text-body-subtle\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Dropdown Popup -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown animate-in fade-in zoom-in-95 duration-100 rounded-base shadow-2xl w-max max-w-[calc(100vw-2rem)]\"\n >\n <ol-datepicker-popup\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n </div>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: OlDatepickerPopup, selector: "ol-datepicker-popup", inputs: ["showFooter", "dualMonth", "presets", "inline"], outputs: ["valueChange", "clearClick", "applyClick", "cancelClick"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6984
7611
|
}
|
|
6985
7612
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDateRangePicker, decorators: [{
|
|
6986
7613
|
type: Component,
|
|
6987
|
-
args: [{ selector: 'ol-date-range-picker', standalone: true, imports: [CommonModule, FormsModule, OlDatepickerPopup, OlIcon], providers: [
|
|
7614
|
+
args: [{ selector: 'ol-date-range-picker', standalone: true, imports: [CommonModule, FormsModule, OlDatepickerPopup, OlIcon, OlFieldFrame, OlFieldLabel], providers: [
|
|
6988
7615
|
OlDatepickerStateService,
|
|
6989
7616
|
{
|
|
6990
7617
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -7004,8 +7631,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
7004
7631
|
*/
|
|
7005
7632
|
class: 'relative inline-block',
|
|
7006
7633
|
'[class.z-dropdown]': 'isOpen()',
|
|
7007
|
-
}, template: "<div\n class=\"relative inline-block\"\n [class.w-full]=\"!inline()\"\n [class.max-w-xl]=\"!inline()\"\n [class.z-dropdown]=\"isOpen()\"\n>\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div class=\"
|
|
7008
|
-
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], startPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPlaceholder", required: false }] }], endPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "endPlaceholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], fromLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "fromLabel", required: false }] }], toLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toLabel", required: false }] }], showInfoIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInfoIcon", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], dualMonth: [{ type: i0.Input, args: [{ isSignal: true, alias: "dualMonth", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], daysDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "daysDisabled", required: false }] }], datesDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesDisabled", required: false }] }], datesEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesEnabled", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
7634
|
+
}, template: "<div\n class=\"relative inline-block\"\n [class.w-full]=\"!inline()\"\n [class.max-w-xl]=\"!inline()\"\n [class.z-dropdown]=\"isOpen()\"\n>\n @if (inline()) {\n <!-- Inline Calendar Mode -->\n <ol-datepicker-popup\n [inline]=\"true\"\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n } @else {\n <!-- Input Trigger & Dropdown Mode -->\n <div [class]=\"stackClasses()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (variant() === 'dual-inputs') {\n <!-- Figma Variant: 2 Separate Inputs (From & To) -->\n <div\n class=\"grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4 w-full\"\n (click)=\"togglePopup($event)\"\n >\n <!-- From Input -->\n <div [class]=\"columnClasses()\">\n @if (!floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"fromLabel()\"\n [forId]=\"startInputId\"\n [required]=\"required()\"\n [helpIcon]=\"showInfoIcon()\"\n [flush]=\"true\"\n />\n }\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"fromLabel()\"\n [forId]=\"startInputId\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"!!startInputValue()\"\n [open]=\"isOpen()\"\n >\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"leadPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"startInputId\"\n type=\"text\"\n [value]=\"startInputValue()\"\n (input)=\"onStartInputChange($any($event.target).value)\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"ol-field-box block w-full border text-heading placeholder:text-body-subtle focus:outline-hidden shadow-xs cursor-pointer transition-colors\"\n [class]=\"inputSizeClasses()\"\n />\n </div>\n </ol-field-frame>\n </div>\n\n <!-- To Input -->\n <div [class]=\"columnClasses()\">\n @if (!floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"toLabel()\"\n [forId]=\"endInputId\"\n [required]=\"required()\"\n [helpIcon]=\"showInfoIcon()\"\n [flush]=\"true\"\n />\n }\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"toLabel()\"\n [forId]=\"endInputId\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"!!endInputValue()\"\n [open]=\"isOpen()\"\n >\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"leadPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"endInputId\"\n type=\"text\"\n [value]=\"endInputValue()\"\n (input)=\"onEndInputChange($any($event.target).value)\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"ol-field-box block w-full border text-heading placeholder:text-body-subtle focus:outline-hidden shadow-xs cursor-pointer transition-colors\"\n [class]=\"endInputClasses()\"\n />\n\n <!-- Clear button on the right input -->\n @if (showClear()) {\n <button\n type=\"button\"\n (click)=\"clearRange($event)\"\n class=\"absolute inset-y-0 inset-e-0 flex items-center text-body-subtle hover:text-heading transition-colors cursor-pointer\"\n [class]=\"trailPadClass()\"\n aria-label=\"Xo\u00E1 kho\u1EA3ng ng\u00E0y\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </ol-field-frame>\n </div>\n </div>\n } @else {\n <!-- Joined Single Container Variant -->\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"startInputId\"\n [required]=\"required()\"\n [flush]=\"true\"\n />\n }\n\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"startInputId\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required()\"\n [hasValue]=\"hasContent()\"\n [open]=\"isOpen()\"\n >\n <div\n class=\"ol-field-box flex items-center gap-2 border rounded-md p-1 shadow-xs transition-all cursor-pointer\"\n [class]=\"joinedWrapperClasses()\"\n (click)=\"togglePopup($event)\"\n >\n <!-- Start Date Input -->\n <div class=\"relative flex-1 flex items-center\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"joinedIconPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"startInputId\"\n type=\"text\"\n [value]=\"startInputValue()\"\n (input)=\"onStartInputChange($any($event.target).value)\"\n [placeholder]=\"startPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-transparent text-heading placeholder:text-body-subtle pe-2 focus:outline-hidden cursor-pointer\"\n [class]=\"joinedInputClasses()\"\n />\n </div>\n\n <!-- Separator Arrow -->\n <span class=\"text-fg-disabled text-xs font-medium px-1 select-none\"> \u0111\u1EBFn </span>\n\n <!-- End Date Input -->\n <div class=\"relative flex-1 flex items-center\">\n <div\n class=\"absolute inset-y-0 inset-s-0 flex items-center pointer-events-none\"\n [class]=\"joinedIconPadClass()\"\n >\n <ol-icon name=\"calendar\" [size]=\"iconSize()\" />\n </div>\n <input\n [id]=\"endInputId\"\n type=\"text\"\n [value]=\"endInputValue()\"\n (input)=\"onEndInputChange($any($event.target).value)\"\n [placeholder]=\"endPlaceholder()\"\n [disabled]=\"disabled()\"\n class=\"block w-full bg-transparent text-heading placeholder:text-body-subtle pe-2 focus:outline-hidden cursor-pointer\"\n [class]=\"joinedInputClasses()\"\n />\n </div>\n\n <!-- Clear Button -->\n @if (clearable() && hasContent() && !disabled()) {\n <button\n type=\"button\"\n (click)=\"clearRange($event)\"\n class=\"text-body-subtle hover:text-heading p-1 mr-1 rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer\"\n aria-label=\"Xo\u00E1 kho\u1EA3ng ng\u00E0y \u0111\u00E3 ch\u1ECDn\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </div>\n </ol-field-frame>\n }\n\n <!-- Helper Text -->\n @if (helperText()) {\n <p class=\"text-xs text-body-subtle\">\n {{ helperText() }}\n </p>\n }\n\n <!-- Dropdown Popup -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown animate-in fade-in zoom-in-95 duration-100 rounded-base shadow-2xl w-max max-w-[calc(100vw-2rem)]\"\n >\n <ol-datepicker-popup\n [showFooter]=\"showFooter()\"\n [dualMonth]=\"dualMonth()\"\n [presets]=\"presets()\"\n (valueChange)=\"onValueSelected($event)\"\n (clearClick)=\"onPopupClear()\"\n (applyClick)=\"onPopupApply()\"\n (cancelClick)=\"onPopupCancel()\"\n />\n </div>\n }\n </div>\n }\n</div>\n" }]
|
|
7635
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], startPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "startPlaceholder", required: false }] }], endPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "endPlaceholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], fromLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "fromLabel", required: false }] }], toLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "toLabel", required: false }] }], showInfoIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInfoIcon", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], dualMonth: [{ type: i0.Input, args: [{ isSignal: true, alias: "dualMonth", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], daysDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "daysDisabled", required: false }] }], datesDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesDisabled", required: false }] }], datesEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "datesEnabled", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], showFooter: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFooter", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], rangeChange: [{ type: i0.Output, args: ["rangeChange"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
7009
7636
|
|
|
7010
7637
|
class OlDatepickerDirective {
|
|
7011
7638
|
el = inject((ElementRef));
|
|
@@ -7934,48 +8561,485 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
7934
8561
|
}]
|
|
7935
8562
|
}], propDecorators: { heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }] } });
|
|
7936
8563
|
|
|
7937
|
-
|
|
8564
|
+
let nextInputId = 0;
|
|
8565
|
+
class OlInput {
|
|
8566
|
+
/** Id tự sinh, dùng khi nơi gọi không truyền id — để <label for> và
|
|
8567
|
+
* aria-describedby luôn nối được với ô nhập. */
|
|
8568
|
+
uid = `ol-input-${nextInputId++}`;
|
|
8569
|
+
value = model('', /* @ts-ignore */
|
|
8570
|
+
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
8571
|
+
type = input('text', /* @ts-ignore */
|
|
8572
|
+
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
8573
|
+
label = input('', /* @ts-ignore */
|
|
8574
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
8575
|
+
placeholder = input('', /* @ts-ignore */
|
|
8576
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8577
|
+
helperText = input('', /* @ts-ignore */
|
|
8578
|
+
...(ngDevMode ? [{ debugName: "helperText" }] : /* istanbul ignore next */ []));
|
|
7938
8579
|
/**
|
|
7939
|
-
*
|
|
7940
|
-
*
|
|
8580
|
+
* `model()` chứ không `input()`: Reactive Forms gọi `setDisabledState()` khi
|
|
8581
|
+
* form khoá/mở control, mà `input()` chỉ đọc nên không ghi được. Cách dùng
|
|
8582
|
+
* từ ngoài KHÔNG đổi — `[disabled]="true"` và `disabled()` vẫn như cũ.
|
|
7941
8583
|
*/
|
|
7942
|
-
|
|
7943
|
-
...(ngDevMode ? [{ debugName: "
|
|
7944
|
-
|
|
7945
|
-
|
|
7946
|
-
|
|
7947
|
-
|
|
7948
|
-
|
|
7949
|
-
}
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
7953
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
7968
|
-
|
|
7969
|
-
|
|
7970
|
-
|
|
7971
|
-
|
|
7972
|
-
|
|
7973
|
-
|
|
7974
|
-
|
|
7975
|
-
|
|
7976
|
-
|
|
7977
|
-
|
|
7978
|
-
|
|
8584
|
+
disabled = model(false, /* @ts-ignore */
|
|
8585
|
+
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
8586
|
+
readonly = input(false, /* @ts-ignore */
|
|
8587
|
+
...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
8588
|
+
size = input('base', /* @ts-ignore */
|
|
8589
|
+
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
8590
|
+
state = input('default', /* @ts-ignore */
|
|
8591
|
+
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
8592
|
+
required = input(false, /* @ts-ignore */
|
|
8593
|
+
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
8594
|
+
showAsterisk = input(false, /* @ts-ignore */
|
|
8595
|
+
...(ngDevMode ? [{ debugName: "showAsterisk" }] : /* istanbul ignore next */ []));
|
|
8596
|
+
labelIcon = input(false, /* @ts-ignore */
|
|
8597
|
+
...(ngDevMode ? [{ debugName: "labelIcon" }] : /* istanbul ignore next */ []));
|
|
8598
|
+
labelTooltip = input('', /* @ts-ignore */
|
|
8599
|
+
...(ngDevMode ? [{ debugName: "labelTooltip" }] : /* istanbul ignore next */ []));
|
|
8600
|
+
/**
|
|
8601
|
+
* Tên trợ năng khi ô nhập KHÔNG có nhãn nhìn thấy được.
|
|
8602
|
+
*
|
|
8603
|
+
* `<label for>` chỉ render khi `label()` có chữ (`@if (label())` ở đầu
|
|
8604
|
+
* template), nên bố cục đặt chữ BÊN NGOÀI component — bảng lọc, thanh công
|
|
8605
|
+
* cụ, trang cài đặt — để lại một `<input>` hoàn toàn không tên. Có `label()`
|
|
8606
|
+
* thì không cần truyền cái này; hai thứ cùng lúc thì `aria-label` thắng và
|
|
8607
|
+
* người dùng bàn phím nghe một đằng, nhìn một nẻo.
|
|
8608
|
+
*/
|
|
8609
|
+
ariaLabel = input('', /* @ts-ignore */
|
|
8610
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
8611
|
+
prefixText = input('', /* @ts-ignore */
|
|
8612
|
+
...(ngDevMode ? [{ debugName: "prefixText" }] : /* istanbul ignore next */ []));
|
|
8613
|
+
suffixText = input('', /* @ts-ignore */
|
|
8614
|
+
...(ngDevMode ? [{ debugName: "suffixText" }] : /* istanbul ignore next */ []));
|
|
8615
|
+
addOnIcon = input(false, /* @ts-ignore */
|
|
8616
|
+
...(ngDevMode ? [{ debugName: "addOnIcon" }] : /* istanbul ignore next */ []));
|
|
8617
|
+
leftIcon = input(false, /* @ts-ignore */
|
|
8618
|
+
...(ngDevMode ? [{ debugName: "leftIcon" }] : /* istanbul ignore next */ []));
|
|
8619
|
+
rightIcon = input(false, /* @ts-ignore */
|
|
8620
|
+
...(ngDevMode ? [{ debugName: "rightIcon" }] : /* istanbul ignore next */ []));
|
|
8621
|
+
/**
|
|
8622
|
+
* Hiện nút xoá khi ô có chữ.
|
|
8623
|
+
*
|
|
8624
|
+
* Mặc định **TẮT**, khác `ol-select` và `ol-datepicker` (mặc định bật).
|
|
8625
|
+
* Lý do: hai cái kia chọn từ danh sách nên xoá là thao tác thường gặp, còn
|
|
8626
|
+
* ô nhập chữ thì người dùng vẫn chọn-hết-rồi-gõ-đè như mọi ô nhập khác.
|
|
8627
|
+
* Bật sẵn cho MỌI `ol-input` đang chạy là thêm một nút vào giao diện của
|
|
8628
|
+
* người khác mà họ không yêu cầu.
|
|
8629
|
+
*/
|
|
8630
|
+
clearable = input(false, /* @ts-ignore */
|
|
8631
|
+
...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
|
|
8632
|
+
/** Tên trợ năng của nút xoá. Đổi được khi ô có nghĩa cụ thể. */
|
|
8633
|
+
clearAriaLabel = input('Xoá nội dung', /* @ts-ignore */
|
|
8634
|
+
...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : /* istanbul ignore next */ []));
|
|
8635
|
+
/** Phát khi người dùng bấm nút xoá — nơi dùng biết để tải lại danh sách. */
|
|
8636
|
+
cleared = output();
|
|
8637
|
+
rightHelperText = input('', /* @ts-ignore */
|
|
8638
|
+
...(ngDevMode ? [{ debugName: "rightHelperText" }] : /* istanbul ignore next */ []));
|
|
8639
|
+
id = input('', /* @ts-ignore */
|
|
8640
|
+
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
8641
|
+
/**
|
|
8642
|
+
* Nhãn nổi: `label` nằm TRONG khung, nổi lên khi focus hoặc có chữ — thay
|
|
8643
|
+
* cho nhãn phía trên ô. Xem `OlFieldFrame`.
|
|
8644
|
+
*/
|
|
8645
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
8646
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
8647
|
+
/**
|
|
8648
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
8649
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
8650
|
+
*/
|
|
8651
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
8652
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
8653
|
+
/**
|
|
8654
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
8655
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
8656
|
+
* mép ô thẳng hàng.
|
|
8657
|
+
*/
|
|
8658
|
+
labelWidth = input('', /* @ts-ignore */
|
|
8659
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
8660
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
8661
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
8662
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
8663
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
8664
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
8665
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
8666
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
8667
|
+
/** Có chữ → nhãn nổi. `0` (input number) là CÓ giá trị. */
|
|
8668
|
+
hasValue = computed(() => {
|
|
8669
|
+
const v = this.value();
|
|
8670
|
+
return v !== '' && v !== null && v !== undefined;
|
|
8671
|
+
}, /* @ts-ignore */
|
|
8672
|
+
...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
|
|
8673
|
+
/** Id thực dùng: ưu tiên id do nơi gọi truyền, không thì dùng id tự sinh. */
|
|
8674
|
+
inputId = computed(() => this.id() || this.uid, /* @ts-ignore */
|
|
8675
|
+
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
8676
|
+
helperId = computed(() => `${this.inputId()}-helper`, /* @ts-ignore */
|
|
8677
|
+
...(ngDevMode ? [{ debugName: "helperId" }] : /* istanbul ignore next */ []));
|
|
8678
|
+
/** Nối ô nhập với dòng gợi ý/lỗi cho trình đọc màn hình. */
|
|
8679
|
+
describedBy = computed(() => (this.helperText() ? this.helperId() : null), /* @ts-ignore */
|
|
8680
|
+
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
8681
|
+
/** Báo trạng thái lỗi cho công nghệ trợ giúp. */
|
|
8682
|
+
ariaInvalid = computed(() => (this.state() === 'error' ? 'true' : null), /* @ts-ignore */
|
|
8683
|
+
...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
|
|
8684
|
+
/**
|
|
8685
|
+
* Padding ngang của ô nhập, theo Figma ol-input:
|
|
8686
|
+
* sm 10px · base 12px · lg 14px · xl 16px.
|
|
8687
|
+
*
|
|
8688
|
+
* Phải gom vào một chuỗi thay vì dùng [class.px-2.5] trên template —
|
|
8689
|
+
* Angular cắt phần sau dấu chấm nên [class.px-2.5] render ra "px-2",
|
|
8690
|
+
* lệch 2px so với thiết kế.
|
|
8691
|
+
*/
|
|
8692
|
+
paddingClasses = computed(() => {
|
|
8693
|
+
const start = this.hasLeadingIcon() ? 'ps-0' : FIELD_PADDING_START[this.size()];
|
|
8694
|
+
const end = this.hasTrailingIcon() || this.showClear() ? 'pe-0' : FIELD_PADDING_END[this.size()];
|
|
8695
|
+
return `${start} ${end}`;
|
|
8696
|
+
}, /* @ts-ignore */
|
|
8697
|
+
...(ngDevMode ? [{ debugName: "paddingClasses" }] : /* istanbul ignore next */ []));
|
|
8698
|
+
/**
|
|
8699
|
+
* Khoảng icon ↔ chữ đúng 8px (`FIELD_ICON_GAP`): icon mang lề đầu/cuối của
|
|
8700
|
+
* khung, ô gõ bỏ lề phía có icon. Khung KHÔNG có `gap` — add-on chữ/icon
|
|
8701
|
+
* phải sát ô gõ (Figma Type=Add-on: gap 0).
|
|
8702
|
+
*/
|
|
8703
|
+
hasLeadingIcon = computed(() => this.leftIcon() && !this.addOnIcon(), /* @ts-ignore */
|
|
8704
|
+
...(ngDevMode ? [{ debugName: "hasLeadingIcon" }] : /* istanbul ignore next */ []));
|
|
8705
|
+
hasTrailingIcon = computed(() => this.rightIcon() || this.state() === 'error' || this.state() === 'success', /* @ts-ignore */
|
|
8706
|
+
...(ngDevMode ? [{ debugName: "hasTrailingIcon" }] : /* istanbul ignore next */ []));
|
|
8707
|
+
showClear = computed(() => this.clearable() && !!this.value() && !this.disabled() && !this.readonly(), /* @ts-ignore */
|
|
8708
|
+
...(ngDevMode ? [{ debugName: "showClear" }] : /* istanbul ignore next */ []));
|
|
8709
|
+
leadIconClasses = computed(() => `flex shrink-0 items-center pe-2 ${FIELD_PADDING_START[this.size()]} text-body-subtle pointer-events-none`, /* @ts-ignore */
|
|
8710
|
+
...(ngDevMode ? [{ debugName: "leadIconClasses" }] : /* istanbul ignore next */ []));
|
|
8711
|
+
/** Nút xoá đứng trước icon trạng thái thì để `ps-2` của icon tạo khoảng. */
|
|
8712
|
+
clearClasses = computed(() => `flex shrink-0 cursor-pointer items-center py-0.5 ps-2 text-body-subtle transition-colors hover:text-heading${this.hasTrailingIcon() ? '' : ` ${FIELD_PADDING_END[this.size()]}`}`, /* @ts-ignore */
|
|
8713
|
+
...(ngDevMode ? [{ debugName: "clearClasses" }] : /* istanbul ignore next */ []));
|
|
8714
|
+
trailIconClasses = computed(() => `flex shrink-0 items-center ps-2 ${FIELD_PADDING_END[this.size()]}`, /* @ts-ignore */
|
|
8715
|
+
...(ngDevMode ? [{ debugName: "trailIconClasses" }] : /* istanbul ignore next */ []));
|
|
8716
|
+
// ── ControlValueAccessor ─────────────────────────────────────────
|
|
8717
|
+
/**
|
|
8718
|
+
* Bốn phương thức này là thứ Angular Forms gọi tới. Thiếu chúng thì
|
|
8719
|
+
* `formControlName` / `[formControl]` KHÔNG gắn được — dùng component trong
|
|
8720
|
+
* Reactive Forms là lỗi runtime "No value accessor for form control".
|
|
8721
|
+
*/
|
|
8722
|
+
onChange = () => { };
|
|
8723
|
+
onTouched = () => { };
|
|
8724
|
+
writeValue(val) {
|
|
8725
|
+
this.value.set(val ?? '');
|
|
8726
|
+
}
|
|
8727
|
+
registerOnChange(fn) {
|
|
8728
|
+
this.onChange = fn;
|
|
8729
|
+
}
|
|
8730
|
+
registerOnTouched(fn) {
|
|
8731
|
+
this.onTouched = fn;
|
|
8732
|
+
}
|
|
8733
|
+
setDisabledState(isDisabled) {
|
|
8734
|
+
this.disabled.set(isDisabled);
|
|
8735
|
+
}
|
|
8736
|
+
/** Gọi từ template mỗi khi giá trị đổi, để form biết. */
|
|
8737
|
+
emitChange(val) {
|
|
8738
|
+
this.value.set(val);
|
|
8739
|
+
this.onChange(val);
|
|
8740
|
+
}
|
|
8741
|
+
/**
|
|
8742
|
+
* Xoá nội dung ô.
|
|
8743
|
+
*
|
|
8744
|
+
* `stopPropagation` vì nút nằm TRONG thẻ bọc: thiếu nó thì cú bấm lan ra
|
|
8745
|
+
* thẻ bọc, và ở component có hành vi bấm-để-mở (select, datepicker) sẽ mở
|
|
8746
|
+
* panel ngay sau khi xoá.
|
|
8747
|
+
*/
|
|
8748
|
+
clearValue(event) {
|
|
8749
|
+
event.stopPropagation();
|
|
8750
|
+
this.value.set('');
|
|
8751
|
+
this.onChange('');
|
|
8752
|
+
this.cleared.emit();
|
|
8753
|
+
}
|
|
8754
|
+
/** Gọi khi rời ô nhập — form dùng để đánh dấu `touched`. */
|
|
8755
|
+
markTouched() {
|
|
8756
|
+
this.onTouched();
|
|
8757
|
+
}
|
|
8758
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8759
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlInput, isStandalone: true, selector: "ol-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, labelIcon: { classPropertyName: "labelIcon", publicName: "labelIcon", isSignal: true, isRequired: false, transformFunction: null }, labelTooltip: { classPropertyName: "labelTooltip", publicName: "labelTooltip", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, prefixText: { classPropertyName: "prefixText", publicName: "prefixText", isSignal: true, isRequired: false, transformFunction: null }, suffixText: { classPropertyName: "suffixText", publicName: "suffixText", isSignal: true, isRequired: false, transformFunction: null }, addOnIcon: { classPropertyName: "addOnIcon", publicName: "addOnIcon", isSignal: true, isRequired: false, transformFunction: null }, leftIcon: { classPropertyName: "leftIcon", publicName: "leftIcon", isSignal: true, isRequired: false, transformFunction: null }, rightIcon: { classPropertyName: "rightIcon", publicName: "rightIcon", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, clearAriaLabel: { classPropertyName: "clearAriaLabel", publicName: "clearAriaLabel", isSignal: true, isRequired: false, transformFunction: null }, rightHelperText: { classPropertyName: "rightHelperText", publicName: "rightHelperText", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", cleared: "cleared" }, host: { classAttribute: "block w-full" }, providers: [
|
|
8760
|
+
{
|
|
8761
|
+
provide: NG_VALUE_ACCESSOR,
|
|
8762
|
+
useExisting: forwardRef(() => OlInput),
|
|
8763
|
+
multi: true,
|
|
8764
|
+
},
|
|
8765
|
+
], ngImport: i0, template: "<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required() || showAsterisk()\"\n [helpIcon]=\"labelIcon()\"\n [helpTooltip]=\"labelTooltip()\"\n />\n }\n\n <!--\n Icon \u0111\u1EA7u chi\u1EBFu M\u1ED8T l\u1EA7n v\u00E0o ng-template, r\u1ED3i d\u1EF1ng l\u1EA1i \u1EDF nh\u00E1nh c\u1EA7n hi\u1EC7n.\n B\u1EA3n c\u0169 khai hai <ng-content select=\"[leftIcon]\"> (m\u1ED9t trong nh\u00E1nh addOn):\n Angular chi\u1EBFu n\u1ED9i dung v\u00E0o slot \u0110\u1EA6U TI\u00CAN kh\u1EDBp theo th\u1EE9 t\u1EF1 template \u2014 t\u1EE9c\n slot n\u1EB1m trong @if (addOnIcon()) \u0111ang t\u1EAFt \u2014 n\u00EAn icon [leftIcon] kh\u00F4ng bao\n gi\u1EDD hi\u1EC7n (\u0111o /floating-label 2026-09-28: 0/28 \u00F4 c\u00F3 icon).\n -->\n <ng-template #leadIcon><ng-content select=\"[addOnIcon], [leftIcon]\" /></ng-template>\n\n <!--\n Khung nh\u00E3n n\u1ED5i d\u00F9ng chung: t\u1EAFt (floatingLabel = false) th\u00EC trong su\u1ED1t, gi\u1EEF\n nguy\u00EAn giao di\u1EC7n c\u0169. B\u1EADt th\u00EC nh\u00E3n n\u1EB1m trong khung, n\u1ED5i khi focus/c\u00F3 ch\u1EEF.\n -->\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"hasValue()\"\n >\n <!-- Main Unified Input Container (S\u1EED d\u1EE5ng Semantic Token Classes 100%) -->\n <div\n class=\"ol-field-box relative flex items-center w-full border shadow-xs overflow-hidden rounded-md\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.focus-within:border-brand]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.focus-within:ring-2]=\"\n (!disabled() && !readonly()) ||\n (state() === 'success' && !disabled()) ||\n (state() === 'error' && !disabled())\n \"\n [class.focus-within:ring-brand-subtle]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.dark:focus-within:ring-brand-950]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.focus-within:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus-within:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus-within:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus-within:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-disabled]=\"readonly() || disabled()\"\n [class.cursor-default]=\"readonly()\"\n [class.opacity-60]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n >\n <!-- Add-on Icon Box -->\n @if (addOnIcon()) {\n <span\n class=\"inline-flex items-center justify-center px-3.5 h-full text-body bg-neutral-tertiary border-e border-default-medium select-none shrink-0\"\n >\n <ng-container [ngTemplateOutlet]=\"leadIcon\" />\n </span>\n }\n\n <!-- Prefix Add-on Text -->\n @if (prefixText()) {\n <span\n class=\"inline-flex items-center px-3.5 h-full text-sm font-normal text-body bg-neutral-tertiary border-e border-default-medium select-none shrink-0\"\n >\n {{ prefixText() }}\n </span>\n }\n\n <!-- Left Icon (Inside Input) -->\n @if (hasLeadingIcon()) {\n <div [class]=\"leadIconClasses()\">\n <ng-container [ngTemplateOutlet]=\"leadIcon\" />\n </div>\n }\n\n <!-- Real HTML Input -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [type]=\"type()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"emitChange($event)\"\n (blur)=\"markTouched()\"\n class=\"block w-full h-full bg-transparent border-0 outline-none placeholder:text-body-subtle font-normal\"\n [class]=\"paddingClasses()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-heading]=\"(state() === 'default' && !disabled() && !readonly()) || readonly()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.cursor-default]=\"readonly()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n />\n\n <!--\n N\u00DAT XO\u00C1 \u2014 \u0111\u1EB7t TR\u01AF\u1EDAC icon tr\u1EA1ng th\u00E1i.\n\n Icon l\u1ED7i/th\u00E0nh c\u00F4ng ch\u1EC9 \u0111\u1EC3 \u0111\u1ECDc, n\u00FAt xo\u00E1 th\u00EC b\u1EA5m \u0111\u01B0\u1EE3c; \u0111\u1EB7t n\u00FAt sau th\u00EC \u1EDF\n tr\u1EA1ng th\u00E1i l\u1ED7i n\u00F3 bi\u1EBFn m\u1EA5t, \u0111\u00FAng l\u00FAc ng\u01B0\u1EDDi d\u00F9ng mu\u1ED1n xo\u00E1 \u0111i g\u00F5 l\u1EA1i.\n\n \u1EA8n khi `readonly` ho\u1EB7c `disabled`: \u00F4 kh\u00F4ng s\u1EEDa \u0111\u01B0\u1EE3c th\u00EC n\u00FAt xo\u00E1 l\u00E0 m\u1EDDi\n b\u1EA5m v\u00E0o ch\u1ED7 kh\u00F4ng l\u00E0m g\u00EC.\n -->\n @if (showClear()) {\n <button\n type=\"button\"\n [class]=\"clearClasses()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n (click)=\"clearValue($event)\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n\n <!-- Right Icon / Status Icon -->\n @if (state() === 'error') {\n <div class=\"text-fg-danger\" [class]=\"trailIconClasses()\">\n <ol-icon name=\"info-circle\" size=\"sm\" />\n </div>\n } @else if (state() === 'success') {\n <div class=\"text-fg-success\" [class]=\"trailIconClasses()\">\n <ol-icon name=\"check-circle\" size=\"sm\" />\n </div>\n } @else if (rightIcon()) {\n <div class=\"text-body-subtle\" [class]=\"trailIconClasses()\">\n <ng-content select=\"[rightIcon]\" />\n </div>\n }\n\n <!-- Suffix Add-on Text -->\n @if (suffixText()) {\n <span\n class=\"inline-flex items-center px-3.5 h-full text-sm font-normal text-body bg-neutral-tertiary border-s border-default-medium select-none shrink-0\"\n >\n {{ suffixText() }}\n </span>\n }\n </div>\n </ol-field-frame>\n\n <!-- Helper Text & Right Helper Action -->\n @if (helperText() || rightHelperText()) {\n <div class=\"flex items-center justify-between mt-1.5 text-xs\">\n @if (helperText()) {\n <p\n [id]=\"helperId()\"\n [attr.role]=\"state() === 'error' ? 'alert' : null\"\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger]=\"state() === 'error'\"\n [class.text-fg-success]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n @if (rightHelperText()) {\n <span class=\"text-xs text-fg-brand hover:underline cursor-pointer ms-auto font-medium\">\n {{ rightHelperText() }}\n </span>\n }\n <ng-content select=\"[rightHelperAction]\" />\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8766
|
+
}
|
|
8767
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlInput, decorators: [{
|
|
8768
|
+
type: Component,
|
|
8769
|
+
args: [{ selector: 'ol-input', standalone: true, imports: [FormsModule, NgTemplateOutlet, OlIcon, OlFieldFrame, OlFieldLabel], host: {
|
|
8770
|
+
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
8771
|
+
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
8772
|
+
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
8773
|
+
// thêm nút xoá. Sửa từng cái là vá lẻ, nên chốt cho cả họ.
|
|
8774
|
+
class: 'block w-full',
|
|
8775
|
+
}, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
8776
|
+
{
|
|
8777
|
+
provide: NG_VALUE_ACCESSOR,
|
|
8778
|
+
useExisting: forwardRef(() => OlInput),
|
|
8779
|
+
multi: true,
|
|
8780
|
+
},
|
|
8781
|
+
], template: "<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required() || showAsterisk()\"\n [helpIcon]=\"labelIcon()\"\n [helpTooltip]=\"labelTooltip()\"\n />\n }\n\n <!--\n Icon \u0111\u1EA7u chi\u1EBFu M\u1ED8T l\u1EA7n v\u00E0o ng-template, r\u1ED3i d\u1EF1ng l\u1EA1i \u1EDF nh\u00E1nh c\u1EA7n hi\u1EC7n.\n B\u1EA3n c\u0169 khai hai <ng-content select=\"[leftIcon]\"> (m\u1ED9t trong nh\u00E1nh addOn):\n Angular chi\u1EBFu n\u1ED9i dung v\u00E0o slot \u0110\u1EA6U TI\u00CAN kh\u1EDBp theo th\u1EE9 t\u1EF1 template \u2014 t\u1EE9c\n slot n\u1EB1m trong @if (addOnIcon()) \u0111ang t\u1EAFt \u2014 n\u00EAn icon [leftIcon] kh\u00F4ng bao\n gi\u1EDD hi\u1EC7n (\u0111o /floating-label 2026-09-28: 0/28 \u00F4 c\u00F3 icon).\n -->\n <ng-template #leadIcon><ng-content select=\"[addOnIcon], [leftIcon]\" /></ng-template>\n\n <!--\n Khung nh\u00E3n n\u1ED5i d\u00F9ng chung: t\u1EAFt (floatingLabel = false) th\u00EC trong su\u1ED1t, gi\u1EEF\n nguy\u00EAn giao di\u1EC7n c\u0169. B\u1EADt th\u00EC nh\u00E3n n\u1EB1m trong khung, n\u1ED5i khi focus/c\u00F3 ch\u1EEF.\n -->\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"hasValue()\"\n >\n <!-- Main Unified Input Container (S\u1EED d\u1EE5ng Semantic Token Classes 100%) -->\n <div\n class=\"ol-field-box relative flex items-center w-full border shadow-xs overflow-hidden rounded-md\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.focus-within:border-brand]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.focus-within:ring-2]=\"\n (!disabled() && !readonly()) ||\n (state() === 'success' && !disabled()) ||\n (state() === 'error' && !disabled())\n \"\n [class.focus-within:ring-brand-subtle]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.dark:focus-within:ring-brand-950]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.focus-within:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus-within:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus-within:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus-within:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-disabled]=\"readonly() || disabled()\"\n [class.cursor-default]=\"readonly()\"\n [class.opacity-60]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n >\n <!-- Add-on Icon Box -->\n @if (addOnIcon()) {\n <span\n class=\"inline-flex items-center justify-center px-3.5 h-full text-body bg-neutral-tertiary border-e border-default-medium select-none shrink-0\"\n >\n <ng-container [ngTemplateOutlet]=\"leadIcon\" />\n </span>\n }\n\n <!-- Prefix Add-on Text -->\n @if (prefixText()) {\n <span\n class=\"inline-flex items-center px-3.5 h-full text-sm font-normal text-body bg-neutral-tertiary border-e border-default-medium select-none shrink-0\"\n >\n {{ prefixText() }}\n </span>\n }\n\n <!-- Left Icon (Inside Input) -->\n @if (hasLeadingIcon()) {\n <div [class]=\"leadIconClasses()\">\n <ng-container [ngTemplateOutlet]=\"leadIcon\" />\n </div>\n }\n\n <!-- Real HTML Input -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [type]=\"type()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"placeholder()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"emitChange($event)\"\n (blur)=\"markTouched()\"\n class=\"block w-full h-full bg-transparent border-0 outline-none placeholder:text-body-subtle font-normal\"\n [class]=\"paddingClasses()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-heading]=\"(state() === 'default' && !disabled() && !readonly()) || readonly()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.cursor-default]=\"readonly()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n />\n\n <!--\n N\u00DAT XO\u00C1 \u2014 \u0111\u1EB7t TR\u01AF\u1EDAC icon tr\u1EA1ng th\u00E1i.\n\n Icon l\u1ED7i/th\u00E0nh c\u00F4ng ch\u1EC9 \u0111\u1EC3 \u0111\u1ECDc, n\u00FAt xo\u00E1 th\u00EC b\u1EA5m \u0111\u01B0\u1EE3c; \u0111\u1EB7t n\u00FAt sau th\u00EC \u1EDF\n tr\u1EA1ng th\u00E1i l\u1ED7i n\u00F3 bi\u1EBFn m\u1EA5t, \u0111\u00FAng l\u00FAc ng\u01B0\u1EDDi d\u00F9ng mu\u1ED1n xo\u00E1 \u0111i g\u00F5 l\u1EA1i.\n\n \u1EA8n khi `readonly` ho\u1EB7c `disabled`: \u00F4 kh\u00F4ng s\u1EEDa \u0111\u01B0\u1EE3c th\u00EC n\u00FAt xo\u00E1 l\u00E0 m\u1EDDi\n b\u1EA5m v\u00E0o ch\u1ED7 kh\u00F4ng l\u00E0m g\u00EC.\n -->\n @if (showClear()) {\n <button\n type=\"button\"\n [class]=\"clearClasses()\"\n [attr.aria-label]=\"clearAriaLabel()\"\n (click)=\"clearValue($event)\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n\n <!-- Right Icon / Status Icon -->\n @if (state() === 'error') {\n <div class=\"text-fg-danger\" [class]=\"trailIconClasses()\">\n <ol-icon name=\"info-circle\" size=\"sm\" />\n </div>\n } @else if (state() === 'success') {\n <div class=\"text-fg-success\" [class]=\"trailIconClasses()\">\n <ol-icon name=\"check-circle\" size=\"sm\" />\n </div>\n } @else if (rightIcon()) {\n <div class=\"text-body-subtle\" [class]=\"trailIconClasses()\">\n <ng-content select=\"[rightIcon]\" />\n </div>\n }\n\n <!-- Suffix Add-on Text -->\n @if (suffixText()) {\n <span\n class=\"inline-flex items-center px-3.5 h-full text-sm font-normal text-body bg-neutral-tertiary border-s border-default-medium select-none shrink-0\"\n >\n {{ suffixText() }}\n </span>\n }\n </div>\n </ol-field-frame>\n\n <!-- Helper Text & Right Helper Action -->\n @if (helperText() || rightHelperText()) {\n <div class=\"flex items-center justify-between mt-1.5 text-xs\">\n @if (helperText()) {\n <p\n [id]=\"helperId()\"\n [attr.role]=\"state() === 'error' ? 'alert' : null\"\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger]=\"state() === 'error'\"\n [class.text-fg-success]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n @if (rightHelperText()) {\n <span class=\"text-xs text-fg-brand hover:underline cursor-pointer ms-auto font-medium\">\n {{ rightHelperText() }}\n </span>\n }\n <ng-content select=\"[rightHelperAction]\" />\n </div>\n }\n</div>\n" }]
|
|
8782
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], labelIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelIcon", required: false }] }], labelTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelTooltip", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], prefixText: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixText", required: false }] }], suffixText: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixText", required: false }] }], addOnIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "addOnIcon", required: false }] }], leftIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftIcon", required: false }] }], rightIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "rightIcon", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], clearAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearAriaLabel", required: false }] }], cleared: [{ type: i0.Output, args: ["cleared"] }], rightHelperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "rightHelperText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }] } });
|
|
8783
|
+
|
|
8784
|
+
/**
|
|
8785
|
+
* Ô tìm ở đầu menu — Figma `ol-dropdown-header` Type=Form (1149:39893): một
|
|
8786
|
+
* `ol-input` cỡ sm có icon tìm, đặt trên danh sách (`ol-dropdown` Type=Users,
|
|
8787
|
+
* Type=With scroll).
|
|
8788
|
+
*
|
|
8789
|
+
* Component KHÔNG tự lọc: mục trong menu do nơi dùng chiếu vào, có thể lấy từ
|
|
8790
|
+
* máy chủ, nên nơi dùng lọc theo `query` rồi hiện `ol-dropdown-state` khi rỗng
|
|
8791
|
+
* hoặc đang tải.
|
|
8792
|
+
*
|
|
8793
|
+
* Bàn phím: ↓ xuống mục đầu, Enter chọn mục đầu còn bấm được. Esc do
|
|
8794
|
+
* `ol-dropdown` lo (đóng + trả tiêu điểm về nút mở).
|
|
8795
|
+
*
|
|
8796
|
+
* `sticky`: menu dài có `menuClass="max-h-… overflow-y-auto"` thì ô tìm đứng
|
|
8797
|
+
* yên trên đỉnh khi cuộn danh sách. `-top-2 -mt-2 pt-2` phủ luôn đệm 8px của
|
|
8798
|
+
* menu, để mục cuộn qua không lộ ra phía trên ô tìm.
|
|
8799
|
+
*/
|
|
8800
|
+
class OlDropdownSearch {
|
|
8801
|
+
query = model('', /* @ts-ignore */
|
|
8802
|
+
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
8803
|
+
placeholder = input('Tìm kiếm', /* @ts-ignore */
|
|
8804
|
+
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8805
|
+
ariaLabel = input('', /* @ts-ignore */
|
|
8806
|
+
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
8807
|
+
/** Mở menu thì đưa tiêu điểm vào ô tìm — người mở menu có ô tìm là để gõ. */
|
|
8808
|
+
autofocus = input(true, /* @ts-ignore */
|
|
8809
|
+
...(ngDevMode ? [{ debugName: "autofocus" }] : /* istanbul ignore next */ []));
|
|
8810
|
+
/** Đóng menu thì xoá từ khoá, lần mở sau thấy lại đủ danh sách. */
|
|
8811
|
+
clearOnClose = input(true, /* @ts-ignore */
|
|
8812
|
+
...(ngDevMode ? [{ debugName: "clearOnClose" }] : /* istanbul ignore next */ []));
|
|
8813
|
+
host = inject(ElementRef);
|
|
8814
|
+
dropdown = inject(forwardRef(() => OlDropdown), { optional: true });
|
|
8815
|
+
constructor() {
|
|
8816
|
+
effect(() => {
|
|
8817
|
+
const open = this.dropdown?.isOpen();
|
|
8818
|
+
if (open === undefined)
|
|
8819
|
+
return;
|
|
8820
|
+
if (open && this.autofocus()) {
|
|
8821
|
+
// Menu vừa bỏ `hidden` trong cùng lượt vẽ — chờ một nhịp mới focus được.
|
|
8822
|
+
setTimeout(() => this.field()?.focus());
|
|
8823
|
+
}
|
|
8824
|
+
else if (!open && this.clearOnClose()) {
|
|
8825
|
+
this.query.set('');
|
|
8826
|
+
}
|
|
8827
|
+
});
|
|
8828
|
+
}
|
|
8829
|
+
onKeydown(event) {
|
|
8830
|
+
if (event.key !== 'ArrowDown' && event.key !== 'Enter')
|
|
8831
|
+
return;
|
|
8832
|
+
const first = this.firstItem();
|
|
8833
|
+
if (!first)
|
|
8834
|
+
return;
|
|
8835
|
+
event.preventDefault();
|
|
8836
|
+
if (event.key === 'ArrowDown')
|
|
8837
|
+
first.focus();
|
|
8838
|
+
else
|
|
8839
|
+
first.click();
|
|
8840
|
+
}
|
|
8841
|
+
field() {
|
|
8842
|
+
return this.host.nativeElement.querySelector('input');
|
|
8843
|
+
}
|
|
8844
|
+
/**
|
|
8845
|
+
* Mục đầu còn bấm được trong CÙNG menu — bỏ qua mục khoá. Tính cả
|
|
8846
|
+
* `menuitemcheckbox`/`menuitemradio` (menu chọn nhiều, chọn một).
|
|
8847
|
+
*/
|
|
8848
|
+
firstItem() {
|
|
8849
|
+
const menu = this.host.nativeElement.closest('[role="menu"]');
|
|
8850
|
+
return (menu?.querySelector(':is([role="menuitem"], [role="menuitemcheckbox"], [role="menuitemradio"]):not([aria-disabled="true"])') ?? null);
|
|
8851
|
+
}
|
|
8852
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDropdownSearch, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8853
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: OlDropdownSearch, isStandalone: true, selector: "ol-dropdown-search", inputs: { query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, autofocus: { classPropertyName: "autofocus", publicName: "autofocus", isSignal: true, isRequired: false, transformFunction: null }, clearOnClose: { classPropertyName: "clearOnClose", publicName: "clearOnClose", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { query: "queryChange" }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
8854
|
+
<div class="sticky -top-2 z-10 -mt-2 bg-neutral-primary pt-2" (keydown)="onKeydown($event)">
|
|
8855
|
+
<ol-input
|
|
8856
|
+
size="sm"
|
|
8857
|
+
[leftIcon]="true"
|
|
8858
|
+
[clearable]="true"
|
|
8859
|
+
[placeholder]="placeholder()"
|
|
8860
|
+
[ariaLabel]="ariaLabel() || placeholder()"
|
|
8861
|
+
[(value)]="query"
|
|
8862
|
+
>
|
|
8863
|
+
<ol-icon leftIcon name="magnifer" size="sm" />
|
|
8864
|
+
</ol-input>
|
|
8865
|
+
</div>
|
|
8866
|
+
`, isInline: true, dependencies: [{ kind: "component", type: OlInput, selector: "ol-input", inputs: ["value", "type", "label", "placeholder", "helperText", "disabled", "readonly", "size", "state", "required", "showAsterisk", "labelIcon", "labelTooltip", "ariaLabel", "prefixText", "suffixText", "addOnIcon", "leftIcon", "rightIcon", "clearable", "clearAriaLabel", "rightHelperText", "id", "floatingLabel", "labelPosition", "labelWidth", "floatingVariant"], outputs: ["valueChange", "disabledChange", "cleared"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8867
|
+
}
|
|
8868
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDropdownSearch, decorators: [{
|
|
8869
|
+
type: Component,
|
|
8870
|
+
args: [{
|
|
8871
|
+
selector: 'ol-dropdown-search',
|
|
8872
|
+
standalone: true,
|
|
8873
|
+
imports: [OlInput, OlIcon],
|
|
8874
|
+
template: `
|
|
8875
|
+
<div class="sticky -top-2 z-10 -mt-2 bg-neutral-primary pt-2" (keydown)="onKeydown($event)">
|
|
8876
|
+
<ol-input
|
|
8877
|
+
size="sm"
|
|
8878
|
+
[leftIcon]="true"
|
|
8879
|
+
[clearable]="true"
|
|
8880
|
+
[placeholder]="placeholder()"
|
|
8881
|
+
[ariaLabel]="ariaLabel() || placeholder()"
|
|
8882
|
+
[(value)]="query"
|
|
8883
|
+
>
|
|
8884
|
+
<ol-icon leftIcon name="magnifer" size="sm" />
|
|
8885
|
+
</ol-input>
|
|
8886
|
+
</div>
|
|
8887
|
+
`,
|
|
8888
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8889
|
+
host: { class: 'block' },
|
|
8890
|
+
}]
|
|
8891
|
+
}], ctorParameters: () => [], propDecorators: { query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }, { type: i0.Output, args: ["queryChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], autofocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autofocus", required: false }] }], clearOnClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearOnClose", required: false }] }] } });
|
|
8892
|
+
|
|
8893
|
+
/**
|
|
8894
|
+
* Trạng thái của menu khi KHÔNG có mục nào để hiện — Figma `ol-dropdown`
|
|
8895
|
+
* Type=Đang tải (5266:29107) · Rỗng (5266:29111) · Không có kết quả (5266:29114).
|
|
8896
|
+
*
|
|
8897
|
+
* Figma ghi: "Menu lấy dữ liệu từ máy chủ phải có đủ bốn trạng thái. Thiếu
|
|
8898
|
+
* Đ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 ý."
|
|
8899
|
+
*
|
|
8900
|
+
* Đệm 24/16px, căn giữa. Đang tải: ba thanh xương cao 10px (thanh cuối ngắn
|
|
8901
|
+
* ~70%), cách 10px. Hai trạng thái chữ: tiêu đề 14px đậm vừa `text-heading`,
|
|
8902
|
+
* dòng phụ 12px `text-body-subtle`, cách 8px.
|
|
8903
|
+
*
|
|
8904
|
+
* `role="status"`: trình đọc màn hình báo khi danh sách vừa lọc ra rỗng —
|
|
8905
|
+
* 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ả.
|
|
8906
|
+
*/
|
|
8907
|
+
class OlDropdownState {
|
|
8908
|
+
type = input('empty', /* @ts-ignore */
|
|
8909
|
+
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
8910
|
+
/** Tiêu đề — bỏ trống thì dùng câu mặc định theo `type`. */
|
|
8911
|
+
heading = input('', /* @ts-ignore */
|
|
8912
|
+
...(ngDevMode ? [{ debugName: "heading" }] : /* istanbul ignore next */ []));
|
|
8913
|
+
/** Dòng phụ — bỏ trống thì dùng câu mặc định theo `type`. */
|
|
8914
|
+
description = input('', /* @ts-ignore */
|
|
8915
|
+
...(ngDevMode ? [{ debugName: "description" }] : /* istanbul ignore next */ []));
|
|
8916
|
+
/** Từ khoá vừa tìm — chèn vào câu mặc định của `no-results`. */
|
|
8917
|
+
query = input('', /* @ts-ignore */
|
|
8918
|
+
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
8919
|
+
boxClasses = computed(() => this.type() === 'loading'
|
|
8920
|
+
? 'flex flex-col items-center gap-2.5 px-4 py-6'
|
|
8921
|
+
: 'flex flex-col items-center gap-2 px-4 py-6 text-center', /* @ts-ignore */
|
|
8922
|
+
...(ngDevMode ? [{ debugName: "boxClasses" }] : /* istanbul ignore next */ []));
|
|
8923
|
+
resolvedHeading = computed(() => {
|
|
8924
|
+
if (this.heading())
|
|
8925
|
+
return this.heading();
|
|
8926
|
+
switch (this.type()) {
|
|
8927
|
+
case 'loading':
|
|
8928
|
+
return 'Đang tải…';
|
|
8929
|
+
case 'empty':
|
|
8930
|
+
return 'Chưa có mục nào';
|
|
8931
|
+
case 'no-results':
|
|
8932
|
+
return this.query() ? `Không tìm thấy “${this.query()}”` : 'Không tìm thấy kết quả';
|
|
8933
|
+
}
|
|
8934
|
+
}, /* @ts-ignore */
|
|
8935
|
+
...(ngDevMode ? [{ debugName: "resolvedHeading" }] : /* istanbul ignore next */ []));
|
|
8936
|
+
resolvedDescription = computed(() => {
|
|
8937
|
+
if (this.description())
|
|
8938
|
+
return this.description();
|
|
8939
|
+
switch (this.type()) {
|
|
8940
|
+
case 'loading':
|
|
8941
|
+
return '';
|
|
8942
|
+
case 'empty':
|
|
8943
|
+
return 'Danh sách sẽ hiện khi có dữ liệu.';
|
|
8944
|
+
case 'no-results':
|
|
8945
|
+
return 'Thử từ khoá khác hoặc bỏ bớt bộ lọc.';
|
|
8946
|
+
}
|
|
8947
|
+
}, /* @ts-ignore */
|
|
8948
|
+
...(ngDevMode ? [{ debugName: "resolvedDescription" }] : /* istanbul ignore next */ []));
|
|
8949
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDropdownState, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8950
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlDropdownState, isStandalone: true, selector: "ol-dropdown-state", inputs: { type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, heading: { classPropertyName: "heading", publicName: "heading", isSignal: true, isRequired: false, transformFunction: null }, description: { classPropertyName: "description", publicName: "description", isSignal: true, isRequired: false, transformFunction: null }, query: { classPropertyName: "query", publicName: "query", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "block" }, ngImport: i0, template: `
|
|
8951
|
+
<div role="status" aria-live="polite" [class]="boxClasses()">
|
|
8952
|
+
@switch (type()) {
|
|
8953
|
+
@case ('loading') {
|
|
8954
|
+
<span class="sr-only">{{ resolvedHeading() }}</span>
|
|
8955
|
+
<span class="block h-2.5 w-full animate-pulse rounded-sm bg-neutral-quaternary"></span>
|
|
8956
|
+
<span class="block h-2.5 w-full animate-pulse rounded-sm bg-neutral-quaternary"></span>
|
|
8957
|
+
<span class="block h-2.5 w-2/3 animate-pulse rounded-sm bg-neutral-quaternary"></span>
|
|
8958
|
+
}
|
|
8959
|
+
@case ('empty') {
|
|
8960
|
+
<p class="text-sm font-medium text-heading">{{ resolvedHeading() }}</p>
|
|
8961
|
+
<p class="text-xs text-body-subtle">{{ resolvedDescription() }}</p>
|
|
8962
|
+
}
|
|
8963
|
+
@case ('no-results') {
|
|
8964
|
+
<p class="text-sm font-medium text-heading">{{ resolvedHeading() }}</p>
|
|
8965
|
+
<p class="text-xs text-body-subtle">{{ resolvedDescription() }}</p>
|
|
8966
|
+
}
|
|
8967
|
+
}
|
|
8968
|
+
</div>
|
|
8969
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8970
|
+
}
|
|
8971
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlDropdownState, decorators: [{
|
|
8972
|
+
type: Component,
|
|
8973
|
+
args: [{
|
|
8974
|
+
selector: 'ol-dropdown-state',
|
|
8975
|
+
standalone: true,
|
|
8976
|
+
template: `
|
|
8977
|
+
<div role="status" aria-live="polite" [class]="boxClasses()">
|
|
8978
|
+
@switch (type()) {
|
|
8979
|
+
@case ('loading') {
|
|
8980
|
+
<span class="sr-only">{{ resolvedHeading() }}</span>
|
|
8981
|
+
<span class="block h-2.5 w-full animate-pulse rounded-sm bg-neutral-quaternary"></span>
|
|
8982
|
+
<span class="block h-2.5 w-full animate-pulse rounded-sm bg-neutral-quaternary"></span>
|
|
8983
|
+
<span class="block h-2.5 w-2/3 animate-pulse rounded-sm bg-neutral-quaternary"></span>
|
|
8984
|
+
}
|
|
8985
|
+
@case ('empty') {
|
|
8986
|
+
<p class="text-sm font-medium text-heading">{{ resolvedHeading() }}</p>
|
|
8987
|
+
<p class="text-xs text-body-subtle">{{ resolvedDescription() }}</p>
|
|
8988
|
+
}
|
|
8989
|
+
@case ('no-results') {
|
|
8990
|
+
<p class="text-sm font-medium text-heading">{{ resolvedHeading() }}</p>
|
|
8991
|
+
<p class="text-xs text-body-subtle">{{ resolvedDescription() }}</p>
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8994
|
+
</div>
|
|
8995
|
+
`,
|
|
8996
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8997
|
+
host: { class: 'block' },
|
|
8998
|
+
}]
|
|
8999
|
+
}], propDecorators: { type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], heading: [{ type: i0.Input, args: [{ isSignal: true, alias: "heading", required: false }] }], description: [{ type: i0.Input, args: [{ isSignal: true, alias: "description", required: false }] }], query: [{ type: i0.Input, args: [{ isSignal: true, alias: "query", required: false }] }] } });
|
|
9000
|
+
|
|
9001
|
+
class OlFooter {
|
|
9002
|
+
/**
|
|
9003
|
+
* 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
|
|
9004
|
+
* trang trải hết bề ngang — dùng khi trang đã có khung bao ngoài.
|
|
9005
|
+
*/
|
|
9006
|
+
container = input(true, /* @ts-ignore */
|
|
9007
|
+
...(ngDevMode ? [{ debugName: "container" }] : /* istanbul ignore next */ []));
|
|
9008
|
+
innerClasses = computed(() => 'w-full p-4 md:flex md:items-center md:justify-between' +
|
|
9009
|
+
(this.container() ? ' mx-auto max-w-screen-xl' : ''), /* @ts-ignore */
|
|
9010
|
+
...(ngDevMode ? [{ debugName: "innerClasses" }] : /* istanbul ignore next */ []));
|
|
9011
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFooter, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9012
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.1.6", type: OlFooter, isStandalone: true, selector: "ol-footer", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<footer class=\"bg-neutral-primary rounded-lg shadow-sm m-4\">\n <!--\n `container` quy\u1EBFt \u0111\u1ECBnh c\u00F3 gi\u1EDBi h\u1EA1n b\u1EC1 ngang hay kh\u00F4ng. B\u1EA3n tr\u01B0\u1EDBc khai input\n n\u00E0y nh\u01B0ng template kh\u00F4ng d\u00F9ng \u2014 khung `max-w-screen-xl mx-auto` b\u1EADt v\u00F4 \u0111i\u1EC1u\n ki\u1EC7n, n\u00EAn `container=false` kh\u00F4ng \u0111\u1ED5i g\u00EC, trong khi b\u1EA3ng API c\u1EE7a trang docs\n v\u1EABn c\u00F4ng b\u1ED1 n\u00F3. Angular kh\u00F4ng ki\u1EC3m input c\u00F3 \u0111\u01B0\u1EE3c \u0111\u1ECDc hay kh\u00F4ng n\u00EAn build v\u1EABn\n xanh.\n -->\n <div [class]=\"innerClasses()\">\n <ng-content />\n </div>\n</footer>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9013
|
+
}
|
|
9014
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFooter, decorators: [{
|
|
9015
|
+
type: Component,
|
|
9016
|
+
args: [{ selector: 'ol-footer', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<footer class=\"bg-neutral-primary rounded-lg shadow-sm m-4\">\n <!--\n `container` quy\u1EBFt \u0111\u1ECBnh c\u00F3 gi\u1EDBi h\u1EA1n b\u1EC1 ngang hay kh\u00F4ng. B\u1EA3n tr\u01B0\u1EDBc khai input\n n\u00E0y nh\u01B0ng template kh\u00F4ng d\u00F9ng \u2014 khung `max-w-screen-xl mx-auto` b\u1EADt v\u00F4 \u0111i\u1EC1u\n ki\u1EC7n, n\u00EAn `container=false` kh\u00F4ng \u0111\u1ED5i g\u00EC, trong khi b\u1EA3ng API c\u1EE7a trang docs\n v\u1EABn c\u00F4ng b\u1ED1 n\u00F3. Angular kh\u00F4ng ki\u1EC3m input c\u00F3 \u0111\u01B0\u1EE3c \u0111\u1ECDc hay kh\u00F4ng n\u00EAn build v\u1EABn\n xanh.\n -->\n <div [class]=\"innerClasses()\">\n <ng-content />\n </div>\n</footer>\n" }]
|
|
9017
|
+
}], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
|
|
9018
|
+
|
|
9019
|
+
/**
|
|
9020
|
+
* Khung bọc nhiều mảng nội dung, khoảng cách đều nhau.
|
|
9021
|
+
*
|
|
9022
|
+
* Chưa có node Figma — dựng theo design system.
|
|
9023
|
+
*
|
|
9024
|
+
* ## Vì sao KHÔNG dùng `ol-card` cho việc này
|
|
9025
|
+
*
|
|
9026
|
+
* `ol-card` là MỘT thẻ nội dung: có ảnh bìa, có `href`, có trạng thái chọn.
|
|
9027
|
+
* 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
|
|
9028
|
+
* 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
|
|
9029
|
+
* `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
|
|
9030
|
+
* một khoảng cách khác và trang trông lộn xộn.
|
|
9031
|
+
*
|
|
9032
|
+
* ## `stacked` bỏ viền giữa, không phải bỏ viền hết
|
|
9033
|
+
*
|
|
9034
|
+
* 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à
|
|
9035
|
+
* kẻ ngăn mảnh ở giữa — giống `ol-list-group`. Tách rời thì mỗi mảng là một
|
|
9036
|
+
* thẻ riêng có viền đủ bốn cạnh.
|
|
9037
|
+
*
|
|
9038
|
+
* Làm bằng `divide-y` cộng `overflow-hidden` trên khung, KHÔNG bằng
|
|
9039
|
+
* `[&>*:not(:last-child)]:border-b`: biến thể `:last-child` trong chuỗi class
|
|
9040
|
+
* của component con sẽ khớp MỌI thể hiện của con đó, kể cả khi nó là con duy
|
|
9041
|
+
* nhất ở nơi khác — lỗi đã gặp trong repo này.
|
|
9042
|
+
*
|
|
7979
9043
|
* ## `ghost` bỏ nền và viền, giữ khoảng cách
|
|
7980
9044
|
*
|
|
7981
9045
|
* Dùng khi khung nằm trong một thẻ đã có viền rồi — hai lớp viền lồng nhau
|
|
@@ -8414,289 +9478,98 @@ class OlCheckboxRadioForm {
|
|
|
8414
9478
|
writeValue(val) {
|
|
8415
9479
|
this.checked.set(val ?? false);
|
|
8416
9480
|
}
|
|
8417
|
-
registerOnChange(fn) {
|
|
8418
|
-
this.onChange = fn;
|
|
8419
|
-
}
|
|
8420
|
-
registerOnTouched(fn) {
|
|
8421
|
-
this.onTouched = fn;
|
|
8422
|
-
}
|
|
8423
|
-
setDisabledState(isDisabled) {
|
|
8424
|
-
this.disabled.set(isDisabled);
|
|
8425
|
-
}
|
|
8426
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8427
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlCheckboxRadioForm, isStandalone: true, selector: "ol-checkbox-radio-form", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, controlPosition: { classPropertyName: "controlPosition", publicName: "controlPosition", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, helper: { classPropertyName: "helper", publicName: "helper", isSignal: true, isRequired: false, transformFunction: null }, number: { classPropertyName: "number", publicName: "number", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconBoxed: { classPropertyName: "iconBoxed", publicName: "iconBoxed", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, inputIdInput: { classPropertyName: "inputIdInput", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", disabled: "disabledChange", changed: "changed" }, providers: [
|
|
8428
|
-
{
|
|
8429
|
-
provide: NG_VALUE_ACCESSOR,
|
|
8430
|
-
useExisting: forwardRef(() => OlCheckboxRadioForm),
|
|
8431
|
-
multi: true,
|
|
8432
|
-
},
|
|
8433
|
-
], ngImport: i0, template: "<!--\n G\u1ED1c l\u00E0 `div` ch\u1EE9 kh\u00F4ng ph\u1EA3i `label`: `ol-label` b\u00EAn trong \u0111\u00E3 render m\u1ED9t th\u1EBB\n `<label>`, l\u1ED3ng hai label l\u00E0 HTML h\u1ECFng. \u00D4 nh\u1EADp th\u1EADt v\u1EABn c\u00F3, ch\u1EC9 \u1EA9n \u0111i, n\u00EAn\n bi\u1EC3u m\u1EABu v\u00E0 b\u1ED9 \u0111\u1ECDc m\u00E0n h\u00ECnh v\u1EABn th\u1EA5y \u0111\u1EE7.\n-->\n<div [class]=\"cardClasses()\" (click)=\"toggle()\">\n <input\n class=\"peer sr-only\"\n [type]=\"control() === 'radio' ? 'radio' : 'checkbox'\"\n [attr.id]=\"inputId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.name]=\"name() || null\"\n [attr.value]=\"value() || null\"\n [checked]=\"checked()\"\n [disabled]=\"disabled()\"\n (change)=\"toggle()\"\n (click)=\"$event.stopPropagation()\"\n />\n\n @if (control() !== 'none' && controlPosition() === 'start') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n\n <div [class]=\"bodyClasses()\">\n @if (icon()) {\n @if (iconBoxed()) {\n <ol-icon-shape [icon]=\"icon()\" size=\"base\" type=\"square\" />\n } @else {\n <ol-icon [name]=\"icon()\" size=\"lg\" class=\"shrink-0 text-fg-brand\" />\n }\n }\n\n <ng-content select=\"[lead]\" />\n\n @if (label() || helper() || number() !== null) {\n <div class=\"flex min-w-0 flex-col gap-1\">\n <ol-label [label]=\"label()\" [for]=\"inputId()\" [number]=\"number()\" />\n @if (helper()) {\n <ol-helper [text]=\"helper()\" size=\"base\" />\n }\n </div>\n }\n\n <ng-content />\n </div>\n\n @if (control() !== 'none' && controlPosition() === 'end') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n</div>\n\n<!-- D\u1EA5u t\u00EDch d\u00F9ng chung cho c\u1EA3 hai v\u1ECB tr\u00ED \u00F4. -->\n<ng-template #mark>\n @if (checked()) {\n @if (control() === 'radio') {\n <span class=\"size-2 rounded-full bg-white\"></span>\n } @else {\n <!-- N\u1EC1n \u00F4 l\u00E0 `bg-brand` \u0111\u1EB7c; kh\u00F4ng khai color th\u00EC icon k\u1EBF th\u1EEBa text-body v\u00E0 t\u00E0ng h\u00ECnh. -->\n <ol-icon name=\"check\" size=\"sm\" color=\"white\" />\n }\n }\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlIconShape, selector: "ol-icon-shape", inputs: ["icon", "size", "color", "type"] }, { kind: "component", type: OlLabel, selector: "ol-label", inputs: ["label", "for", "required", "showAsterisk", "labelIcon", "labelIconName", "labelTooltip", "hint", "number"] }, { kind: "component", type: OlHelper, selector: "ol-helper", inputs: ["type", "text", "state", "size", "helperId", "actionText", "actionIcon", "progress", "heading", "rules", "features"], outputs: ["action"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8434
|
-
}
|
|
8435
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioForm, decorators: [{
|
|
8436
|
-
type: Component,
|
|
8437
|
-
args: [{ selector: 'ol-checkbox-radio-form', standalone: true, imports: [NgTemplateOutlet, OlIcon, OlIconShape, OlLabel, OlHelper], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
8438
|
-
{
|
|
8439
|
-
provide: NG_VALUE_ACCESSOR,
|
|
8440
|
-
useExisting: forwardRef(() => OlCheckboxRadioForm),
|
|
8441
|
-
multi: true,
|
|
8442
|
-
},
|
|
8443
|
-
], template: "<!--\n G\u1ED1c l\u00E0 `div` ch\u1EE9 kh\u00F4ng ph\u1EA3i `label`: `ol-label` b\u00EAn trong \u0111\u00E3 render m\u1ED9t th\u1EBB\n `<label>`, l\u1ED3ng hai label l\u00E0 HTML h\u1ECFng. \u00D4 nh\u1EADp th\u1EADt v\u1EABn c\u00F3, ch\u1EC9 \u1EA9n \u0111i, n\u00EAn\n bi\u1EC3u m\u1EABu v\u00E0 b\u1ED9 \u0111\u1ECDc m\u00E0n h\u00ECnh v\u1EABn th\u1EA5y \u0111\u1EE7.\n-->\n<div [class]=\"cardClasses()\" (click)=\"toggle()\">\n <input\n class=\"peer sr-only\"\n [type]=\"control() === 'radio' ? 'radio' : 'checkbox'\"\n [attr.id]=\"inputId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.name]=\"name() || null\"\n [attr.value]=\"value() || null\"\n [checked]=\"checked()\"\n [disabled]=\"disabled()\"\n (change)=\"toggle()\"\n (click)=\"$event.stopPropagation()\"\n />\n\n @if (control() !== 'none' && controlPosition() === 'start') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n\n <div [class]=\"bodyClasses()\">\n @if (icon()) {\n @if (iconBoxed()) {\n <ol-icon-shape [icon]=\"icon()\" size=\"base\" type=\"square\" />\n } @else {\n <ol-icon [name]=\"icon()\" size=\"lg\" class=\"shrink-0 text-fg-brand\" />\n }\n }\n\n <ng-content select=\"[lead]\" />\n\n @if (label() || helper() || number() !== null) {\n <div class=\"flex min-w-0 flex-col gap-1\">\n <ol-label [label]=\"label()\" [for]=\"inputId()\" [number]=\"number()\" />\n @if (helper()) {\n <ol-helper [text]=\"helper()\" size=\"base\" />\n }\n </div>\n }\n\n <ng-content />\n </div>\n\n @if (control() !== 'none' && controlPosition() === 'end') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n</div>\n\n<!-- D\u1EA5u t\u00EDch d\u00F9ng chung cho c\u1EA3 hai v\u1ECB tr\u00ED \u00F4. -->\n<ng-template #mark>\n @if (checked()) {\n @if (control() === 'radio') {\n <span class=\"size-2 rounded-full bg-white\"></span>\n } @else {\n <!-- N\u1EC1n \u00F4 l\u00E0 `bg-brand` \u0111\u1EB7c; kh\u00F4ng khai color th\u00EC icon k\u1EBF th\u1EEBa text-body v\u00E0 t\u00E0ng h\u00ECnh. -->\n <ol-icon name=\"check\" size=\"sm\" color=\"white\" />\n }\n }\n</ng-template>\n" }]
|
|
8444
|
-
}], ctorParameters: () => [], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], controlPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlPosition", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], helper: [{ type: i0.Input, args: [{ isSignal: true, alias: "helper", required: false }] }], number: [{ type: i0.Input, args: [{ isSignal: true, alias: "number", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconBoxed: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconBoxed", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], inputIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
8445
|
-
|
|
8446
|
-
class OlCheckboxRadioGroup {
|
|
8447
|
-
/** Tiêu đề nhóm. Rỗng thì không render, nhưng nhóm vẫn giữ vai trò. */
|
|
8448
|
-
label = input('', /* @ts-ignore */
|
|
8449
|
-
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
8450
|
-
layout = input('stack', /* @ts-ignore */
|
|
8451
|
-
...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
8452
|
-
/** Số cột khi `layout='grid'`. Figma `Big cards grid` để 2 cột. */
|
|
8453
|
-
columns = input(2, /* @ts-ignore */
|
|
8454
|
-
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
8455
|
-
/** `radiogroup` khi các thẻ bên trong chỉ chọn được một. */
|
|
8456
|
-
role = input('group', /* @ts-ignore */
|
|
8457
|
-
...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
8458
|
-
/**
|
|
8459
|
-
* Tên class phải viết NGUYÊN VĂN trong mã. Tailwind v4 quét mã nguồn dạng
|
|
8460
|
-
* chữ để sinh CSS, nên `grid-cols-${n}` ghép lúc chạy sẽ không có luật nào
|
|
8461
|
-
* được sinh ra — class có trong DOM mà không có CSS, trông y như đúng.
|
|
8462
|
-
*/
|
|
8463
|
-
static COLUMN_CLASSES = {
|
|
8464
|
-
1: 'grid-cols-1',
|
|
8465
|
-
2: 'grid-cols-2',
|
|
8466
|
-
3: 'grid-cols-3',
|
|
8467
|
-
4: 'grid-cols-4',
|
|
8468
|
-
5: 'grid-cols-5',
|
|
8469
|
-
6: 'grid-cols-6',
|
|
8470
|
-
};
|
|
8471
|
-
listClasses = computed(() => {
|
|
8472
|
-
if (this.layout() === 'row')
|
|
8473
|
-
return 'flex flex-wrap gap-4';
|
|
8474
|
-
if (this.layout() === 'grid') {
|
|
8475
|
-
const cols = OlCheckboxRadioGroup.COLUMN_CLASSES[this.columns()] ?? 'grid-cols-2';
|
|
8476
|
-
return `grid gap-4 ${cols}`;
|
|
8477
|
-
}
|
|
8478
|
-
return 'flex flex-col gap-4';
|
|
8479
|
-
}, /* @ts-ignore */
|
|
8480
|
-
...(ngDevMode ? [{ debugName: "listClasses" }] : /* istanbul ignore next */ []));
|
|
8481
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8482
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlCheckboxRadioGroup, isStandalone: true, selector: "ol-checkbox-radio-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
8483
|
-
@if (label()) {
|
|
8484
|
-
<p class="mb-2 text-sm font-medium text-heading">{{ label() }}</p>
|
|
8485
|
-
}
|
|
8486
|
-
|
|
8487
|
-
<div [attr.role]="role()" [attr.aria-label]="label() || null" [class]="listClasses()">
|
|
8488
|
-
<ng-content />
|
|
8489
|
-
</div>
|
|
8490
|
-
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8491
|
-
}
|
|
8492
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioGroup, decorators: [{
|
|
8493
|
-
type: Component,
|
|
8494
|
-
args: [{
|
|
8495
|
-
selector: 'ol-checkbox-radio-group',
|
|
8496
|
-
standalone: true,
|
|
8497
|
-
template: `
|
|
8498
|
-
@if (label()) {
|
|
8499
|
-
<p class="mb-2 text-sm font-medium text-heading">{{ label() }}</p>
|
|
8500
|
-
}
|
|
8501
|
-
|
|
8502
|
-
<div [attr.role]="role()" [attr.aria-label]="label() || null" [class]="listClasses()">
|
|
8503
|
-
<ng-content />
|
|
8504
|
-
</div>
|
|
8505
|
-
`,
|
|
8506
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
8507
|
-
}]
|
|
8508
|
-
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }] } });
|
|
8509
|
-
|
|
8510
|
-
let nextInputId = 0;
|
|
8511
|
-
class OlInput {
|
|
8512
|
-
/** Id tự sinh, dùng khi nơi gọi không truyền id — để <label for> và
|
|
8513
|
-
* aria-describedby luôn nối được với ô nhập. */
|
|
8514
|
-
uid = `ol-input-${nextInputId++}`;
|
|
8515
|
-
value = model('', /* @ts-ignore */
|
|
8516
|
-
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
8517
|
-
type = input('text', /* @ts-ignore */
|
|
8518
|
-
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
8519
|
-
label = input('', /* @ts-ignore */
|
|
8520
|
-
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
8521
|
-
placeholder = input('', /* @ts-ignore */
|
|
8522
|
-
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
8523
|
-
helperText = input('', /* @ts-ignore */
|
|
8524
|
-
...(ngDevMode ? [{ debugName: "helperText" }] : /* istanbul ignore next */ []));
|
|
8525
|
-
/**
|
|
8526
|
-
* `model()` chứ không `input()`: Reactive Forms gọi `setDisabledState()` khi
|
|
8527
|
-
* form khoá/mở control, mà `input()` chỉ đọc nên không ghi được. Cách dùng
|
|
8528
|
-
* từ ngoài KHÔNG đổi — `[disabled]="true"` và `disabled()` vẫn như cũ.
|
|
8529
|
-
*/
|
|
8530
|
-
disabled = model(false, /* @ts-ignore */
|
|
8531
|
-
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
8532
|
-
readonly = input(false, /* @ts-ignore */
|
|
8533
|
-
...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
8534
|
-
size = input('base', /* @ts-ignore */
|
|
8535
|
-
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
8536
|
-
state = input('default', /* @ts-ignore */
|
|
8537
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
8538
|
-
required = input(false, /* @ts-ignore */
|
|
8539
|
-
...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
|
|
8540
|
-
showAsterisk = input(false, /* @ts-ignore */
|
|
8541
|
-
...(ngDevMode ? [{ debugName: "showAsterisk" }] : /* istanbul ignore next */ []));
|
|
8542
|
-
labelIcon = input(false, /* @ts-ignore */
|
|
8543
|
-
...(ngDevMode ? [{ debugName: "labelIcon" }] : /* istanbul ignore next */ []));
|
|
8544
|
-
labelTooltip = input('', /* @ts-ignore */
|
|
8545
|
-
...(ngDevMode ? [{ debugName: "labelTooltip" }] : /* istanbul ignore next */ []));
|
|
8546
|
-
/**
|
|
8547
|
-
* Tên trợ năng khi ô nhập KHÔNG có nhãn nhìn thấy được.
|
|
8548
|
-
*
|
|
8549
|
-
* `<label for>` chỉ render khi `label()` có chữ (`@if (label())` ở đầu
|
|
8550
|
-
* template), nên bố cục đặt chữ BÊN NGOÀI component — bảng lọc, thanh công
|
|
8551
|
-
* cụ, trang cài đặt — để lại một `<input>` hoàn toàn không tên. Có `label()`
|
|
8552
|
-
* thì không cần truyền cái này; hai thứ cùng lúc thì `aria-label` thắng và
|
|
8553
|
-
* người dùng bàn phím nghe một đằng, nhìn một nẻo.
|
|
8554
|
-
*/
|
|
8555
|
-
ariaLabel = input('', /* @ts-ignore */
|
|
8556
|
-
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
8557
|
-
prefixText = input('', /* @ts-ignore */
|
|
8558
|
-
...(ngDevMode ? [{ debugName: "prefixText" }] : /* istanbul ignore next */ []));
|
|
8559
|
-
suffixText = input('', /* @ts-ignore */
|
|
8560
|
-
...(ngDevMode ? [{ debugName: "suffixText" }] : /* istanbul ignore next */ []));
|
|
8561
|
-
addOnIcon = input(false, /* @ts-ignore */
|
|
8562
|
-
...(ngDevMode ? [{ debugName: "addOnIcon" }] : /* istanbul ignore next */ []));
|
|
8563
|
-
leftIcon = input(false, /* @ts-ignore */
|
|
8564
|
-
...(ngDevMode ? [{ debugName: "leftIcon" }] : /* istanbul ignore next */ []));
|
|
8565
|
-
rightIcon = input(false, /* @ts-ignore */
|
|
8566
|
-
...(ngDevMode ? [{ debugName: "rightIcon" }] : /* istanbul ignore next */ []));
|
|
8567
|
-
/**
|
|
8568
|
-
* Hiện nút xoá khi ô có chữ.
|
|
8569
|
-
*
|
|
8570
|
-
* Mặc định **TẮT**, khác `ol-select` và `ol-datepicker` (mặc định bật).
|
|
8571
|
-
* Lý do: hai cái kia chọn từ danh sách nên xoá là thao tác thường gặp, còn
|
|
8572
|
-
* ô nhập chữ thì người dùng vẫn chọn-hết-rồi-gõ-đè như mọi ô nhập khác.
|
|
8573
|
-
* Bật sẵn cho MỌI `ol-input` đang chạy là thêm một nút vào giao diện của
|
|
8574
|
-
* người khác mà họ không yêu cầu.
|
|
8575
|
-
*/
|
|
8576
|
-
clearable = input(false, /* @ts-ignore */
|
|
8577
|
-
...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
|
|
8578
|
-
/** Tên trợ năng của nút xoá. Đổi được khi ô có nghĩa cụ thể. */
|
|
8579
|
-
clearAriaLabel = input('Xoá nội dung', /* @ts-ignore */
|
|
8580
|
-
...(ngDevMode ? [{ debugName: "clearAriaLabel" }] : /* istanbul ignore next */ []));
|
|
8581
|
-
/** Phát khi người dùng bấm nút xoá — nơi dùng biết để tải lại danh sách. */
|
|
8582
|
-
cleared = output();
|
|
8583
|
-
rightHelperText = input('', /* @ts-ignore */
|
|
8584
|
-
...(ngDevMode ? [{ debugName: "rightHelperText" }] : /* istanbul ignore next */ []));
|
|
8585
|
-
id = input('', /* @ts-ignore */
|
|
8586
|
-
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
8587
|
-
/** Id thực dùng: ưu tiên id do nơi gọi truyền, không thì dùng id tự sinh. */
|
|
8588
|
-
inputId = computed(() => this.id() || this.uid, /* @ts-ignore */
|
|
8589
|
-
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
8590
|
-
helperId = computed(() => `${this.inputId()}-helper`, /* @ts-ignore */
|
|
8591
|
-
...(ngDevMode ? [{ debugName: "helperId" }] : /* istanbul ignore next */ []));
|
|
8592
|
-
/** Nối ô nhập với dòng gợi ý/lỗi cho trình đọc màn hình. */
|
|
8593
|
-
describedBy = computed(() => (this.helperText() ? this.helperId() : null), /* @ts-ignore */
|
|
8594
|
-
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
8595
|
-
/** Báo trạng thái lỗi cho công nghệ trợ giúp. */
|
|
8596
|
-
ariaInvalid = computed(() => (this.state() === 'error' ? 'true' : null), /* @ts-ignore */
|
|
8597
|
-
...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
|
|
8598
|
-
/**
|
|
8599
|
-
* Padding ngang của ô nhập, theo Figma ol-input:
|
|
8600
|
-
* sm 10px · base 12px · lg 14px · xl 16px.
|
|
8601
|
-
*
|
|
8602
|
-
* Phải gom vào một chuỗi thay vì dùng [class.px-2.5] trên template —
|
|
8603
|
-
* Angular cắt phần sau dấu chấm nên [class.px-2.5] render ra "px-2",
|
|
8604
|
-
* lệch 2px so với thiết kế.
|
|
8605
|
-
*/
|
|
8606
|
-
paddingClasses = computed(() => {
|
|
8607
|
-
const hasLeadingIcon = this.leftIcon() && !this.addOnIcon();
|
|
8608
|
-
const hasTrailingIcon = this.rightIcon() || this.state() === 'error' || this.state() === 'success';
|
|
8609
|
-
const classes = [];
|
|
8610
|
-
if (!hasLeadingIcon) {
|
|
8611
|
-
switch (this.size()) {
|
|
8612
|
-
case 'sm':
|
|
8613
|
-
classes.push('px-2.5');
|
|
8614
|
-
break;
|
|
8615
|
-
case 'lg':
|
|
8616
|
-
classes.push('px-3.5');
|
|
8617
|
-
break;
|
|
8618
|
-
case 'xl':
|
|
8619
|
-
classes.push('px-4');
|
|
8620
|
-
break;
|
|
8621
|
-
default:
|
|
8622
|
-
classes.push('px-3');
|
|
8623
|
-
}
|
|
8624
|
-
}
|
|
8625
|
-
else {
|
|
8626
|
-
classes.push('ps-2.5');
|
|
8627
|
-
}
|
|
8628
|
-
if (hasTrailingIcon)
|
|
8629
|
-
classes.push('pe-2.5');
|
|
8630
|
-
return classes.join(' ');
|
|
8631
|
-
}, /* @ts-ignore */
|
|
8632
|
-
...(ngDevMode ? [{ debugName: "paddingClasses" }] : /* istanbul ignore next */ []));
|
|
8633
|
-
// ── ControlValueAccessor ─────────────────────────────────────────
|
|
8634
|
-
/**
|
|
8635
|
-
* Bốn phương thức này là thứ Angular Forms gọi tới. Thiếu chúng thì
|
|
8636
|
-
* `formControlName` / `[formControl]` KHÔNG gắn được — dùng component trong
|
|
8637
|
-
* Reactive Forms là lỗi runtime "No value accessor for form control".
|
|
8638
|
-
*/
|
|
8639
|
-
onChange = () => { };
|
|
8640
|
-
onTouched = () => { };
|
|
8641
|
-
writeValue(val) {
|
|
8642
|
-
this.value.set(val ?? '');
|
|
8643
|
-
}
|
|
8644
|
-
registerOnChange(fn) {
|
|
8645
|
-
this.onChange = fn;
|
|
8646
|
-
}
|
|
8647
|
-
registerOnTouched(fn) {
|
|
8648
|
-
this.onTouched = fn;
|
|
8649
|
-
}
|
|
8650
|
-
setDisabledState(isDisabled) {
|
|
8651
|
-
this.disabled.set(isDisabled);
|
|
8652
|
-
}
|
|
8653
|
-
/** Gọi từ template mỗi khi giá trị đổi, để form biết. */
|
|
8654
|
-
emitChange(val) {
|
|
8655
|
-
this.value.set(val);
|
|
8656
|
-
this.onChange(val);
|
|
8657
|
-
}
|
|
8658
|
-
/**
|
|
8659
|
-
* Xoá nội dung ô.
|
|
8660
|
-
*
|
|
8661
|
-
* `stopPropagation` vì nút nằm TRONG thẻ bọc: thiếu nó thì cú bấm lan ra
|
|
8662
|
-
* thẻ bọc, và ở component có hành vi bấm-để-mở (select, datepicker) sẽ mở
|
|
8663
|
-
* panel ngay sau khi xoá.
|
|
8664
|
-
*/
|
|
8665
|
-
clearValue(event) {
|
|
8666
|
-
event.stopPropagation();
|
|
8667
|
-
this.value.set('');
|
|
8668
|
-
this.onChange('');
|
|
8669
|
-
this.cleared.emit();
|
|
9481
|
+
registerOnChange(fn) {
|
|
9482
|
+
this.onChange = fn;
|
|
8670
9483
|
}
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
this.onTouched();
|
|
9484
|
+
registerOnTouched(fn) {
|
|
9485
|
+
this.onTouched = fn;
|
|
8674
9486
|
}
|
|
8675
|
-
|
|
8676
|
-
|
|
9487
|
+
setDisabledState(isDisabled) {
|
|
9488
|
+
this.disabled.set(isDisabled);
|
|
9489
|
+
}
|
|
9490
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9491
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlCheckboxRadioForm, isStandalone: true, selector: "ol-checkbox-radio-form", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, control: { classPropertyName: "control", publicName: "control", isSignal: true, isRequired: false, transformFunction: null }, controlPosition: { classPropertyName: "controlPosition", publicName: "controlPosition", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, shape: { classPropertyName: "shape", publicName: "shape", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, helper: { classPropertyName: "helper", publicName: "helper", isSignal: true, isRequired: false, transformFunction: null }, number: { classPropertyName: "number", publicName: "number", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconBoxed: { classPropertyName: "iconBoxed", publicName: "iconBoxed", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, inputIdInput: { classPropertyName: "inputIdInput", publicName: "inputId", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange", disabled: "disabledChange", changed: "changed" }, providers: [
|
|
8677
9492
|
{
|
|
8678
9493
|
provide: NG_VALUE_ACCESSOR,
|
|
8679
|
-
useExisting: forwardRef(() =>
|
|
9494
|
+
useExisting: forwardRef(() => OlCheckboxRadioForm),
|
|
8680
9495
|
multi: true,
|
|
8681
9496
|
},
|
|
8682
|
-
], ngImport: i0, template: "
|
|
9497
|
+
], ngImport: i0, template: "<!--\n G\u1ED1c l\u00E0 `div` ch\u1EE9 kh\u00F4ng ph\u1EA3i `label`: `ol-label` b\u00EAn trong \u0111\u00E3 render m\u1ED9t th\u1EBB\n `<label>`, l\u1ED3ng hai label l\u00E0 HTML h\u1ECFng. \u00D4 nh\u1EADp th\u1EADt v\u1EABn c\u00F3, ch\u1EC9 \u1EA9n \u0111i, n\u00EAn\n bi\u1EC3u m\u1EABu v\u00E0 b\u1ED9 \u0111\u1ECDc m\u00E0n h\u00ECnh v\u1EABn th\u1EA5y \u0111\u1EE7.\n-->\n<div [class]=\"cardClasses()\" (click)=\"toggle()\">\n <input\n class=\"peer sr-only\"\n [type]=\"control() === 'radio' ? 'radio' : 'checkbox'\"\n [attr.id]=\"inputId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.name]=\"name() || null\"\n [attr.value]=\"value() || null\"\n [checked]=\"checked()\"\n [disabled]=\"disabled()\"\n (change)=\"toggle()\"\n (click)=\"$event.stopPropagation()\"\n />\n\n @if (control() !== 'none' && controlPosition() === 'start') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n\n <div [class]=\"bodyClasses()\">\n @if (icon()) {\n @if (iconBoxed()) {\n <ol-icon-shape [icon]=\"icon()\" size=\"base\" type=\"square\" />\n } @else {\n <ol-icon [name]=\"icon()\" size=\"lg\" class=\"shrink-0 text-fg-brand\" />\n }\n }\n\n <ng-content select=\"[lead]\" />\n\n @if (label() || helper() || number() !== null) {\n <div class=\"flex min-w-0 flex-col gap-1\">\n <ol-label [label]=\"label()\" [for]=\"inputId()\" [number]=\"number()\" />\n @if (helper()) {\n <ol-helper [text]=\"helper()\" size=\"base\" />\n }\n </div>\n }\n\n <ng-content />\n </div>\n\n @if (control() !== 'none' && controlPosition() === 'end') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n</div>\n\n<!-- D\u1EA5u t\u00EDch d\u00F9ng chung cho c\u1EA3 hai v\u1ECB tr\u00ED \u00F4. -->\n<ng-template #mark>\n @if (checked()) {\n @if (control() === 'radio') {\n <span class=\"size-2 rounded-full bg-white\"></span>\n } @else {\n <!-- N\u1EC1n \u00F4 l\u00E0 `bg-brand` \u0111\u1EB7c; kh\u00F4ng khai color th\u00EC icon k\u1EBF th\u1EEBa text-body v\u00E0 t\u00E0ng h\u00ECnh. -->\n <ol-icon name=\"check\" size=\"sm\" color=\"white\" />\n }\n }\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlIconShape, selector: "ol-icon-shape", inputs: ["icon", "size", "color", "type"] }, { kind: "component", type: OlLabel, selector: "ol-label", inputs: ["label", "for", "required", "showAsterisk", "labelIcon", "labelIconName", "labelTooltip", "hint", "number"] }, { kind: "component", type: OlHelper, selector: "ol-helper", inputs: ["type", "text", "state", "size", "helperId", "actionText", "actionIcon", "progress", "heading", "rules", "features"], outputs: ["action"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8683
9498
|
}
|
|
8684
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type:
|
|
9499
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioForm, decorators: [{
|
|
8685
9500
|
type: Component,
|
|
8686
|
-
args: [{ selector: 'ol-
|
|
8687
|
-
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
8688
|
-
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
8689
|
-
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
8690
|
-
// thêm nút xoá. Sửa từng cái là vá lẻ, nên chốt cho cả họ.
|
|
8691
|
-
class: 'block w-full',
|
|
8692
|
-
}, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
9501
|
+
args: [{ selector: 'ol-checkbox-radio-form', standalone: true, imports: [NgTemplateOutlet, OlIcon, OlIconShape, OlLabel, OlHelper], changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
8693
9502
|
{
|
|
8694
9503
|
provide: NG_VALUE_ACCESSOR,
|
|
8695
|
-
useExisting: forwardRef(() =>
|
|
9504
|
+
useExisting: forwardRef(() => OlCheckboxRadioForm),
|
|
8696
9505
|
multi: true,
|
|
8697
9506
|
},
|
|
8698
|
-
], template: "
|
|
8699
|
-
}],
|
|
9507
|
+
], template: "<!--\n G\u1ED1c l\u00E0 `div` ch\u1EE9 kh\u00F4ng ph\u1EA3i `label`: `ol-label` b\u00EAn trong \u0111\u00E3 render m\u1ED9t th\u1EBB\n `<label>`, l\u1ED3ng hai label l\u00E0 HTML h\u1ECFng. \u00D4 nh\u1EADp th\u1EADt v\u1EABn c\u00F3, ch\u1EC9 \u1EA9n \u0111i, n\u00EAn\n bi\u1EC3u m\u1EABu v\u00E0 b\u1ED9 \u0111\u1ECDc m\u00E0n h\u00ECnh v\u1EABn th\u1EA5y \u0111\u1EE7.\n-->\n<div [class]=\"cardClasses()\" (click)=\"toggle()\">\n <input\n class=\"peer sr-only\"\n [type]=\"control() === 'radio' ? 'radio' : 'checkbox'\"\n [attr.id]=\"inputId()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.name]=\"name() || null\"\n [attr.value]=\"value() || null\"\n [checked]=\"checked()\"\n [disabled]=\"disabled()\"\n (change)=\"toggle()\"\n (click)=\"$event.stopPropagation()\"\n />\n\n @if (control() !== 'none' && controlPosition() === 'start') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n\n <div [class]=\"bodyClasses()\">\n @if (icon()) {\n @if (iconBoxed()) {\n <ol-icon-shape [icon]=\"icon()\" size=\"base\" type=\"square\" />\n } @else {\n <ol-icon [name]=\"icon()\" size=\"lg\" class=\"shrink-0 text-fg-brand\" />\n }\n }\n\n <ng-content select=\"[lead]\" />\n\n @if (label() || helper() || number() !== null) {\n <div class=\"flex min-w-0 flex-col gap-1\">\n <ol-label [label]=\"label()\" [for]=\"inputId()\" [number]=\"number()\" />\n @if (helper()) {\n <ol-helper [text]=\"helper()\" size=\"base\" />\n }\n </div>\n }\n\n <ng-content />\n </div>\n\n @if (control() !== 'none' && controlPosition() === 'end') {\n <span [class]=\"boxClasses()\" aria-hidden=\"true\">\n <ng-container [ngTemplateOutlet]=\"mark\" />\n </span>\n }\n</div>\n\n<!-- D\u1EA5u t\u00EDch d\u00F9ng chung cho c\u1EA3 hai v\u1ECB tr\u00ED \u00F4. -->\n<ng-template #mark>\n @if (checked()) {\n @if (control() === 'radio') {\n <span class=\"size-2 rounded-full bg-white\"></span>\n } @else {\n <!-- N\u1EC1n \u00F4 l\u00E0 `bg-brand` \u0111\u1EB7c; kh\u00F4ng khai color th\u00EC icon k\u1EBF th\u1EEBa text-body v\u00E0 t\u00E0ng h\u00ECnh. -->\n <ol-icon name=\"check\" size=\"sm\" color=\"white\" />\n }\n }\n</ng-template>\n" }]
|
|
9508
|
+
}], ctorParameters: () => [], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], control: [{ type: i0.Input, args: [{ isSignal: true, alias: "control", required: false }] }], controlPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "controlPosition", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], shape: [{ type: i0.Input, args: [{ isSignal: true, alias: "shape", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], helper: [{ type: i0.Input, args: [{ isSignal: true, alias: "helper", required: false }] }], number: [{ type: i0.Input, args: [{ isSignal: true, alias: "number", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], iconBoxed: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconBoxed", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], inputIdInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputId", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
9509
|
+
|
|
9510
|
+
class OlCheckboxRadioGroup {
|
|
9511
|
+
/** Tiêu đề nhóm. Rỗng thì không render, nhưng nhóm vẫn giữ vai trò. */
|
|
9512
|
+
label = input('', /* @ts-ignore */
|
|
9513
|
+
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
9514
|
+
layout = input('stack', /* @ts-ignore */
|
|
9515
|
+
...(ngDevMode ? [{ debugName: "layout" }] : /* istanbul ignore next */ []));
|
|
9516
|
+
/** Số cột khi `layout='grid'`. Figma `Big cards grid` để 2 cột. */
|
|
9517
|
+
columns = input(2, /* @ts-ignore */
|
|
9518
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
9519
|
+
/** `radiogroup` khi các thẻ bên trong chỉ chọn được một. */
|
|
9520
|
+
role = input('group', /* @ts-ignore */
|
|
9521
|
+
...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
9522
|
+
/**
|
|
9523
|
+
* Tên class phải viết NGUYÊN VĂN trong mã. Tailwind v4 quét mã nguồn dạng
|
|
9524
|
+
* chữ để sinh CSS, nên `grid-cols-${n}` ghép lúc chạy sẽ không có luật nào
|
|
9525
|
+
* được sinh ra — class có trong DOM mà không có CSS, trông y như đúng.
|
|
9526
|
+
*/
|
|
9527
|
+
static COLUMN_CLASSES = {
|
|
9528
|
+
1: 'grid-cols-1',
|
|
9529
|
+
2: 'grid-cols-2',
|
|
9530
|
+
3: 'grid-cols-3',
|
|
9531
|
+
4: 'grid-cols-4',
|
|
9532
|
+
5: 'grid-cols-5',
|
|
9533
|
+
6: 'grid-cols-6',
|
|
9534
|
+
};
|
|
9535
|
+
listClasses = computed(() => {
|
|
9536
|
+
if (this.layout() === 'row')
|
|
9537
|
+
return 'flex flex-wrap gap-4';
|
|
9538
|
+
if (this.layout() === 'grid') {
|
|
9539
|
+
const cols = OlCheckboxRadioGroup.COLUMN_CLASSES[this.columns()] ?? 'grid-cols-2';
|
|
9540
|
+
return `grid gap-4 ${cols}`;
|
|
9541
|
+
}
|
|
9542
|
+
return 'flex flex-col gap-4';
|
|
9543
|
+
}, /* @ts-ignore */
|
|
9544
|
+
...(ngDevMode ? [{ debugName: "listClasses" }] : /* istanbul ignore next */ []));
|
|
9545
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9546
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlCheckboxRadioGroup, isStandalone: true, selector: "ol-checkbox-radio-group", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, layout: { classPropertyName: "layout", publicName: "layout", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
9547
|
+
@if (label()) {
|
|
9548
|
+
<p class="mb-2 text-sm font-medium text-heading">{{ label() }}</p>
|
|
9549
|
+
}
|
|
9550
|
+
|
|
9551
|
+
<div [attr.role]="role()" [attr.aria-label]="label() || null" [class]="listClasses()">
|
|
9552
|
+
<ng-content />
|
|
9553
|
+
</div>
|
|
9554
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9555
|
+
}
|
|
9556
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlCheckboxRadioGroup, decorators: [{
|
|
9557
|
+
type: Component,
|
|
9558
|
+
args: [{
|
|
9559
|
+
selector: 'ol-checkbox-radio-group',
|
|
9560
|
+
standalone: true,
|
|
9561
|
+
template: `
|
|
9562
|
+
@if (label()) {
|
|
9563
|
+
<p class="mb-2 text-sm font-medium text-heading">{{ label() }}</p>
|
|
9564
|
+
}
|
|
9565
|
+
|
|
9566
|
+
<div [attr.role]="role()" [attr.aria-label]="label() || null" [class]="listClasses()">
|
|
9567
|
+
<ng-content />
|
|
9568
|
+
</div>
|
|
9569
|
+
`,
|
|
9570
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
9571
|
+
}]
|
|
9572
|
+
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }] } });
|
|
8700
9573
|
|
|
8701
9574
|
let nextTextareaId = 0;
|
|
8702
9575
|
class OlTextarea {
|
|
@@ -8749,6 +9622,32 @@ class OlTextarea {
|
|
|
8749
9622
|
...(ngDevMode ? [{ debugName: "commentBox" }] : /* istanbul ignore next */ []));
|
|
8750
9623
|
id = input('', /* @ts-ignore */
|
|
8751
9624
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
9625
|
+
/**
|
|
9626
|
+
* Nhãn nổi: `label` nằm TRONG khung ở dòng đầu, nổi lên khi focus hoặc có
|
|
9627
|
+
* chữ. Không áp cho `commentBox` (thẻ bình luận có bố cục riêng).
|
|
9628
|
+
*/
|
|
9629
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
9630
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
9631
|
+
/**
|
|
9632
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
9633
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
9634
|
+
*/
|
|
9635
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
9636
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
9637
|
+
/**
|
|
9638
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
9639
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
9640
|
+
* mép ô thẳng hàng.
|
|
9641
|
+
*/
|
|
9642
|
+
labelWidth = input('', /* @ts-ignore */
|
|
9643
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
9644
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel() && !this.commentBox()), /* @ts-ignore */
|
|
9645
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
9646
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
9647
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
9648
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
9649
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
9650
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
8752
9651
|
submitComment = output();
|
|
8753
9652
|
onSubmit() {
|
|
8754
9653
|
// `?.` chứ không phải `.`: bind `[value]="undefined"` đè mất giá trị mặc
|
|
@@ -8788,17 +9687,17 @@ class OlTextarea {
|
|
|
8788
9687
|
this.onTouched();
|
|
8789
9688
|
}
|
|
8790
9689
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlTextarea, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8791
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlTextarea, isStandalone: true, selector: "ol-textarea", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, showCharCount: { classPropertyName: "showCharCount", publicName: "showCharCount", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, commentBox: { classPropertyName: "commentBox", publicName: "commentBox", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", submitComment: "submitComment" }, host: { classAttribute: "block w-full" }, providers: [
|
|
9690
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlTextarea, isStandalone: true, selector: "ol-textarea", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, rows: { classPropertyName: "rows", publicName: "rows", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, showCharCount: { classPropertyName: "showCharCount", publicName: "showCharCount", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, commentBox: { classPropertyName: "commentBox", publicName: "commentBox", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", submitComment: "submitComment" }, host: { classAttribute: "block w-full" }, providers: [
|
|
8792
9691
|
{
|
|
8793
9692
|
provide: NG_VALUE_ACCESSOR,
|
|
8794
9693
|
useExisting: forwardRef(() => OlTextarea),
|
|
8795
9694
|
multi: true,
|
|
8796
9695
|
},
|
|
8797
|
-
], ngImport: i0, template: "<div class=\"w-full\">\n @if (label()) {\n <
|
|
9696
|
+
], ngImport: i0, template: "<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && (!floatingLabel() || commentBox())) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n @if (!commentBox()) {\n <!-- Standard Textarea Input -->\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"!!value()\"\n [floatStart]=\"14\"\n >\n <div class=\"relative w-full\">\n <textarea\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [rows]=\"rows()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder()\"\n [maxLength]=\"maxLength() ?? 5000\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"emitChange($event)\"\n (blur)=\"markTouched()\"\n class=\"ol-field-box block w-full p-3.5 text-sm rounded-base outline-none border font-normal shadow-xs\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled()\"\n [class.text-body]=\"state() === 'default' && !disabled()\"\n [class.focus:border-brand]=\"state() === 'default' && !disabled()\"\n [class.focus:ring-2]=\"!disabled()\"\n [class.focus:ring-brand-subtle]=\"state() === 'default' && !disabled()\"\n [class.dark:focus:ring-brand-950]=\"state() === 'default' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.focus:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.focus:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n ></textarea>\n </div>\n </ol-field-frame>\n } @else {\n <!--\n Figma Ready-to-use Comment / Reply Textarea Card (`Type=Chatroom`).\n\n `rounded-md` (12px) ch\u1EE9 kh\u00F4ng `rounded-lg` (16px): \u0111\u1ECDc Figma\n `ol-textarea` bi\u1EBFn th\u1EC3 `Type=Chatroom, State=Initial` ra **radius 12** \u2014\n c\u00F9ng b\u1EADc v\u1EDBi `ol-input` v\u00E0 m\u1ECDi \u00F4 nh\u1EADp kh\u00E1c trong h\u1ECD. \u0110o trang /forms\n 2026-09-17: `ol-textarea` l\u00E0 component DUY NH\u1EA4T trong h\u1ECD c\u00F3 hai gi\u00E1 tr\u1ECB\n bo g\u00F3c (12px v\u00E0 16px), l\u1EC7ch v\u1EDBi 9 component c\u00F2n l\u1EA1i.\n -->\n <div\n class=\"border border-default-medium rounded-md bg-neutral-primary overflow-hidden shadow-xs focus-within:ring-2 focus-within:ring-brand-subtle dark:focus-within:ring-brand-950 focus-within:border-brand\"\n >\n <textarea\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [rows]=\"rows()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() || 'Vi\u1EBFt b\u00ECnh lu\u1EADn ho\u1EB7c ph\u1EA3n h\u1ED3i...'\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"emitChange($event)\"\n (blur)=\"markTouched()\"\n class=\"block w-full p-3.5 text-sm text-body bg-transparent border-0 outline-none resize-none placeholder:text-body-subtle\"\n ></textarea>\n <!-- Bottom Action Bar -->\n <div\n class=\"flex items-center justify-between px-3 py-2 border-t border-default-medium bg-neutral-secondary-medium\"\n >\n <div class=\"flex items-center gap-1 text-body-subtle\">\n <button\n type=\"button\"\n class=\"p-1.5 hover:bg-neutral-secondary-medium rounded-lg text-body-subtle hover:text-body\"\n title=\"\u0110\u00EDnh k\u00E8m t\u1EC7p\"\n >\n <ol-icon name=\"link\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n class=\"p-1.5 hover:bg-neutral-secondary-medium rounded-lg text-body-subtle hover:text-body\"\n title=\"Ch\u00E8n v\u1ECB tr\u00ED/h\u00ECnh \u1EA3nh\"\n >\n <ol-icon name=\"photo\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n class=\"p-1.5 hover:bg-neutral-secondary-medium rounded-lg text-body-subtle hover:text-body\"\n title=\"Bi\u1EC3u t\u01B0\u1EE3ng c\u1EA3m x\u00FAc\"\n >\n <ol-icon name=\"mynaui:smile\" size=\"sm\" />\n </button>\n </div>\n <button\n type=\"button\"\n (click)=\"onSubmit()\"\n [disabled]=\"disabled() || !value().trim()\"\n class=\"inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium text-white bg-brand hover:bg-brand-strong disabled:opacity-50 disabled:cursor-not-allowed rounded-lg shadow-xs\"\n >\n <span>G\u1EEDi</span>\n <ol-icon name=\"arrow-right\" size=\"xs\" />\n </button>\n </div>\n </div>\n }\n @if (helperText() || showCharCount()) {\n <div class=\"flex items-center justify-between mt-1.5 text-xs\">\n @if (helperText()) {\n <p\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger-strong]=\"state() === 'error'\"\n [class.text-fg-success-strong]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n @if (showCharCount()) {\n <span class=\"text-body-subtle ms-auto\">\n {{ value().length }}{{ maxLength() ? '/' + maxLength() : '' }} k\u00FD t\u1EF1\n </span>\n }\n </div>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8798
9697
|
}
|
|
8799
9698
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlTextarea, decorators: [{
|
|
8800
9699
|
type: Component,
|
|
8801
|
-
args: [{ selector: 'ol-textarea', standalone: true, imports: [FormsModule, OlIcon], host: {
|
|
9700
|
+
args: [{ selector: 'ol-textarea', standalone: true, imports: [FormsModule, OlIcon, OlFieldFrame, OlFieldLabel], host: {
|
|
8802
9701
|
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
8803
9702
|
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
8804
9703
|
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
@@ -8810,8 +9709,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
8810
9709
|
useExisting: forwardRef(() => OlTextarea),
|
|
8811
9710
|
multi: true,
|
|
8812
9711
|
},
|
|
8813
|
-
], template: "<div class=\"w-full\">\n @if (label()) {\n <
|
|
8814
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], showCharCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCharCount", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], commentBox: [{ type: i0.Input, args: [{ isSignal: true, alias: "commentBox", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], submitComment: [{ type: i0.Output, args: ["submitComment"] }] } });
|
|
9712
|
+
], template: "<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && (!floatingLabel() || commentBox())) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n @if (!commentBox()) {\n <!-- Standard Textarea Input -->\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"!!value()\"\n [floatStart]=\"14\"\n >\n <div class=\"relative w-full\">\n <textarea\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [rows]=\"rows()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder()\"\n [maxLength]=\"maxLength() ?? 5000\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"emitChange($event)\"\n (blur)=\"markTouched()\"\n class=\"ol-field-box block w-full p-3.5 text-sm rounded-base outline-none border font-normal shadow-xs\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled()\"\n [class.text-body]=\"state() === 'default' && !disabled()\"\n [class.focus:border-brand]=\"state() === 'default' && !disabled()\"\n [class.focus:ring-2]=\"!disabled()\"\n [class.focus:ring-brand-subtle]=\"state() === 'default' && !disabled()\"\n [class.dark:focus:ring-brand-950]=\"state() === 'default' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.focus:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.focus:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n ></textarea>\n </div>\n </ol-field-frame>\n } @else {\n <!--\n Figma Ready-to-use Comment / Reply Textarea Card (`Type=Chatroom`).\n\n `rounded-md` (12px) ch\u1EE9 kh\u00F4ng `rounded-lg` (16px): \u0111\u1ECDc Figma\n `ol-textarea` bi\u1EBFn th\u1EC3 `Type=Chatroom, State=Initial` ra **radius 12** \u2014\n c\u00F9ng b\u1EADc v\u1EDBi `ol-input` v\u00E0 m\u1ECDi \u00F4 nh\u1EADp kh\u00E1c trong h\u1ECD. \u0110o trang /forms\n 2026-09-17: `ol-textarea` l\u00E0 component DUY NH\u1EA4T trong h\u1ECD c\u00F3 hai gi\u00E1 tr\u1ECB\n bo g\u00F3c (12px v\u00E0 16px), l\u1EC7ch v\u1EDBi 9 component c\u00F2n l\u1EA1i.\n -->\n <div\n class=\"border border-default-medium rounded-md bg-neutral-primary overflow-hidden shadow-xs focus-within:ring-2 focus-within:ring-brand-subtle dark:focus-within:ring-brand-950 focus-within:border-brand\"\n >\n <textarea\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-describedby]=\"describedBy()\"\n [rows]=\"rows()\"\n [disabled]=\"disabled()\"\n [placeholder]=\"placeholder() || 'Vi\u1EBFt b\u00ECnh lu\u1EADn ho\u1EB7c ph\u1EA3n h\u1ED3i...'\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"emitChange($event)\"\n (blur)=\"markTouched()\"\n class=\"block w-full p-3.5 text-sm text-body bg-transparent border-0 outline-none resize-none placeholder:text-body-subtle\"\n ></textarea>\n <!-- Bottom Action Bar -->\n <div\n class=\"flex items-center justify-between px-3 py-2 border-t border-default-medium bg-neutral-secondary-medium\"\n >\n <div class=\"flex items-center gap-1 text-body-subtle\">\n <button\n type=\"button\"\n class=\"p-1.5 hover:bg-neutral-secondary-medium rounded-lg text-body-subtle hover:text-body\"\n title=\"\u0110\u00EDnh k\u00E8m t\u1EC7p\"\n >\n <ol-icon name=\"link\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n class=\"p-1.5 hover:bg-neutral-secondary-medium rounded-lg text-body-subtle hover:text-body\"\n title=\"Ch\u00E8n v\u1ECB tr\u00ED/h\u00ECnh \u1EA3nh\"\n >\n <ol-icon name=\"photo\" size=\"sm\" />\n </button>\n <button\n type=\"button\"\n class=\"p-1.5 hover:bg-neutral-secondary-medium rounded-lg text-body-subtle hover:text-body\"\n title=\"Bi\u1EC3u t\u01B0\u1EE3ng c\u1EA3m x\u00FAc\"\n >\n <ol-icon name=\"mynaui:smile\" size=\"sm\" />\n </button>\n </div>\n <button\n type=\"button\"\n (click)=\"onSubmit()\"\n [disabled]=\"disabled() || !value().trim()\"\n class=\"inline-flex items-center gap-1.5 px-3.5 py-1.5 text-xs font-medium text-white bg-brand hover:bg-brand-strong disabled:opacity-50 disabled:cursor-not-allowed rounded-lg shadow-xs\"\n >\n <span>G\u1EEDi</span>\n <ol-icon name=\"arrow-right\" size=\"xs\" />\n </button>\n </div>\n </div>\n }\n @if (helperText() || showCharCount()) {\n <div class=\"flex items-center justify-between mt-1.5 text-xs\">\n @if (helperText()) {\n <p\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger-strong]=\"state() === 'error'\"\n [class.text-fg-success-strong]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n @if (showCharCount()) {\n <span class=\"text-body-subtle ms-auto\">\n {{ value().length }}{{ maxLength() ? '/' + maxLength() : '' }} k\u00FD t\u1EF1\n </span>\n }\n </div>\n }\n</div>\n" }]
|
|
9713
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], rows: [{ type: i0.Input, args: [{ isSignal: true, alias: "rows", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], showCharCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "showCharCount", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], commentBox: [{ type: i0.Input, args: [{ isSignal: true, alias: "commentBox", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], submitComment: [{ type: i0.Output, args: ["submitComment"] }] } });
|
|
8815
9714
|
|
|
8816
9715
|
class OlSpinner {
|
|
8817
9716
|
/**
|
|
@@ -9159,6 +10058,32 @@ class OlSelect {
|
|
|
9159
10058
|
disabled = model(false, /* @ts-ignore */
|
|
9160
10059
|
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
9161
10060
|
// ── API mới ──────────────────────────────────────────────────────
|
|
10061
|
+
/**
|
|
10062
|
+
* Nhãn nổi: `label` nằm TRONG khung, nổi lên khi focus, mở danh sách hoặc có
|
|
10063
|
+
* lựa chọn. Đa chọn: nhãn nằm yên ở dòng đầu, nổi lên không đẩy thẻ.
|
|
10064
|
+
*/
|
|
10065
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
10066
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
10067
|
+
/**
|
|
10068
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
10069
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
10070
|
+
*/
|
|
10071
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
10072
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
10073
|
+
/**
|
|
10074
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
10075
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
10076
|
+
* mép ô thẳng hàng.
|
|
10077
|
+
*/
|
|
10078
|
+
labelWidth = input('', /* @ts-ignore */
|
|
10079
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
10080
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
10081
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
10082
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
10083
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
10084
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
10085
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
10086
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
9162
10087
|
placeholder = input('Chọn...', /* @ts-ignore */
|
|
9163
10088
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
9164
10089
|
multiple = input(false, /* @ts-ignore */
|
|
@@ -9177,7 +10102,11 @@ class OlSelect {
|
|
|
9177
10102
|
...(ngDevMode ? [{ debugName: "valueKey" }] : /* istanbul ignore next */ []));
|
|
9178
10103
|
groupBy = input('', /* @ts-ignore */
|
|
9179
10104
|
...(ngDevMode ? [{ debugName: "groupBy" }] : /* istanbul ignore next */ []));
|
|
9180
|
-
|
|
10105
|
+
/**
|
|
10106
|
+
* Tiêu đề khi danh sách rỗng. Bỏ trống thì theo Figma: có từ khoá →
|
|
10107
|
+
* "Không tìm thấy “…”", chưa có mục nào → "Chưa có mục nào".
|
|
10108
|
+
*/
|
|
10109
|
+
emptyText = input('', /* @ts-ignore */
|
|
9181
10110
|
...(ngDevMode ? [{ debugName: "emptyText" }] : /* istanbul ignore next */ []));
|
|
9182
10111
|
searchPlaceholder = input('Tìm kiếm...', /* @ts-ignore */
|
|
9183
10112
|
...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
@@ -9347,6 +10276,8 @@ class OlSelect {
|
|
|
9347
10276
|
/** Ô tìm ở đầu panel, để chuyển focus vào khi mở. */
|
|
9348
10277
|
oTimPanel = viewChild('oTimPanel', /* @ts-ignore */
|
|
9349
10278
|
...(ngDevMode ? [{ debugName: "oTimPanel" }] : /* istanbul ignore next */ []));
|
|
10279
|
+
inlineSearch = viewChild('inlineSearch', /* @ts-ignore */
|
|
10280
|
+
...(ngDevMode ? [{ debugName: "inlineSearch" }] : /* istanbul ignore next */ []));
|
|
9350
10281
|
// ── Trạng thái ───────────────────────────────────────────────────
|
|
9351
10282
|
isOpen = signal(false, /* @ts-ignore */
|
|
9352
10283
|
...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
@@ -9413,10 +10344,24 @@ class OlSelect {
|
|
|
9413
10344
|
* 10/12/14/16px. Đặt `ol-select size="sm"` cạnh `ol-input size="sm"` là
|
|
9414
10345
|
* lệch 4px, và ở `xl` lệch 2px theo chiều ngược lại.
|
|
9415
10346
|
*
|
|
9416
|
-
*
|
|
10347
|
+
* Icon trái và cụm nút phải đều `absolute`, nên khung tự chừa chỗ cho
|
|
10348
|
+
* chúng bằng `FIELD_ICON_RESERVE_*`: icon ↔ chữ đúng 8px. Bản cũ `ps-10` /
|
|
10349
|
+
* `pe-10` cố định cho mọi cỡ ra 10px ở cỡ base, 14px ở sm (đo 2026-09-29).
|
|
9417
10350
|
*/
|
|
9418
|
-
paddingStartClass = computed(() =>
|
|
10351
|
+
paddingStartClass = computed(() => {
|
|
10352
|
+
const size = this.resolvedSize();
|
|
10353
|
+
const start = this.leftIcon() ? FIELD_ICON_RESERVE_START[size] : FIELD_PADDING_START[size];
|
|
10354
|
+
const padY = this.multiple() ? ` ${multiValuePadY(this.floatingOutlined())}` : '';
|
|
10355
|
+
return `${start} ${FIELD_ICON_RESERVE_END[size]}${padY}`;
|
|
10356
|
+
}, /* @ts-ignore */
|
|
9419
10357
|
...(ngDevMode ? [{ debugName: "paddingStartClass" }] : /* istanbul ignore next */ []));
|
|
10358
|
+
floatingOutlined = computed(() => this.floatingLabel() && this.floatingVariant() === 'outlined', /* @ts-ignore */
|
|
10359
|
+
...(ngDevMode ? [{ debugName: "floatingOutlined" }] : /* istanbul ignore next */ []));
|
|
10360
|
+
/** Icon trái và cụm nút phải nằm ở lề theo cỡ, như `ol-input`. */
|
|
10361
|
+
leftIconPadClass = computed(() => FIELD_PADDING_START[this.resolvedSize()], /* @ts-ignore */
|
|
10362
|
+
...(ngDevMode ? [{ debugName: "leftIconPadClass" }] : /* istanbul ignore next */ []));
|
|
10363
|
+
controlPadClass = computed(() => FIELD_PADDING_END[this.resolvedSize()], /* @ts-ignore */
|
|
10364
|
+
...(ngDevMode ? [{ debugName: "controlPadClass" }] : /* istanbul ignore next */ []));
|
|
9420
10365
|
onChange = () => { };
|
|
9421
10366
|
onTouched = () => { };
|
|
9422
10367
|
destroyRef = inject(DestroyRef);
|
|
@@ -9722,12 +10667,14 @@ class OlSelect {
|
|
|
9722
10667
|
if (this.remoteSearch()) {
|
|
9723
10668
|
return this.waitingForTerm() ? [] : list;
|
|
9724
10669
|
}
|
|
9725
|
-
|
|
10670
|
+
// Bỏ dấu cả hai phía: gõ "ha" ra "Hà", "dang" ra "Đặng" — người dùng
|
|
10671
|
+
// hay gõ không dấu khi tìm nhanh. Bản cũ chỉ `toLowerCase()`.
|
|
10672
|
+
const q = stripDiacritics((this.searchQuery() ?? '').trim());
|
|
9726
10673
|
if (!q)
|
|
9727
10674
|
return list;
|
|
9728
10675
|
return list.filter((o) => {
|
|
9729
|
-
const label = this.getItemLabel(o)
|
|
9730
|
-
const subLabel = `${o?.['subLabel'] ?? ''}
|
|
10676
|
+
const label = stripDiacritics(this.getItemLabel(o));
|
|
10677
|
+
const subLabel = stripDiacritics(`${o?.['subLabel'] ?? ''}`);
|
|
9731
10678
|
return label.includes(q) || subLabel.includes(q);
|
|
9732
10679
|
});
|
|
9733
10680
|
}, /* @ts-ignore */
|
|
@@ -9863,14 +10810,19 @@ class OlSelect {
|
|
|
9863
10810
|
//
|
|
9864
10811
|
// Đa chọn thì nhảy tới mục chọn ĐẦU TIÊN; chưa chọn gì thì về mục đầu.
|
|
9865
10812
|
this.activeIndex.set(this.selectedOptionIndex());
|
|
9866
|
-
//
|
|
9867
|
-
//
|
|
9868
|
-
//
|
|
10813
|
+
// Đưa focus vào ô tìm ở CẢ HAI lối, không thì người dùng gõ mà chữ không
|
|
10814
|
+
// vào đâu cả. Chú thích cũ ghi lối `'inline'` "không cần vì ô nhập thay
|
|
10815
|
+
// chỗ nhãn ngay trên phần tử đang focus" — sai: ô nhập là phần tử RIÊNG
|
|
10816
|
+
// bên trong combobox, focus ở lại `div`. Đo 2026-09-29 trên /select: bấm
|
|
10817
|
+
// mở rồi gõ "van", danh sách vẫn đủ 12 mục. Phím ↑↓/Enter/Esc từ ô tìm
|
|
10818
|
+
// nổi bọt lên `(keydown)` của combobox nên vẫn chạy.
|
|
9869
10819
|
//
|
|
9870
10820
|
// `setTimeout` vì `@if (isOpen())` chưa dựng xong DOM ở thời điểm này —
|
|
9871
10821
|
// `viewChild` còn trả `undefined`.
|
|
9872
|
-
if (this.searchable()
|
|
9873
|
-
setTimeout(() => this.
|
|
10822
|
+
if (this.searchable()) {
|
|
10823
|
+
setTimeout(() => (this.searchPosition() === 'panel'
|
|
10824
|
+
? this.oTimPanel()
|
|
10825
|
+
: this.inlineSearch())?.nativeElement?.focus());
|
|
9874
10826
|
}
|
|
9875
10827
|
// Kéo mục đang chọn vào TẦM NHÌN. Đánh dấu thôi chưa đủ: danh sách dài
|
|
9876
10828
|
// thì mục đó nằm ngoài khung, người dùng vẫn thấy đầu danh sách.
|
|
@@ -10139,17 +11091,25 @@ class OlSelect {
|
|
|
10139
11091
|
this.disabled.set(isDisabled);
|
|
10140
11092
|
}
|
|
10141
11093
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
10142
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlSelect, isStandalone: true, selector: "ol-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, leftIcon: { classPropertyName: "leftIcon", publicName: "leftIcon", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, menuClass: { classPropertyName: "menuClass", publicName: "menuClass", isSignal: true, isRequired: false, transformFunction: null }, matchTriggerWidth: { classPropertyName: "matchTriggerWidth", publicName: "matchTriggerWidth", isSignal: true, isRequired: false, transformFunction: null }, maxSelectedItems: { classPropertyName: "maxSelectedItems", publicName: "maxSelectedItems", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, hideSelected: { classPropertyName: "hideSelected", publicName: "hideSelected", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, createText: { classPropertyName: "createText", publicName: "createText", isSignal: true, isRequired: false, transformFunction: null }, groupSeparator: { classPropertyName: "groupSeparator", publicName: "groupSeparator", isSignal: true, isRequired: false, transformFunction: null }, searchPosition: { classPropertyName: "searchPosition", publicName: "searchPosition", isSignal: true, isRequired: false, transformFunction: null }, searchIcon: { classPropertyName: "searchIcon", publicName: "searchIcon", isSignal: true, isRequired: false, transformFunction: null }, searchPrefix: { classPropertyName: "searchPrefix", publicName: "searchPrefix", isSignal: true, isRequired: false, transformFunction: null }, triggerIcon: { classPropertyName: "triggerIcon", publicName: "triggerIcon", isSignal: true, isRequired: false, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, remoteSearch: { classPropertyName: "remoteSearch", publicName: "remoteSearch", isSignal: true, isRequired: false, transformFunction: null }, minTermLength: { classPropertyName: "minTermLength", publicName: "minTermLength", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, typeToSearchText: { classPropertyName: "typeToSearchText", publicName: "typeToSearchText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, bufferAmount: { classPropertyName: "bufferAmount", publicName: "bufferAmount", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", search: "search", opened: "opened", closed: "closed", cleared: "cleared", added: "added", removed: "removed", selectionChange: "selectionChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block w-full" }, providers: [
|
|
11094
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlSelect, isStandalone: true, selector: "ol-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, leftIcon: { classPropertyName: "leftIcon", publicName: "leftIcon", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, menuClass: { classPropertyName: "menuClass", publicName: "menuClass", isSignal: true, isRequired: false, transformFunction: null }, matchTriggerWidth: { classPropertyName: "matchTriggerWidth", publicName: "matchTriggerWidth", isSignal: true, isRequired: false, transformFunction: null }, maxSelectedItems: { classPropertyName: "maxSelectedItems", publicName: "maxSelectedItems", isSignal: true, isRequired: false, transformFunction: null }, closeOnSelect: { classPropertyName: "closeOnSelect", publicName: "closeOnSelect", isSignal: true, isRequired: false, transformFunction: null }, hideSelected: { classPropertyName: "hideSelected", publicName: "hideSelected", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, createText: { classPropertyName: "createText", publicName: "createText", isSignal: true, isRequired: false, transformFunction: null }, groupSeparator: { classPropertyName: "groupSeparator", publicName: "groupSeparator", isSignal: true, isRequired: false, transformFunction: null }, searchPosition: { classPropertyName: "searchPosition", publicName: "searchPosition", isSignal: true, isRequired: false, transformFunction: null }, searchIcon: { classPropertyName: "searchIcon", publicName: "searchIcon", isSignal: true, isRequired: false, transformFunction: null }, searchPrefix: { classPropertyName: "searchPrefix", publicName: "searchPrefix", isSignal: true, isRequired: false, transformFunction: null }, triggerIcon: { classPropertyName: "triggerIcon", publicName: "triggerIcon", isSignal: true, isRequired: false, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, remoteSearch: { classPropertyName: "remoteSearch", publicName: "remoteSearch", isSignal: true, isRequired: false, transformFunction: null }, minTermLength: { classPropertyName: "minTermLength", publicName: "minTermLength", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, typeToSearchText: { classPropertyName: "typeToSearchText", publicName: "typeToSearchText", isSignal: true, isRequired: false, transformFunction: null }, virtualScroll: { classPropertyName: "virtualScroll", publicName: "virtualScroll", isSignal: true, isRequired: false, transformFunction: null }, itemHeight: { classPropertyName: "itemHeight", publicName: "itemHeight", isSignal: true, isRequired: false, transformFunction: null }, bufferAmount: { classPropertyName: "bufferAmount", publicName: "bufferAmount", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", search: "search", opened: "opened", closed: "closed", cleared: "cleared", added: "added", removed: "removed", selectionChange: "selectionChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block w-full" }, providers: [
|
|
10143
11095
|
{
|
|
10144
11096
|
provide: NG_VALUE_ACCESSOR,
|
|
10145
11097
|
useExisting: forwardRef(() => OlSelect),
|
|
10146
11098
|
multi: true,
|
|
10147
11099
|
},
|
|
10148
|
-
], queries: [{ propertyName: "optionTemplate", first: true, predicate: ["optionTemplate"], descendants: true }, { propertyName: "labelTemplate", first: true, predicate: ["labelTemplate"], descendants: true }], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "listboxRef", first: true, predicate: ["listboxRef"], descendants: true, isSignal: true }, { propertyName: "oTimPanel", first: true, predicate: ["oTimPanel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"w-full\">\n @if (label()) {\n <div class=\"flex items-center justify-between mb-1.5\">\n <label\n [id]=\"labelId()\"\n [for]=\"inputId()\"\n class=\"inline-flex items-center gap-1 text-sm font-medium text-heading\"\n >\n <span>{{ label() }}</span>\n @if (required() || showAsterisk()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n </div>\n }\n\n <div class=\"relative w-full\">\n @if (leftIcon()) {\n <div\n class=\"absolute inset-y-0 inset-s-0 z-10 flex items-center ps-3.5 pointer-events-none text-body-subtle\"\n >\n <ng-content select=\"[leftIcon]\" />\n </div>\n }\n\n <!--\n \u00D4 \u0110I\u1EC0U KHI\u1EC2N \u2014 gi\u1EEF NGUY\u00CAN b\u1EA3ng class c\u1EE7a b\u1EA3n `<select>` c\u0169 \u0111\u1EC3 giao di\u1EC7n\n Figma kh\u00F4ng \u0111\u1ED5i m\u1ED9t pixel: c\u00F9ng vi\u1EC1n, n\u1EC1n, bo g\u00F3c, thang c\u1EE1 (h-9/h-10/h-12)\n v\u00E0 c\u00F9ng b\u1EA3ng m\u00E0u theo `state`.\n\n `dark:focus:ring-brand-950` GI\u1EEE b\u1EADc s\u1ED1: `brand` l\u00E0 b\u1EA3ng Figma C\u00D3 n\u00EAn b\u1EADc\n s\u1ED1 h\u1EE3p l\u1EC7, c\u00F2n `ring-brand-softer` (token duy nh\u1EA5t tr\u1ECF brand-950 \u1EDF ch\u1EBF \u0111\u1ED9\n t\u1ED1i) ch\u01B0a n\u01A1i n\u00E0o d\u00F9ng n\u00EAn Tailwind kh\u00F4ng sinh CSS \u2014 thay v\u00E0o l\u00E0 class\n CH\u1EBET m\u00E0 build v\u1EABn xanh (\u0111o 2026-09-15).\n\n Kh\u00E1c b\u1EA3n c\u0169 \u1EDF ch\u1ED7 \u0111\u00E2y l\u00E0 `<div role=\"combobox\">` ch\u1EE9 kh\u00F4ng ph\u1EA3i `<select>`\n g\u1ED1c: th\u1EBB g\u1ED1c kh\u00F4ng cho t\u00ECm ki\u1EBFm, \u0111a ch\u1ECDn hay t\u1EF1 v\u1EBD t\u1EEBng d\u00F2ng.\n -->\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? labelId() : triggerLabel() ? triggerLabelId() : null\"\n [attr.aria-label]=\"label() || triggerLabel() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n aria-haspopup=\"listbox\"\n (click)=\"toggle()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"relative flex w-full items-center gap-1.5 border shadow-xs outline-none pe-10 font-normal\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.rounded-base]=\"true\"\n [class]=\"paddingStartClass()\"\n [class.flex-wrap]=\"multiple()\"\n [class.content-center]=\"multiple()\"\n [class.py-1]=\"multiple()\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.text-sm]=\"resolvedSize() === 'sm' || resolvedSize() === 'base'\"\n [class.text-base]=\"resolvedSize() === 'lg' || resolvedSize() === 'xl'\"\n [class.cursor-pointer]=\"!disabled() && !readonly()\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled()\"\n [class.text-heading]=\"state() === 'default' && !disabled()\"\n [class.focus:border-brand]=\"state() === 'default' && !disabled()\"\n [class.focus:ring-2]=\"!disabled()\"\n [class.focus:ring-brand-subtle]=\"state() === 'default' && !disabled()\"\n [class.dark:focus:ring-brand-950]=\"state() === 'default' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.focus:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.focus:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n >\n <!-- Th\u1EBB c\u1EE7a c\u00E1c gi\u00E1 tr\u1ECB \u0111\u00E3 ch\u1ECDn (\u0111a ch\u1ECDn) -->\n @if (multiple()) {\n @for (val of selectedValues(); track val) {\n <!-- `h-6` c\u1ED1 \u0111\u1ECBnh: \u0111\u1EC3 th\u1EBB co theo ch\u1EEF th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t ki\u1EC3u v\u00E0 \u00F4\n b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. C\u00F9ng c\u00E1ch \u0111\u00E3 ch\u1ED1t \u1EDF `ol-autocomplete`. -->\n <!-- Chip kho\u00E1 d\u00F9ng b\u1EA3ng m\u00E0u TRUNG T\u00CDNH: m\u1EA5t n\u00FAt \u00D7 l\u00E0 d\u1EA5u hi\u1EC7u qu\u00E1\n nh\u1EB9, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng n\u00FAt ch\u01B0a hi\u1EC7n. \u0110\u1ED5i m\u00E0u cho th\u1EA5y ngay \u0111\u00E2y\n l\u00E0 lo\u1EA1i chip kh\u00E1c. -->\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md border px-2 text-xs font-medium\"\n [class.border-brand-subtle]=\"!isLocked(val)\"\n [class.bg-brand-softer]=\"!isLocked(val)\"\n [class.text-fg-brand-strong]=\"!isLocked(val)\"\n [class.border-default]=\"isLocked(val)\"\n [class.bg-neutral-tertiary]=\"isLocked(val)\"\n [class.text-body]=\"isLocked(val)\"\n >\n @if (isLocked(val)) {\n <ol-icon name=\"lucide:lock\" size=\"xs\" class=\"shrink-0 opacity-70\" />\n } @else if (optionOf(val)?.icon) {\n <ol-icon [name]=\"$any(optionOf(val)).icon\" size=\"xs\" class=\"shrink-0\" />\n } @else if (optionOf(val)?.prefix) {\n <span class=\"shrink-0 text-2xs font-semibold tabular-nums\">{{\n optionOf(val)?.prefix\n }}</span>\n }\n <span>{{ getDisplayLabel(val) }}</span>\n @if (!disabled() && !readonly() && !isLocked(val)) {\n <button\n type=\"button\"\n (click)=\"removeValue(val, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabel(val)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!--\n PH\u1EA6N C\u1ED0 \u0110\u1ECANH c\u1EE7a \u00F4 \u0111\u00F3ng \u2014 icon v\u00E0 nh\u00E3n LU\u00D4N hi\u1EC7n, d\u00F9 \u0111\u00E3 ch\u1ECDn hay ch\u01B0a,\n d\u00F9 danh s\u00E1ch \u0111ang m\u1EDF hay \u0111\u00F3ng. Ch\u1EC9 ph\u1EA7n GI\u00C1 TR\u1ECA b\u00EAn ph\u1EA3i m\u1EDBi \u0111\u1ED5i.\n\n \u0110\u1EB7t NGO\u00C0I c\u1EA3 ba nh\u00E1nh d\u01B0\u1EDBi (\u00F4 t\u00ECm / nh\u00E3n \u0111\u00E3 ch\u1ECDn / placeholder) ch\u00EDnh\n l\u00E0 \u0111i\u1EC1u l\u00E0m n\u00F3 c\u1ED1 \u0111\u1ECBnh \u2014 n\u1EB1m trong nh\u00E1nh n\u00E0o th\u00EC n\u00F3 bi\u1EBFn m\u1EA5t \u1EDF hai\n nh\u00E1nh kia.\n\n Kh\u00E1c `icon` c\u1EE7a M\u1EE4C: c\u00E1i \u0111\u00F3 \u0111\u1ED5i theo m\u1EE5c \u0111ang ch\u1ECDn. Khai c\u1EA3 hai th\u00EC\n `triggerIcon` th\u1EAFng, v\u00EC nh\u00E3n c\u1ED1 \u0111\u1ECBnh m\u00E0 nh\u1EA3y theo m\u1EE5c th\u00EC h\u1EBFt c\u1ED1 \u0111\u1ECBnh.\n -->\n @if (triggerIcon()) {\n <ol-icon [name]=\"triggerIcon()\" size=\"sm\" class=\"shrink-0 text-body-subtle\" />\n }\n @if (triggerLabel()) {\n <span [id]=\"triggerLabelId()\" class=\"shrink-0 text-sm font-medium text-body\">{{\n triggerLabel()\n }}</span>\n }\n\n <!--\n \u00D4 G\u00D5 T\u00CCM ch\u1EC9 hi\u1EC7n khi danh s\u00E1ch \u0110ANG M\u1EDE. \u0110\u00F3ng l\u1EA1i th\u00EC hi\u1EC7n nh\u00E3n t\u0129nh \u2014\n n\u1EBFu \u0111\u1EC3 \u00F4 nh\u1EADp lu\u00F4n \u0111\u00F3, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng g\u00F5 th\u1EB3ng v\u00E0o \u0111\u01B0\u1EE3c, m\u00E0 gi\u00E1 tr\u1ECB\n l\u1EA1i kh\u00F4ng \u0111\u1ED5i theo ch\u1EEF h\u1ECD g\u00F5.\n -->\n @if (searchable() && isOpen() && searchPosition() === 'inline') {\n <input\n type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n autocomplete=\"off\"\n class=\"min-w-0 flex-1 border-none bg-transparent p-0 text-inherit outline-none placeholder:text-body-subtle\"\n />\n } @else if (!multiple() && hasValue()) {\n @if (labelTemplate) {\n <ng-container\n *ngTemplateOutlet=\"labelTemplate; context: { $implicit: selectedValues()[0] }\"\n />\n } @else {\n <!-- \u00D4 \u0111\u00F3ng hi\u1EC7n C\u1EA2 ph\u1EA7n \u0111\u1EA7u c\u1EE7a m\u1EE5c \u0111ang ch\u1ECDn, kh\u00F4ng ch\u1EC9 ch\u1EEF \u2014 \u0111\u00E2y\n l\u00E0 \u0111i\u1EC3m kh\u00E1c c\u1EE7a combobox reui so v\u1EDBi \u00F4 ch\u1ECDn th\u01B0\u1EDDng. -->\n @if (selectedItem()?.icon && !triggerIcon()) {\n <ol-icon\n [name]=\"$any(selectedItem()).icon\"\n size=\"sm\"\n class=\"shrink-0\"\n [class]=\"selectedItem()?.prefixClass || ''\"\n />\n } @else if (selectedItem()?.prefix && !triggerIcon()) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"selectedItem()?.prefixClass || 'text-body-subtle'\"\n >{{ selectedItem()?.prefix }}</span\n >\n }\n <span class=\"truncate\">{{ singleLabel() }}</span>\n }\n } @else if (!hasValue()) {\n <!--\n \u1EDE tr\u1EA1ng th\u00E1i L\u1ED6I, n\u1EC1n \u0111\u1ED5i sang `bg-danger-soft` (#fef0f2) v\u00E0\n `text-body-subtle` ch\u1EC9 c\u00F2n **4,36** \u2014 d\u01B0\u1EDBi chu\u1EA9n 4,5. N\u1EC1n `success`\n (#ecfdf5) cho 4,59, v\u1EEBa \u0111\u1EE7, n\u00EAn ch\u1EC9 nh\u00E1nh l\u1ED7i c\u1EA7n \u0111\u1EADm h\u01A1n.\n `text-body` tr\u00EAn n\u1EC1n \u0111\u00F3 ra 6,82 (s\u00E1ng) v\u00E0 10,66 (t\u1ED1i).\n -->\n <span\n class=\"truncate\"\n [class.text-body-subtle]=\"state() !== 'error'\"\n [class.text-body]=\"state() === 'error'\"\n >{{ placeholder() }}</span\n >\n }\n\n <!--\n M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0110\u1ED4I VAI theo tr\u1EA1ng th\u00E1i \u2014 kh\u00F4ng x\u1EBFp c\u1EA1nh nhau.\n B\u1EA3n \u0111\u1EA7u c\u1EE7a t\u00F4i \u0111\u1EC3 n\u00FAt xo\u00E1 v\u00E0 `chevron-down` C\u00D9NG hi\u1EC7n: \u0111o 2026-09-17\n tr\u00EAn `/forms` \u0111\u1EBFm **2 icon b\u00EAn ph\u1EA3i \u1EDF M\u1ECCI tr\u1EA1ng th\u00E1i**, k\u1EC3 c\u1EA3 khi danh\n s\u00E1ch \u0111ang m\u1EDF. C\u1EE5m b\u00EAn ph\u1EA3i ch\u1EADt, hai icon 16px chen nhau tr\u00F4ng r\u1ED1i.\n\n `ol-autocomplete` \u0111\u00E3 ch\u1ED1t l\u1ED1i n\u00E0y t\u1EEB tr\u01B0\u1EDBc (\u00F4 `w-7` \u0111\u1ED5i vai\n spinner / xo\u00E1 / chevron); `ol-select` ph\u1EA3i theo cho nh\u1EA5t qu\u00E1n.\n\n \u00D4 r\u1ED9ng C\u1ED0 \u0110\u1ECANH \u0111\u1EC3 khung kh\u00F4ng nh\u1EA3y m\u1ED7i l\u1EA7n icon \u0111\u1ED5i vai.\n\n \u0110\u1EC6M PH\u1EA2I l\u00E0 B\u1EAET BU\u1ED8C, kh\u00F4ng ph\u1EA3i trang tr\u00ED: c\u1EE5m n\u00E0y `absolute` n\u00EAn BUNG\n KH\u1ECEI LU\u1ED2NG, kh\u00F4ng h\u01B0\u1EDFng `px-3.5` c\u1EE7a khung nh\u01B0 `ol-autocomplete` (c\u00E1i\n \u0111\u00F3 d\u00F9ng `ms-auto`, v\u1EABn n\u1EB1m trong lu\u1ED3ng). B\u1EA3n \u0111\u1EA7u ch\u1EC9 c\u00F3 `w-7` \u2014 \u0111o\n 2026-09-17 ra icon c\u00E1ch m\u00E9p ph\u1EA3i **3px**, d\u00EDnh s\u00E1t l\u1EC1.\n\n Figma `ol-select` Type=Default: icon `angle-down` c\u00E1ch m\u00E9p ph\u1EA3i **12px**.\n Con s\u1ED1 `pe-2` (8px) tr\u00F4ng c\u00F3 v\u1EBB h\u1EE5t, nh\u01B0ng n\u00FAt xo\u00E1 l\u00E0 `size-6` (24px)\n b\u1ECDc icon 16px n\u00EAn d\u01B0 4px m\u1ED7i b\u00EAn \u2014 8 + 4 = 12px t\u1EDBi SVG th\u1EADt. \u0110o b\u1EB1ng\n m\u00E9p SVG ch\u1EE9 kh\u00F4ng m\u00E9p n\u00FAt, v\u00EC m\u1EAFt ng\u01B0\u1EDDi nh\u00ECn th\u1EA5y SVG.\n\n `justify-end` ch\u1EE9 kh\u00F4ng `justify-center`: c\u0103n gi\u1EEFa th\u00EC icon tr\u00F4i theo\n b\u1EC1 r\u1ED9ng \u00F4, \u0111\u1ED5i `w-*` l\u00E0 l\u1EC7ch l\u1EA1i.\n -->\n <div class=\"absolute inset-y-0 inset-e-0 flex w-10 items-center justify-end pe-3.5\">\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (clearable() && hasContent() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"pointer-events-none shrink-0 text-body-subtle transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n\n <!--\n DANH S\u00C1CH. Token \u0111\u1ECDc th\u1EB3ng t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147),\n bi\u1EBFn th\u1EC3 `Type=Default` \u2014 \u0111\u00F3 l\u00E0 dropdown chu\u1EA9n c\u1EE7a design system:\n\n bo g\u00F3c 12px -> rounded-md\n \u0111\u1EC7m 8px -> p-2 (tr\u01B0\u1EDBc l\u00E0 p-1.5 = 6px)\n n\u1EC1n bg-neutral-primary-medium (tr\u01B0\u1EDBc l\u00E0 bg-neutral-primary)\n vi\u1EC1n border-default-medium (tr\u01B0\u1EDBc l\u00E0 border-default)\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng (#ffffff / #e5e7eb) n\u00EAn \u0111\u1ED5i\n hay kh\u00F4ng nh\u00ECn c\u0169ng th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I th\u00EC kh\u00E1c h\u1EB3n:\n\n bg-primary gray-950 \u2190 \u0111ang d\u00F9ng, g\u1EA7n \u0111en, panel CH\u00CCM v\u00E0o n\u1EC1n\n bg-primary-medium gray-800 \u2190 Figma, panel n\u1ED5i l\u00EAn\n border-default gray-800\n border-default-medium gray-700\n\n \u0110\u00E2y \u0111\u00FAng lo\u1EA1i l\u1ED7i ch\u1EC9 l\u1ED9 \u1EDF ch\u1EBF \u0111\u1ED9 t\u1ED1i m\u00E0 ch\u1EBF \u0111\u1ED9 s\u00E1ng kh\u00F4ng bao gi\u1EDD th\u1EA5y.\n -->\n @if (isOpen()) {\n <!--\n \u00D4 T\u00CCM \u1EDE \u0110\u1EA6U PANEL (`searchPosition=\"panel\"`).\n \u0110\u1EB7t NGO\u00C0I th\u1EBB `role=\"listbox\"`: m\u1ED9t `<input>` n\u1EB1m trong listbox l\u00E0 sai\n ARIA \u2014 m\u1ECDi con c\u1EE7a listbox ph\u1EA3i l\u00E0 `option` ho\u1EB7c `group`. \u0110\u00E2y l\u00E0 l\u1ED1i\n `ol-command` v\u00E0 combobox c\u1EE7a reui d\u00F9ng.\n -->\n <!--\n H\u1ED8P B\u1ECCC mang `#panel` v\u00E0 `position: absolute`.\n\n `#panel` ph\u1EA3i \u1EDF \u0110\u00C2Y ch\u1EE9 kh\u00F4ng \u1EDF th\u1EBB `role=\"listbox\"`: `appendTo=\"body\"`\n \u0111\u1EA9y ch\u00EDnh ph\u1EA7n t\u1EED `#panel` ra `<body>`. \u0110\u1EC3 n\u00F3 \u1EDF listbox th\u00EC \u00F4 t\u00ECm \u1EDF l\u1EA1i\n trong c\u00E2y DOM c\u0169 c\u00F2n danh s\u00E1ch bay \u0111i \u2014 h\u1ED9p tho\u1EA1i v\u1EE1 l\u00E0m \u0111\u00F4i.\n -->\n <div\n #panel\n class=\"absolute z-dropdown overflow-hidden rounded-md border border-default-medium bg-neutral-primary-medium shadow-lg flex flex-col max-h-[var(--ol-overlay-max-height,24rem)]\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (searchable() && searchPosition() === 'panel') {\n <div class=\"border-b border-default-subtle p-2\">\n <!-- Icon/ch\u1EEF \u0111\u1EE9ng \u0111\u1EA7u \u00F4 T\u00CCM \u2014 c\u1EE7a vi\u1EC7c T\u00CCM, kh\u00F4ng ph\u1EA3i c\u1EE7a m\u1EE5c\n \u0111ang ch\u1ECDn. B\u1ECDc trong khung c\u00F3 vi\u1EC1n \u0111\u1EC3 icon n\u1EB1m TRONG \u00F4, kh\u00F4ng\n \u0111\u1EE9ng r\u1EDDi b\u00EAn ngo\u00E0i nh\u01B0 m\u1ED9t nh\u00E3n. -->\n <div\n class=\"flex items-center gap-2 rounded border border-default bg-neutral-primary px-2.5 py-1.5 focus-within:border-brand\"\n >\n @if (searchIcon()) {\n <ol-icon [name]=\"searchIcon()\" size=\"sm\" class=\"shrink-0 text-body-subtle\" />\n } @else if (searchPrefix()) {\n <span class=\"shrink-0 text-xs font-medium text-body-subtle\">{{\n searchPrefix()\n }}</span>\n }\n <input\n #oTimPanel\n type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n [attr.aria-controls]=\"listboxId()\"\n autocomplete=\"off\"\n class=\"w-full min-w-0 border-none bg-transparent p-0 text-sm outline-none placeholder:text-body-subtle\"\n />\n </div>\n </div>\n }\n <!--\n `overflow-x-hidden` KHAI R\u00D5: khai `overflow-y-auto` m\u1ED9t m\u00ECnh th\u00EC tr\u00ECnh\n duy\u1EC7t t\u1EF1 t\u00EDnh `overflow-x` th\u00E0nh `auto` (spec CSS: m\u1ED9t tr\u1EE5c `visible`\n c\u00F2n tr\u1EE5c kia kh\u00E1c `visible` l\u00E0 kh\u00F4ng h\u1EE3p l\u1EC7). \u0110o \u0111\u01B0\u1EE3c `scrollWidth\n 765 > 620` khi option c\u00F3 `white-space: nowrap`.\n\n Ch\u1EC9 ch\u1EB7n \u1EDF PANEL CH\u1ECCN c\u00F3 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh \u2014 n\u1ED9i dung ph\u1EA3i v\u1EEBa b\u1EC1 ngang.\n KH\u00D4NG ch\u1EB7n \u1EDF khung ch\u1EE9a n\u1ED9i dung t\u1EF1 do (`device-mockup`,\n `chat-container`, `drawer`): \u1EDF \u0111\u00F3 ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu n\u1ED9i dung b\u1EA5t k\u1EF3\n v\u00E0o, ch\u1EB7n l\u00E0 c\u1EAFt m\u1EA5t ph\u1EA7n r\u1ED9ng c\u1EE7a h\u1ECD.\n -->\n <div\n #listboxRef\n role=\"listbox\"\n [id]=\"listboxId()\"\n [attr.aria-multiselectable]=\"multiple() ? 'true' : null\"\n (scroll)=\"onPanelScroll($event)\"\n class=\"flex-1 min-h-0 overflow-y-auto overflow-x-hidden p-2\"\n >\n @if (waitingForTerm()) {\n <!-- Ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`: \"kh\u00F4ng t\u00ECm th\u1EA5y\" l\u00E0 sai s\u1EF1 th\u1EADt, v\u00EC\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i `typeToSearchText` c\u1EE7a ng-select. -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">{{ typeToSearchText() }}</div>\n } @else if (rows().length === 0) {\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ emptyText() }}</p>\n @if (canCreate()) {\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"mt-2 inline-flex items-center gap-1.5 rounded-lg bg-brand-softer px-3 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-subtle\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>{{ createLabel() }}</span>\n </button>\n }\n </div>\n } @else {\n <!-- \u0110\u1EC7m tr\u00EAn: gi\u1EEF thanh cu\u1ED9n d\u00E0i \u0111\u00FAng nh\u01B0 khi v\u1EBD h\u1EBFt danh s\u00E1ch. -->\n @if (viewport().padTop > 0) {\n <div [style.height.px]=\"viewport().padTop\" aria-hidden=\"true\"></div>\n }\n\n @for (row of viewport().rows; track row.kind + '-' + row.index + '-' + row.label) {\n @if (row.kind === 'group') {\n <!-- `dauTien` t\u00EDnh khi d\u1EF1ng danh s\u00E1ch, KH\u00D4NG d\u00F9ng `$first` c\u1EE7a\n `@for`: khi `virtualScroll` b\u1EADt, `$first` l\u00E0 d\u00F2ng \u0111\u1EA7u c\u1EE7a\n khung nh\u00ECn n\u00EAn cu\u1ED9n xu\u1ED1ng l\u00E0 m\u1EA5t \u0111\u01B0\u1EDDng ng\u0103n. -->\n @if (groupSeparator() && !row.isFirstGroup) {\n <div role=\"presentation\" class=\"my-1 border-t border-default-subtle\"></div>\n }\n <div\n role=\"presentation\"\n class=\"px-2.5 pt-2 pb-0 text-2xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ row.label }}\n </div>\n } @else {\n <!--\n `rounded-sm` (6px) ch\u1EE9 kh\u00F4ng `rounded-md` (12px): Figma v\u1EBD\n `ol-autocomplete-item` bo **8px**, m\u00E0 thang token c\u1EE7a lib kh\u00F4ng\n c\u00F3 b\u1EADc 8 (xs 4 \u00B7 sm 6 \u00B7 md/base 12 \u00B7 lg 16). L\u1EA5y b\u1EADc G\u1EA6N NH\u1EA4T\n thay v\u00EC vi\u1EBFt `rounded-[8px]` \u2014 gi\u00E1 tr\u1ECB th\u00F4 n\u1EB1m ngo\u00E0i h\u1EC7 token\n th\u00EC \u0111\u1ED5i theme kh\u00F4ng theo \u0111\u01B0\u1EE3c.\n\n `ol-dropdown-item` c\u0169ng \u0111\u00E3 ch\u1ED1t `rounded-sm` cho m\u1EE5c menu, n\u00EAn\n \u0111\u00E2y c\u00F2n l\u00E0 gi\u1EEF nh\u1EA5t qu\u00E1n trong lib.\n -->\n <!--\n `min-w-0` cho ph\u00E9p h\u00E0ng CO l\u1EA1i, \u0111i\u1EC1u ki\u1EC7n c\u1EA7n \u0111\u1EC3 `truncate`\n c\u1EE7a ng\u01B0\u1EDDi d\u00F9ng ch\u1EA1y. Nh\u01B0ng n\u00F3 KH\u00D4NG t\u1EF1 ch\u1EB7n tr\u00E0n: \u0111o b\u1EB1ng c\u00E1ch\n ti\u00EAm `white-space: nowrap` v\u00E0o h\u00E0ng \u2014 panel v\u1EABn `scrollWidth\n 1763 > 831`. Ch\u1EC9 `overflow: hidden` \u0111\u1EB7t tr\u00EAn CH\u00CDNH ph\u1EA7n t\u1EED n\u1ED9i\n dung m\u1EDBi ch\u1EB7n \u0111\u01B0\u1EE3c, m\u00E0 ph\u1EA7n t\u1EED \u0111\u00F3 do ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu v\u00E0o qua\n `optionTemplate` n\u00EAn lib kh\u00F4ng v\u1EDBi t\u1EDBi. `overflow-x-hidden` \u1EDF\n panel l\u00E0 l\u1EDBp ch\u1EB7n cu\u1ED1i.\n -->\n <div\n role=\"option\"\n [id]=\"optionId(row.index)\"\n [attr.data-option-index]=\"row.index\"\n [attr.aria-selected]=\"isSelected(row.item)\"\n [attr.aria-disabled]=\"row.item?.disabled ? 'true' : null\"\n (click)=\"selectItem(row.item); $event.stopPropagation()\"\n [style.height.px]=\"virtualScroll() ? itemHeight() : null\"\n class=\"flex min-w-0 items-center gap-2 rounded-sm px-3 py-2 text-sm transition-colors\"\n [class.cursor-pointer]=\"!row.item?.disabled\"\n [class.cursor-not-allowed]=\"row.item?.disabled\"\n [class.bg-neutral-tertiary-medium]=\"\n activeIndex() === row.index && !row.item?.disabled\n \"\n [class.hover:bg-neutral-tertiary-medium]=\"\n !row.item?.disabled && activeIndex() !== row.index\n \"\n [class.text-heading]=\"isSelected(row.item) || activeIndex() === row.index\"\n [class.hover:text-heading]=\"!row.item?.disabled\"\n [class.font-medium]=\"isSelected(row.item)\"\n [class.text-body]=\"!isSelected(row.item) && activeIndex() !== row.index\"\n [class.text-fg-disabled]=\"row.item?.disabled\"\n >\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: row.item }\"\n />\n } @else {\n <!-- Ph\u1EA7n \u0111\u1EA7u: `icon` \u01B0u ti\u00EAn, kh\u00F4ng c\u00F3 th\u00EC `prefix` (ch\u1EEF).\n `prefixClass` nh\u1EADn CLASS ch\u1EE9 kh\u00F4ng nh\u1EADn m\u00E3 m\u00E0u \u2014 m\u00E3 m\u00E0u\n c\u1EE9ng kh\u00F4ng \u0111\u1ED5i theo theme v\u00E0 theo ch\u1EBF \u0111\u1ED9 t\u1ED1i. -->\n @if (row.item?.icon) {\n <ol-icon\n [name]=\"row.item.icon\"\n size=\"sm\"\n class=\"shrink-0\"\n [class]=\"row.item.prefixClass || ''\"\n />\n } @else if (row.item?.prefix) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"row.item.prefixClass || 'text-body-subtle'\"\n >{{ row.item.prefix }}</span\n >\n }\n <span class=\"min-w-0 flex-1 truncate\">{{ row.label }}</span>\n }\n @if (isSelected(row.item)) {\n <ol-icon name=\"check\" size=\"sm\" class=\"shrink-0 text-fg-brand\" />\n }\n </div>\n }\n }\n\n <!-- \u0110\u1EC7m d\u01B0\u1EDBi \u2014 xem l\u00FD do \u1EDF \u0111\u1EC7m tr\u00EAn. -->\n @if (viewport().padBottom > 0) {\n <div [style.height.px]=\"viewport().padBottom\" aria-hidden=\"true\"></div>\n }\n\n <!-- Danh s\u00E1ch C\u00D2N m\u1EE5c nh\u01B0ng ch\u1EEF \u0111ang g\u00F5 ch\u01B0a tr\u00F9ng nh\u00E3n n\u00E0o: v\u1EABn ph\u1EA3i\n cho t\u1EA1o. V\u00ED d\u1EE5 \u0111\u00E3 c\u00F3 \"To\u00E1n 10\" m\u00E0 ng\u01B0\u1EDDi d\u00F9ng g\u00F5 \"To\u00E1n 11\" \u2014\n nh\u00E1nh r\u1ED7ng \u1EDF tr\u00EAn kh\u00F4ng ch\u1EA1y v\u00EC b\u1ED9 l\u1ECDc v\u1EABn ra m\u1ED9t m\u1EE5c. -->\n @if (canCreate()) {\n <div class=\"mt-1 border-t border-default-subtle p-2\">\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"inline-flex w-full items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-softer\"\n >\n <ol-icon name=\"plus\" size=\"xs\" class=\"shrink-0\" />\n <span class=\"truncate\">{{ createLabel() }}</span>\n </button>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n\n @if (helperText()) {\n <p\n class=\"mt-1.5 text-xs\"\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger-strong]=\"state() === 'error'\"\n [class.text-fg-success-strong]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlSpinner, selector: "ol-spinner", inputs: ["variant", "size", "color", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
11100
|
+
], queries: [{ propertyName: "optionTemplate", first: true, predicate: ["optionTemplate"], descendants: true }, { propertyName: "labelTemplate", first: true, predicate: ["labelTemplate"], descendants: true }], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "listboxRef", first: true, predicate: ["listboxRef"], descendants: true, isSignal: true }, { propertyName: "oTimPanel", first: true, predicate: ["oTimPanel"], descendants: true, isSignal: true }, { propertyName: "inlineSearch", first: true, predicate: ["inlineSearch"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"resolvedSize()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n\n <div class=\"relative w-full\">\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"resolvedSize()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"hasValue()\"\n [open]=\"isOpen()\"\n >\n <!-- Icon tr\u00E1i neo theo KHUNG NH\u1EACP, kh\u00F4ng theo khung n\u1ED5i (c\u00F3 h\u00E0ng nh\u00E3n 20px). -->\n <div class=\"relative\">\n @if (leftIcon()) {\n <div\n class=\"absolute inset-y-0 inset-s-0 z-10 flex items-center pointer-events-none text-body-subtle\"\n [class]=\"leftIconPadClass()\"\n >\n <ng-content select=\"[leftIcon]\" />\n </div>\n }\n\n <!--\n \u00D4 \u0110I\u1EC0U KHI\u1EC2N \u2014 gi\u1EEF NGUY\u00CAN b\u1EA3ng class c\u1EE7a b\u1EA3n `<select>` c\u0169 \u0111\u1EC3 giao di\u1EC7n\n Figma kh\u00F4ng \u0111\u1ED5i m\u1ED9t pixel: c\u00F9ng vi\u1EC1n, n\u1EC1n, bo g\u00F3c, thang c\u1EE1 (h-9/h-10/h-12)\n v\u00E0 c\u00F9ng b\u1EA3ng m\u00E0u theo `state`.\n\n `dark:focus:ring-brand-950` GI\u1EEE b\u1EADc s\u1ED1: `brand` l\u00E0 b\u1EA3ng Figma C\u00D3 n\u00EAn b\u1EADc\n s\u1ED1 h\u1EE3p l\u1EC7, c\u00F2n `ring-brand-softer` (token duy nh\u1EA5t tr\u1ECF brand-950 \u1EDF ch\u1EBF \u0111\u1ED9\n t\u1ED1i) ch\u01B0a n\u01A1i n\u00E0o d\u00F9ng n\u00EAn Tailwind kh\u00F4ng sinh CSS \u2014 thay v\u00E0o l\u00E0 class\n CH\u1EBET m\u00E0 build v\u1EABn xanh (\u0111o 2026-09-15).\n\n Kh\u00E1c b\u1EA3n c\u0169 \u1EDF ch\u1ED7 \u0111\u00E2y l\u00E0 `<div role=\"combobox\">` ch\u1EE9 kh\u00F4ng ph\u1EA3i `<select>`\n g\u1ED1c: th\u1EBB g\u1ED1c kh\u00F4ng cho t\u00ECm ki\u1EBFm, \u0111a ch\u1ECDn hay t\u1EF1 v\u1EBD t\u1EEBng d\u00F2ng.\n -->\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? labelId() : triggerLabel() ? triggerLabelId() : null\"\n [attr.aria-label]=\"label() || triggerLabel() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n aria-haspopup=\"listbox\"\n (click)=\"toggle()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"ol-field-box relative flex w-full items-center gap-1.5 border shadow-xs outline-none font-normal\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.rounded-base]=\"true\"\n [class]=\"paddingStartClass()\"\n [class.flex-wrap]=\"multiple()\"\n [class.content-center]=\"multiple()\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.text-sm]=\"resolvedSize() === 'sm' || resolvedSize() === 'base'\"\n [class.text-base]=\"resolvedSize() === 'lg' || resolvedSize() === 'xl'\"\n [class.cursor-pointer]=\"!disabled() && !readonly()\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled()\"\n [class.text-heading]=\"state() === 'default' && !disabled()\"\n [class.focus:border-brand]=\"state() === 'default' && !disabled()\"\n [class.focus:ring-2]=\"!disabled()\"\n [class.focus:ring-brand-subtle]=\"state() === 'default' && !disabled()\"\n [class.dark:focus:ring-brand-950]=\"state() === 'default' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.focus:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.focus:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n >\n <!-- Th\u1EBB c\u1EE7a c\u00E1c gi\u00E1 tr\u1ECB \u0111\u00E3 ch\u1ECDn (\u0111a ch\u1ECDn) -->\n @if (multiple()) {\n @for (val of selectedValues(); track val) {\n <!-- `h-6` c\u1ED1 \u0111\u1ECBnh: \u0111\u1EC3 th\u1EBB co theo ch\u1EEF th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t ki\u1EC3u v\u00E0 \u00F4\n b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. C\u00F9ng c\u00E1ch \u0111\u00E3 ch\u1ED1t \u1EDF `ol-autocomplete`. -->\n <!-- Chip kho\u00E1 d\u00F9ng b\u1EA3ng m\u00E0u TRUNG T\u00CDNH: m\u1EA5t n\u00FAt \u00D7 l\u00E0 d\u1EA5u hi\u1EC7u qu\u00E1\n nh\u1EB9, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng n\u00FAt ch\u01B0a hi\u1EC7n. \u0110\u1ED5i m\u00E0u cho th\u1EA5y ngay \u0111\u00E2y\n l\u00E0 lo\u1EA1i chip kh\u00E1c. -->\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md border px-2 text-xs font-medium\"\n [class.border-brand-subtle]=\"!isLocked(val)\"\n [class.bg-brand-softer]=\"!isLocked(val)\"\n [class.text-fg-brand-strong]=\"!isLocked(val)\"\n [class.border-default]=\"isLocked(val)\"\n [class.bg-neutral-tertiary]=\"isLocked(val)\"\n [class.text-body]=\"isLocked(val)\"\n >\n @if (isLocked(val)) {\n <ol-icon name=\"lucide:lock\" size=\"xs\" class=\"shrink-0 opacity-70\" />\n } @else if (optionOf(val)?.icon) {\n <ol-icon [name]=\"$any(optionOf(val)).icon\" size=\"xs\" class=\"shrink-0\" />\n } @else if (optionOf(val)?.prefix) {\n <span class=\"shrink-0 text-2xs font-semibold tabular-nums\">{{\n optionOf(val)?.prefix\n }}</span>\n }\n <span>{{ getDisplayLabel(val) }}</span>\n @if (!disabled() && !readonly() && !isLocked(val)) {\n <button\n type=\"button\"\n (click)=\"removeValue(val, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabel(val)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!--\n PH\u1EA6N C\u1ED0 \u0110\u1ECANH c\u1EE7a \u00F4 \u0111\u00F3ng \u2014 icon v\u00E0 nh\u00E3n LU\u00D4N hi\u1EC7n, d\u00F9 \u0111\u00E3 ch\u1ECDn hay ch\u01B0a,\n d\u00F9 danh s\u00E1ch \u0111ang m\u1EDF hay \u0111\u00F3ng. Ch\u1EC9 ph\u1EA7n GI\u00C1 TR\u1ECA b\u00EAn ph\u1EA3i m\u1EDBi \u0111\u1ED5i.\n\n \u0110\u1EB7t NGO\u00C0I c\u1EA3 ba nh\u00E1nh d\u01B0\u1EDBi (\u00F4 t\u00ECm / nh\u00E3n \u0111\u00E3 ch\u1ECDn / placeholder) ch\u00EDnh\n l\u00E0 \u0111i\u1EC1u l\u00E0m n\u00F3 c\u1ED1 \u0111\u1ECBnh \u2014 n\u1EB1m trong nh\u00E1nh n\u00E0o th\u00EC n\u00F3 bi\u1EBFn m\u1EA5t \u1EDF hai\n nh\u00E1nh kia.\n\n Kh\u00E1c `icon` c\u1EE7a M\u1EE4C: c\u00E1i \u0111\u00F3 \u0111\u1ED5i theo m\u1EE5c \u0111ang ch\u1ECDn. Khai c\u1EA3 hai th\u00EC\n `triggerIcon` th\u1EAFng, v\u00EC nh\u00E3n c\u1ED1 \u0111\u1ECBnh m\u00E0 nh\u1EA3y theo m\u1EE5c th\u00EC h\u1EBFt c\u1ED1 \u0111\u1ECBnh.\n -->\n @if (triggerIcon()) {\n <ol-icon [name]=\"triggerIcon()\" size=\"sm\" class=\"shrink-0 me-0.5 text-body-subtle\" />\n }\n @if (triggerLabel()) {\n <span [id]=\"triggerLabelId()\" class=\"shrink-0 text-sm font-medium text-body\">{{\n triggerLabel()\n }}</span>\n }\n\n <!--\n \u00D4 G\u00D5 T\u00CCM ch\u1EC9 hi\u1EC7n khi danh s\u00E1ch \u0110ANG M\u1EDE. \u0110\u00F3ng l\u1EA1i th\u00EC hi\u1EC7n nh\u00E3n t\u0129nh \u2014\n n\u1EBFu \u0111\u1EC3 \u00F4 nh\u1EADp lu\u00F4n \u0111\u00F3, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng g\u00F5 th\u1EB3ng v\u00E0o \u0111\u01B0\u1EE3c, m\u00E0 gi\u00E1 tr\u1ECB\n l\u1EA1i kh\u00F4ng \u0111\u1ED5i theo ch\u1EEF h\u1ECD g\u00F5.\n -->\n @if (searchable() && isOpen() && searchPosition() === 'inline') {\n <input\n #inlineSearch\n type=\"text\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n autocomplete=\"off\"\n class=\"min-w-0 flex-1 border-none bg-transparent p-0 text-inherit outline-none placeholder:text-body-subtle\"\n />\n } @else if (!multiple() && hasValue()) {\n @if (labelTemplate) {\n <ng-container\n *ngTemplateOutlet=\"labelTemplate; context: { $implicit: selectedValues()[0] }\"\n />\n } @else {\n <!-- \u00D4 \u0111\u00F3ng hi\u1EC7n C\u1EA2 ph\u1EA7n \u0111\u1EA7u c\u1EE7a m\u1EE5c \u0111ang ch\u1ECDn, kh\u00F4ng ch\u1EC9 ch\u1EEF \u2014 \u0111\u00E2y\n l\u00E0 \u0111i\u1EC3m kh\u00E1c c\u1EE7a combobox reui so v\u1EDBi \u00F4 ch\u1ECDn th\u01B0\u1EDDng. -->\n @if (selectedItem()?.icon && !triggerIcon()) {\n <ol-icon\n [name]=\"$any(selectedItem()).icon\"\n size=\"sm\"\n class=\"shrink-0 me-0.5\"\n [class]=\"selectedItem()?.prefixClass || ''\"\n />\n } @else if (selectedItem()?.prefix && !triggerIcon()) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"selectedItem()?.prefixClass || 'text-body-subtle'\"\n >{{ selectedItem()?.prefix }}</span\n >\n }\n <span class=\"truncate\">{{ singleLabel() }}</span>\n }\n } @else if (!hasValue()) {\n <!--\n \u1EDE tr\u1EA1ng th\u00E1i L\u1ED6I, n\u1EC1n \u0111\u1ED5i sang `bg-danger-soft` (#fef0f2) v\u00E0\n `text-body-subtle` ch\u1EC9 c\u00F2n **4,36** \u2014 d\u01B0\u1EDBi chu\u1EA9n 4,5. N\u1EC1n `success`\n (#ecfdf5) cho 4,59, v\u1EEBa \u0111\u1EE7, n\u00EAn ch\u1EC9 nh\u00E1nh l\u1ED7i c\u1EA7n \u0111\u1EADm h\u01A1n.\n `text-body` tr\u00EAn n\u1EC1n \u0111\u00F3 ra 6,82 (s\u00E1ng) v\u00E0 10,66 (t\u1ED1i).\n -->\n <span\n class=\"truncate\"\n data-ol-field-text\n data-ol-field-placeholder\n [class.text-body-subtle]=\"state() !== 'error'\"\n [class.text-body]=\"state() === 'error'\"\n >{{ placeholder() }}</span\n >\n }\n\n <!--\n M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0110\u1ED4I VAI theo tr\u1EA1ng th\u00E1i \u2014 kh\u00F4ng x\u1EBFp c\u1EA1nh nhau.\n B\u1EA3n \u0111\u1EA7u c\u1EE7a t\u00F4i \u0111\u1EC3 n\u00FAt xo\u00E1 v\u00E0 `chevron-down` C\u00D9NG hi\u1EC7n: \u0111o 2026-09-17\n tr\u00EAn `/forms` \u0111\u1EBFm **2 icon b\u00EAn ph\u1EA3i \u1EDF M\u1ECCI tr\u1EA1ng th\u00E1i**, k\u1EC3 c\u1EA3 khi danh\n s\u00E1ch \u0111ang m\u1EDF. C\u1EE5m b\u00EAn ph\u1EA3i ch\u1EADt, hai icon 16px chen nhau tr\u00F4ng r\u1ED1i.\n\n `ol-autocomplete` \u0111\u00E3 ch\u1ED1t l\u1ED1i n\u00E0y t\u1EEB tr\u01B0\u1EDBc (\u00F4 `w-7` \u0111\u1ED5i vai\n spinner / xo\u00E1 / chevron); `ol-select` ph\u1EA3i theo cho nh\u1EA5t qu\u00E1n.\n\n C\u1EE5m n\u00E0y `absolute` n\u00EAn BUNG KH\u1ECEI LU\u1ED2NG: l\u1EC1 ph\u1EA3i l\u1EA5y theo c\u1EE1\n (`controlPadClass`), c\u00F2n khung ch\u1EEBa ch\u1ED7 b\u1EB1ng `FIELD_ICON_RESERVE_END`\n \u0111\u1EC3 ch\u1EEF d\u00E0i d\u1EEBng c\u00E1ch icon \u0111\u00FAng 8px. N\u00FAt xo\u00E1 b\u1ECDc `size-4` b\u1EB1ng icon, \u0111o\n theo m\u00E9p SVG \u2014 th\u1EE9 m\u1EAFt ng\u01B0\u1EDDi nh\u00ECn th\u1EA5y.\n -->\n <div\n class=\"absolute inset-y-0 inset-e-0 flex items-center justify-end\"\n [class]=\"controlPadClass()\"\n >\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (clearable() && hasContent() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"pointer-events-none shrink-0 text-body-subtle transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n </div>\n </ol-field-frame>\n\n <!--\n DANH S\u00C1CH. Token \u0111\u1ECDc th\u1EB3ng t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147),\n bi\u1EBFn th\u1EC3 `Type=Default` \u2014 \u0111\u00F3 l\u00E0 dropdown chu\u1EA9n c\u1EE7a design system:\n\n bo g\u00F3c 12px -> rounded-md\n \u0111\u1EC7m 8px -> p-2 (tr\u01B0\u1EDBc l\u00E0 p-1.5 = 6px)\n n\u1EC1n bg-neutral-primary-medium (tr\u01B0\u1EDBc l\u00E0 bg-neutral-primary)\n vi\u1EC1n border-default-medium (tr\u01B0\u1EDBc l\u00E0 border-default)\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng (#ffffff / #e5e7eb) n\u00EAn \u0111\u1ED5i\n hay kh\u00F4ng nh\u00ECn c\u0169ng th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I th\u00EC kh\u00E1c h\u1EB3n:\n\n bg-primary gray-950 \u2190 \u0111ang d\u00F9ng, g\u1EA7n \u0111en, panel CH\u00CCM v\u00E0o n\u1EC1n\n bg-primary-medium gray-800 \u2190 Figma, panel n\u1ED5i l\u00EAn\n border-default gray-800\n border-default-medium gray-700\n\n \u0110\u00E2y \u0111\u00FAng lo\u1EA1i l\u1ED7i ch\u1EC9 l\u1ED9 \u1EDF ch\u1EBF \u0111\u1ED9 t\u1ED1i m\u00E0 ch\u1EBF \u0111\u1ED9 s\u00E1ng kh\u00F4ng bao gi\u1EDD th\u1EA5y.\n -->\n @if (isOpen()) {\n <!--\n \u00D4 T\u00CCM \u1EDE \u0110\u1EA6U PANEL (`searchPosition=\"panel\"`).\n \u0110\u1EB7t NGO\u00C0I th\u1EBB `role=\"listbox\"`: m\u1ED9t `<input>` n\u1EB1m trong listbox l\u00E0 sai\n ARIA \u2014 m\u1ECDi con c\u1EE7a listbox ph\u1EA3i l\u00E0 `option` ho\u1EB7c `group`. \u0110\u00E2y l\u00E0 l\u1ED1i\n `ol-command` v\u00E0 combobox c\u1EE7a reui d\u00F9ng.\n -->\n <!--\n H\u1ED8P B\u1ECCC mang `#panel` v\u00E0 `position: absolute`.\n\n `#panel` ph\u1EA3i \u1EDF \u0110\u00C2Y ch\u1EE9 kh\u00F4ng \u1EDF th\u1EBB `role=\"listbox\"`: `appendTo=\"body\"`\n \u0111\u1EA9y ch\u00EDnh ph\u1EA7n t\u1EED `#panel` ra `<body>`. \u0110\u1EC3 n\u00F3 \u1EDF listbox th\u00EC \u00F4 t\u00ECm \u1EDF l\u1EA1i\n trong c\u00E2y DOM c\u0169 c\u00F2n danh s\u00E1ch bay \u0111i \u2014 h\u1ED9p tho\u1EA1i v\u1EE1 l\u00E0m \u0111\u00F4i.\n -->\n <div\n #panel\n class=\"absolute z-dropdown overflow-hidden rounded-md border border-default-medium bg-neutral-primary-medium shadow-lg flex flex-col max-h-[var(--ol-overlay-max-height,24rem)]\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (searchable() && searchPosition() === 'panel') {\n <div class=\"border-b border-default-subtle p-2\">\n <!-- Icon/ch\u1EEF \u0111\u1EE9ng \u0111\u1EA7u \u00F4 T\u00CCM \u2014 c\u1EE7a vi\u1EC7c T\u00CCM, kh\u00F4ng ph\u1EA3i c\u1EE7a m\u1EE5c\n \u0111ang ch\u1ECDn. B\u1ECDc trong khung c\u00F3 vi\u1EC1n \u0111\u1EC3 icon n\u1EB1m TRONG \u00F4, kh\u00F4ng\n \u0111\u1EE9ng r\u1EDDi b\u00EAn ngo\u00E0i nh\u01B0 m\u1ED9t nh\u00E3n. -->\n <div\n class=\"flex items-center gap-2 rounded border border-default bg-neutral-primary px-2.5 py-1.5 focus-within:border-brand\"\n >\n @if (searchIcon()) {\n <ol-icon [name]=\"searchIcon()\" size=\"sm\" class=\"shrink-0 text-body-subtle\" />\n } @else if (searchPrefix()) {\n <span class=\"shrink-0 text-xs font-medium text-body-subtle\">{{\n searchPrefix()\n }}</span>\n }\n <input\n #oTimPanel\n type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n [attr.aria-controls]=\"listboxId()\"\n autocomplete=\"off\"\n class=\"w-full min-w-0 border-none bg-transparent p-0 text-sm outline-none placeholder:text-body-subtle\"\n />\n </div>\n </div>\n }\n <!--\n `overflow-x-hidden` KHAI R\u00D5: khai `overflow-y-auto` m\u1ED9t m\u00ECnh th\u00EC tr\u00ECnh\n duy\u1EC7t t\u1EF1 t\u00EDnh `overflow-x` th\u00E0nh `auto` (spec CSS: m\u1ED9t tr\u1EE5c `visible`\n c\u00F2n tr\u1EE5c kia kh\u00E1c `visible` l\u00E0 kh\u00F4ng h\u1EE3p l\u1EC7). \u0110o \u0111\u01B0\u1EE3c `scrollWidth\n 765 > 620` khi option c\u00F3 `white-space: nowrap`.\n\n Ch\u1EC9 ch\u1EB7n \u1EDF PANEL CH\u1ECCN c\u00F3 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh \u2014 n\u1ED9i dung ph\u1EA3i v\u1EEBa b\u1EC1 ngang.\n KH\u00D4NG ch\u1EB7n \u1EDF khung ch\u1EE9a n\u1ED9i dung t\u1EF1 do (`device-mockup`,\n `chat-container`, `drawer`): \u1EDF \u0111\u00F3 ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu n\u1ED9i dung b\u1EA5t k\u1EF3\n v\u00E0o, ch\u1EB7n l\u00E0 c\u1EAFt m\u1EA5t ph\u1EA7n r\u1ED9ng c\u1EE7a h\u1ECD.\n -->\n <div\n #listboxRef\n role=\"listbox\"\n [id]=\"listboxId()\"\n [attr.aria-multiselectable]=\"multiple() ? 'true' : null\"\n (scroll)=\"onPanelScroll($event)\"\n class=\"flex-1 min-h-0 overflow-y-auto overflow-x-hidden p-2\"\n >\n <!--\n B\u1ED1n tr\u1EA1ng th\u00E1i c\u1EE7a danh s\u00E1ch l\u1EA5y t\u1EEB m\u00E1y ch\u1EE7 (Figma `ol-dropdown`\n 5266:29107): \u0110ang t\u1EA3i \u00B7 Ch\u01B0a g\u00F5 \u0111\u1EE7 \u00B7 R\u1ED7ng/Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3 \u00B7 c\u00F3 m\u1EE5c.\n \u0110ang t\u1EA3i \u0111\u1EE9ng TR\u01AF\u1EDAC r\u1ED7ng: b\u1EA3n c\u0169 b\u00E1o \"kh\u00F4ng t\u00ECm th\u1EA5y\" trong l\u00FAc\n c\u00E2u tr\u1EA3 l\u1EDDi c\u00F2n ch\u01B0a v\u1EC1 \u2014 sai s\u1EF1 th\u1EADt.\n -->\n @if (loading()) {\n <ol-dropdown-state type=\"loading\" />\n } @else if (waitingForTerm()) {\n <!-- Ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`: \"kh\u00F4ng t\u00ECm th\u1EA5y\" l\u00E0 sai s\u1EF1 th\u1EADt, v\u00EC\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i `typeToSearchText` c\u1EE7a ng-select. -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">{{ typeToSearchText() }}</div>\n } @else if (rows().length === 0) {\n <div class=\"text-center\">\n <ol-dropdown-state\n [type]=\"searchQuery().trim() ? 'no-results' : 'empty'\"\n [query]=\"searchQuery().trim()\"\n [heading]=\"emptyText()\"\n />\n @if (canCreate()) {\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"-mt-2 mb-4 inline-flex items-center gap-1.5 rounded-lg bg-brand-softer px-3 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-subtle\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>{{ createLabel() }}</span>\n </button>\n }\n </div>\n } @else {\n <!-- \u0110\u1EC7m tr\u00EAn: gi\u1EEF thanh cu\u1ED9n d\u00E0i \u0111\u00FAng nh\u01B0 khi v\u1EBD h\u1EBFt danh s\u00E1ch. -->\n @if (viewport().padTop > 0) {\n <div [style.height.px]=\"viewport().padTop\" aria-hidden=\"true\"></div>\n }\n\n @for (row of viewport().rows; track row.kind + '-' + row.index + '-' + row.label) {\n @if (row.kind === 'group') {\n <!-- `dauTien` t\u00EDnh khi d\u1EF1ng danh s\u00E1ch, KH\u00D4NG d\u00F9ng `$first` c\u1EE7a\n `@for`: khi `virtualScroll` b\u1EADt, `$first` l\u00E0 d\u00F2ng \u0111\u1EA7u c\u1EE7a\n khung nh\u00ECn n\u00EAn cu\u1ED9n xu\u1ED1ng l\u00E0 m\u1EA5t \u0111\u01B0\u1EDDng ng\u0103n. -->\n @if (groupSeparator() && !row.isFirstGroup) {\n <div role=\"presentation\" class=\"my-1 border-t border-default-subtle\"></div>\n }\n <div\n role=\"presentation\"\n class=\"px-2.5 pt-2 pb-0 text-2xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ row.label }}\n </div>\n } @else {\n <!--\n `rounded-sm` (6px) ch\u1EE9 kh\u00F4ng `rounded-md` (12px): Figma v\u1EBD\n `ol-autocomplete-item` bo **8px**, m\u00E0 thang token c\u1EE7a lib kh\u00F4ng\n c\u00F3 b\u1EADc 8 (xs 4 \u00B7 sm 6 \u00B7 md/base 12 \u00B7 lg 16). L\u1EA5y b\u1EADc G\u1EA6N NH\u1EA4T\n thay v\u00EC vi\u1EBFt `rounded-[8px]` \u2014 gi\u00E1 tr\u1ECB th\u00F4 n\u1EB1m ngo\u00E0i h\u1EC7 token\n th\u00EC \u0111\u1ED5i theme kh\u00F4ng theo \u0111\u01B0\u1EE3c.\n\n `ol-dropdown-item` c\u0169ng \u0111\u00E3 ch\u1ED1t `rounded-sm` cho m\u1EE5c menu, n\u00EAn\n \u0111\u00E2y c\u00F2n l\u00E0 gi\u1EEF nh\u1EA5t qu\u00E1n trong lib.\n -->\n <!--\n `min-w-0` cho ph\u00E9p h\u00E0ng CO l\u1EA1i, \u0111i\u1EC1u ki\u1EC7n c\u1EA7n \u0111\u1EC3 `truncate`\n c\u1EE7a ng\u01B0\u1EDDi d\u00F9ng ch\u1EA1y. Nh\u01B0ng n\u00F3 KH\u00D4NG t\u1EF1 ch\u1EB7n tr\u00E0n: \u0111o b\u1EB1ng c\u00E1ch\n ti\u00EAm `white-space: nowrap` v\u00E0o h\u00E0ng \u2014 panel v\u1EABn `scrollWidth\n 1763 > 831`. Ch\u1EC9 `overflow: hidden` \u0111\u1EB7t tr\u00EAn CH\u00CDNH ph\u1EA7n t\u1EED n\u1ED9i\n dung m\u1EDBi ch\u1EB7n \u0111\u01B0\u1EE3c, m\u00E0 ph\u1EA7n t\u1EED \u0111\u00F3 do ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu v\u00E0o qua\n `optionTemplate` n\u00EAn lib kh\u00F4ng v\u1EDBi t\u1EDBi. `overflow-x-hidden` \u1EDF\n panel l\u00E0 l\u1EDBp ch\u1EB7n cu\u1ED1i.\n -->\n <div\n role=\"option\"\n [id]=\"optionId(row.index)\"\n [attr.data-option-index]=\"row.index\"\n [attr.aria-selected]=\"isSelected(row.item)\"\n [attr.aria-disabled]=\"row.item?.disabled ? 'true' : null\"\n (click)=\"selectItem(row.item); $event.stopPropagation()\"\n [style.height.px]=\"virtualScroll() ? itemHeight() : null\"\n class=\"flex min-w-0 items-center gap-2 rounded-sm px-3 py-2 text-sm transition-colors\"\n [class.cursor-pointer]=\"!row.item?.disabled\"\n [class.cursor-not-allowed]=\"row.item?.disabled\"\n [class.bg-neutral-tertiary-medium]=\"\n activeIndex() === row.index && !row.item?.disabled\n \"\n [class.hover:bg-neutral-tertiary-medium]=\"\n !row.item?.disabled && activeIndex() !== row.index\n \"\n [class.text-heading]=\"isSelected(row.item) || activeIndex() === row.index\"\n [class.hover:text-heading]=\"!row.item?.disabled\"\n [class.font-medium]=\"isSelected(row.item)\"\n [class.text-body]=\"!isSelected(row.item) && activeIndex() !== row.index\"\n [class.text-fg-disabled]=\"row.item?.disabled\"\n >\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: row.item }\"\n />\n } @else {\n <!-- Ph\u1EA7n \u0111\u1EA7u: `icon` \u01B0u ti\u00EAn, kh\u00F4ng c\u00F3 th\u00EC `prefix` (ch\u1EEF).\n `prefixClass` nh\u1EADn CLASS ch\u1EE9 kh\u00F4ng nh\u1EADn m\u00E3 m\u00E0u \u2014 m\u00E3 m\u00E0u\n c\u1EE9ng kh\u00F4ng \u0111\u1ED5i theo theme v\u00E0 theo ch\u1EBF \u0111\u1ED9 t\u1ED1i. -->\n @if (row.item?.icon) {\n <ol-icon\n [name]=\"row.item.icon\"\n size=\"sm\"\n class=\"shrink-0\"\n [class]=\"row.item.prefixClass || ''\"\n />\n } @else if (row.item?.prefix) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"row.item.prefixClass || 'text-body-subtle'\"\n >{{ row.item.prefix }}</span\n >\n }\n <span class=\"min-w-0 flex-1 truncate\">{{ row.label }}</span>\n }\n @if (isSelected(row.item)) {\n <ol-icon name=\"check\" size=\"sm\" class=\"shrink-0 text-fg-brand\" />\n }\n </div>\n }\n }\n\n <!-- \u0110\u1EC7m d\u01B0\u1EDBi \u2014 xem l\u00FD do \u1EDF \u0111\u1EC7m tr\u00EAn. -->\n @if (viewport().padBottom > 0) {\n <div [style.height.px]=\"viewport().padBottom\" aria-hidden=\"true\"></div>\n }\n\n <!-- Danh s\u00E1ch C\u00D2N m\u1EE5c nh\u01B0ng ch\u1EEF \u0111ang g\u00F5 ch\u01B0a tr\u00F9ng nh\u00E3n n\u00E0o: v\u1EABn ph\u1EA3i\n cho t\u1EA1o. V\u00ED d\u1EE5 \u0111\u00E3 c\u00F3 \"To\u00E1n 10\" m\u00E0 ng\u01B0\u1EDDi d\u00F9ng g\u00F5 \"To\u00E1n 11\" \u2014\n nh\u00E1nh r\u1ED7ng \u1EDF tr\u00EAn kh\u00F4ng ch\u1EA1y v\u00EC b\u1ED9 l\u1ECDc v\u1EABn ra m\u1ED9t m\u1EE5c. -->\n @if (canCreate()) {\n <div class=\"mt-1 border-t border-default-subtle p-2\">\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"inline-flex w-full items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-softer\"\n >\n <ol-icon name=\"plus\" size=\"xs\" class=\"shrink-0\" />\n <span class=\"truncate\">{{ createLabel() }}</span>\n </button>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n\n @if (helperText()) {\n <p\n class=\"mt-1.5 text-xs\"\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger-strong]=\"state() === 'error'\"\n [class.text-fg-success-strong]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlSpinner, selector: "ol-spinner", inputs: ["variant", "size", "color", "ariaLabel"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }, { kind: "component", type: OlDropdownState, selector: "ol-dropdown-state", inputs: ["type", "heading", "description", "query"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
10149
11101
|
}
|
|
10150
11102
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlSelect, decorators: [{
|
|
10151
11103
|
type: Component,
|
|
10152
|
-
args: [{ selector: 'ol-select', standalone: true, imports: [
|
|
11104
|
+
args: [{ selector: 'ol-select', standalone: true, imports: [
|
|
11105
|
+
CommonModule,
|
|
11106
|
+
FormsModule,
|
|
11107
|
+
OlIcon,
|
|
11108
|
+
OlSpinner,
|
|
11109
|
+
OlFieldFrame,
|
|
11110
|
+
OlFieldLabel,
|
|
11111
|
+
OlDropdownState,
|
|
11112
|
+
], host: {
|
|
10153
11113
|
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
10154
11114
|
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG.
|
|
10155
11115
|
class: 'block w-full',
|
|
@@ -10159,14 +11119,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
10159
11119
|
useExisting: forwardRef(() => OlSelect),
|
|
10160
11120
|
multi: true,
|
|
10161
11121
|
},
|
|
10162
|
-
], template: "<div class=\"w-full\">\n @if (label()) {\n <div class=\"flex items-center justify-between mb-1.5\">\n <label\n [id]=\"labelId()\"\n [for]=\"inputId()\"\n class=\"inline-flex items-center gap-1 text-sm font-medium text-heading\"\n >\n <span>{{ label() }}</span>\n @if (required() || showAsterisk()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n </div>\n }\n\n <div class=\"relative w-full\">\n @if (leftIcon()) {\n <div\n class=\"absolute inset-y-0 inset-s-0 z-10 flex items-center ps-3.5 pointer-events-none text-body-subtle\"\n >\n <ng-content select=\"[leftIcon]\" />\n </div>\n }\n\n <!--\n \u00D4 \u0110I\u1EC0U KHI\u1EC2N \u2014 gi\u1EEF NGUY\u00CAN b\u1EA3ng class c\u1EE7a b\u1EA3n `<select>` c\u0169 \u0111\u1EC3 giao di\u1EC7n\n Figma kh\u00F4ng \u0111\u1ED5i m\u1ED9t pixel: c\u00F9ng vi\u1EC1n, n\u1EC1n, bo g\u00F3c, thang c\u1EE1 (h-9/h-10/h-12)\n v\u00E0 c\u00F9ng b\u1EA3ng m\u00E0u theo `state`.\n\n `dark:focus:ring-brand-950` GI\u1EEE b\u1EADc s\u1ED1: `brand` l\u00E0 b\u1EA3ng Figma C\u00D3 n\u00EAn b\u1EADc\n s\u1ED1 h\u1EE3p l\u1EC7, c\u00F2n `ring-brand-softer` (token duy nh\u1EA5t tr\u1ECF brand-950 \u1EDF ch\u1EBF \u0111\u1ED9\n t\u1ED1i) ch\u01B0a n\u01A1i n\u00E0o d\u00F9ng n\u00EAn Tailwind kh\u00F4ng sinh CSS \u2014 thay v\u00E0o l\u00E0 class\n CH\u1EBET m\u00E0 build v\u1EABn xanh (\u0111o 2026-09-15).\n\n Kh\u00E1c b\u1EA3n c\u0169 \u1EDF ch\u1ED7 \u0111\u00E2y l\u00E0 `<div role=\"combobox\">` ch\u1EE9 kh\u00F4ng ph\u1EA3i `<select>`\n g\u1ED1c: th\u1EBB g\u1ED1c kh\u00F4ng cho t\u00ECm ki\u1EBFm, \u0111a ch\u1ECDn hay t\u1EF1 v\u1EBD t\u1EEBng d\u00F2ng.\n -->\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? labelId() : triggerLabel() ? triggerLabelId() : null\"\n [attr.aria-label]=\"label() || triggerLabel() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n aria-haspopup=\"listbox\"\n (click)=\"toggle()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"relative flex w-full items-center gap-1.5 border shadow-xs outline-none pe-10 font-normal\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.rounded-base]=\"true\"\n [class]=\"paddingStartClass()\"\n [class.flex-wrap]=\"multiple()\"\n [class.content-center]=\"multiple()\"\n [class.py-1]=\"multiple()\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.text-sm]=\"resolvedSize() === 'sm' || resolvedSize() === 'base'\"\n [class.text-base]=\"resolvedSize() === 'lg' || resolvedSize() === 'xl'\"\n [class.cursor-pointer]=\"!disabled() && !readonly()\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled()\"\n [class.text-heading]=\"state() === 'default' && !disabled()\"\n [class.focus:border-brand]=\"state() === 'default' && !disabled()\"\n [class.focus:ring-2]=\"!disabled()\"\n [class.focus:ring-brand-subtle]=\"state() === 'default' && !disabled()\"\n [class.dark:focus:ring-brand-950]=\"state() === 'default' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.focus:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.focus:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n >\n <!-- Th\u1EBB c\u1EE7a c\u00E1c gi\u00E1 tr\u1ECB \u0111\u00E3 ch\u1ECDn (\u0111a ch\u1ECDn) -->\n @if (multiple()) {\n @for (val of selectedValues(); track val) {\n <!-- `h-6` c\u1ED1 \u0111\u1ECBnh: \u0111\u1EC3 th\u1EBB co theo ch\u1EEF th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t ki\u1EC3u v\u00E0 \u00F4\n b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. C\u00F9ng c\u00E1ch \u0111\u00E3 ch\u1ED1t \u1EDF `ol-autocomplete`. -->\n <!-- Chip kho\u00E1 d\u00F9ng b\u1EA3ng m\u00E0u TRUNG T\u00CDNH: m\u1EA5t n\u00FAt \u00D7 l\u00E0 d\u1EA5u hi\u1EC7u qu\u00E1\n nh\u1EB9, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng n\u00FAt ch\u01B0a hi\u1EC7n. \u0110\u1ED5i m\u00E0u cho th\u1EA5y ngay \u0111\u00E2y\n l\u00E0 lo\u1EA1i chip kh\u00E1c. -->\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md border px-2 text-xs font-medium\"\n [class.border-brand-subtle]=\"!isLocked(val)\"\n [class.bg-brand-softer]=\"!isLocked(val)\"\n [class.text-fg-brand-strong]=\"!isLocked(val)\"\n [class.border-default]=\"isLocked(val)\"\n [class.bg-neutral-tertiary]=\"isLocked(val)\"\n [class.text-body]=\"isLocked(val)\"\n >\n @if (isLocked(val)) {\n <ol-icon name=\"lucide:lock\" size=\"xs\" class=\"shrink-0 opacity-70\" />\n } @else if (optionOf(val)?.icon) {\n <ol-icon [name]=\"$any(optionOf(val)).icon\" size=\"xs\" class=\"shrink-0\" />\n } @else if (optionOf(val)?.prefix) {\n <span class=\"shrink-0 text-2xs font-semibold tabular-nums\">{{\n optionOf(val)?.prefix\n }}</span>\n }\n <span>{{ getDisplayLabel(val) }}</span>\n @if (!disabled() && !readonly() && !isLocked(val)) {\n <button\n type=\"button\"\n (click)=\"removeValue(val, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabel(val)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!--\n PH\u1EA6N C\u1ED0 \u0110\u1ECANH c\u1EE7a \u00F4 \u0111\u00F3ng \u2014 icon v\u00E0 nh\u00E3n LU\u00D4N hi\u1EC7n, d\u00F9 \u0111\u00E3 ch\u1ECDn hay ch\u01B0a,\n d\u00F9 danh s\u00E1ch \u0111ang m\u1EDF hay \u0111\u00F3ng. Ch\u1EC9 ph\u1EA7n GI\u00C1 TR\u1ECA b\u00EAn ph\u1EA3i m\u1EDBi \u0111\u1ED5i.\n\n \u0110\u1EB7t NGO\u00C0I c\u1EA3 ba nh\u00E1nh d\u01B0\u1EDBi (\u00F4 t\u00ECm / nh\u00E3n \u0111\u00E3 ch\u1ECDn / placeholder) ch\u00EDnh\n l\u00E0 \u0111i\u1EC1u l\u00E0m n\u00F3 c\u1ED1 \u0111\u1ECBnh \u2014 n\u1EB1m trong nh\u00E1nh n\u00E0o th\u00EC n\u00F3 bi\u1EBFn m\u1EA5t \u1EDF hai\n nh\u00E1nh kia.\n\n Kh\u00E1c `icon` c\u1EE7a M\u1EE4C: c\u00E1i \u0111\u00F3 \u0111\u1ED5i theo m\u1EE5c \u0111ang ch\u1ECDn. Khai c\u1EA3 hai th\u00EC\n `triggerIcon` th\u1EAFng, v\u00EC nh\u00E3n c\u1ED1 \u0111\u1ECBnh m\u00E0 nh\u1EA3y theo m\u1EE5c th\u00EC h\u1EBFt c\u1ED1 \u0111\u1ECBnh.\n -->\n @if (triggerIcon()) {\n <ol-icon [name]=\"triggerIcon()\" size=\"sm\" class=\"shrink-0 text-body-subtle\" />\n }\n @if (triggerLabel()) {\n <span [id]=\"triggerLabelId()\" class=\"shrink-0 text-sm font-medium text-body\">{{\n triggerLabel()\n }}</span>\n }\n\n <!--\n \u00D4 G\u00D5 T\u00CCM ch\u1EC9 hi\u1EC7n khi danh s\u00E1ch \u0110ANG M\u1EDE. \u0110\u00F3ng l\u1EA1i th\u00EC hi\u1EC7n nh\u00E3n t\u0129nh \u2014\n n\u1EBFu \u0111\u1EC3 \u00F4 nh\u1EADp lu\u00F4n \u0111\u00F3, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng g\u00F5 th\u1EB3ng v\u00E0o \u0111\u01B0\u1EE3c, m\u00E0 gi\u00E1 tr\u1ECB\n l\u1EA1i kh\u00F4ng \u0111\u1ED5i theo ch\u1EEF h\u1ECD g\u00F5.\n -->\n @if (searchable() && isOpen() && searchPosition() === 'inline') {\n <input\n type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n autocomplete=\"off\"\n class=\"min-w-0 flex-1 border-none bg-transparent p-0 text-inherit outline-none placeholder:text-body-subtle\"\n />\n } @else if (!multiple() && hasValue()) {\n @if (labelTemplate) {\n <ng-container\n *ngTemplateOutlet=\"labelTemplate; context: { $implicit: selectedValues()[0] }\"\n />\n } @else {\n <!-- \u00D4 \u0111\u00F3ng hi\u1EC7n C\u1EA2 ph\u1EA7n \u0111\u1EA7u c\u1EE7a m\u1EE5c \u0111ang ch\u1ECDn, kh\u00F4ng ch\u1EC9 ch\u1EEF \u2014 \u0111\u00E2y\n l\u00E0 \u0111i\u1EC3m kh\u00E1c c\u1EE7a combobox reui so v\u1EDBi \u00F4 ch\u1ECDn th\u01B0\u1EDDng. -->\n @if (selectedItem()?.icon && !triggerIcon()) {\n <ol-icon\n [name]=\"$any(selectedItem()).icon\"\n size=\"sm\"\n class=\"shrink-0\"\n [class]=\"selectedItem()?.prefixClass || ''\"\n />\n } @else if (selectedItem()?.prefix && !triggerIcon()) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"selectedItem()?.prefixClass || 'text-body-subtle'\"\n >{{ selectedItem()?.prefix }}</span\n >\n }\n <span class=\"truncate\">{{ singleLabel() }}</span>\n }\n } @else if (!hasValue()) {\n <!--\n \u1EDE tr\u1EA1ng th\u00E1i L\u1ED6I, n\u1EC1n \u0111\u1ED5i sang `bg-danger-soft` (#fef0f2) v\u00E0\n `text-body-subtle` ch\u1EC9 c\u00F2n **4,36** \u2014 d\u01B0\u1EDBi chu\u1EA9n 4,5. N\u1EC1n `success`\n (#ecfdf5) cho 4,59, v\u1EEBa \u0111\u1EE7, n\u00EAn ch\u1EC9 nh\u00E1nh l\u1ED7i c\u1EA7n \u0111\u1EADm h\u01A1n.\n `text-body` tr\u00EAn n\u1EC1n \u0111\u00F3 ra 6,82 (s\u00E1ng) v\u00E0 10,66 (t\u1ED1i).\n -->\n <span\n class=\"truncate\"\n [class.text-body-subtle]=\"state() !== 'error'\"\n [class.text-body]=\"state() === 'error'\"\n >{{ placeholder() }}</span\n >\n }\n\n <!--\n M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0110\u1ED4I VAI theo tr\u1EA1ng th\u00E1i \u2014 kh\u00F4ng x\u1EBFp c\u1EA1nh nhau.\n B\u1EA3n \u0111\u1EA7u c\u1EE7a t\u00F4i \u0111\u1EC3 n\u00FAt xo\u00E1 v\u00E0 `chevron-down` C\u00D9NG hi\u1EC7n: \u0111o 2026-09-17\n tr\u00EAn `/forms` \u0111\u1EBFm **2 icon b\u00EAn ph\u1EA3i \u1EDF M\u1ECCI tr\u1EA1ng th\u00E1i**, k\u1EC3 c\u1EA3 khi danh\n s\u00E1ch \u0111ang m\u1EDF. C\u1EE5m b\u00EAn ph\u1EA3i ch\u1EADt, hai icon 16px chen nhau tr\u00F4ng r\u1ED1i.\n\n `ol-autocomplete` \u0111\u00E3 ch\u1ED1t l\u1ED1i n\u00E0y t\u1EEB tr\u01B0\u1EDBc (\u00F4 `w-7` \u0111\u1ED5i vai\n spinner / xo\u00E1 / chevron); `ol-select` ph\u1EA3i theo cho nh\u1EA5t qu\u00E1n.\n\n \u00D4 r\u1ED9ng C\u1ED0 \u0110\u1ECANH \u0111\u1EC3 khung kh\u00F4ng nh\u1EA3y m\u1ED7i l\u1EA7n icon \u0111\u1ED5i vai.\n\n \u0110\u1EC6M PH\u1EA2I l\u00E0 B\u1EAET BU\u1ED8C, kh\u00F4ng ph\u1EA3i trang tr\u00ED: c\u1EE5m n\u00E0y `absolute` n\u00EAn BUNG\n KH\u1ECEI LU\u1ED2NG, kh\u00F4ng h\u01B0\u1EDFng `px-3.5` c\u1EE7a khung nh\u01B0 `ol-autocomplete` (c\u00E1i\n \u0111\u00F3 d\u00F9ng `ms-auto`, v\u1EABn n\u1EB1m trong lu\u1ED3ng). B\u1EA3n \u0111\u1EA7u ch\u1EC9 c\u00F3 `w-7` \u2014 \u0111o\n 2026-09-17 ra icon c\u00E1ch m\u00E9p ph\u1EA3i **3px**, d\u00EDnh s\u00E1t l\u1EC1.\n\n Figma `ol-select` Type=Default: icon `angle-down` c\u00E1ch m\u00E9p ph\u1EA3i **12px**.\n Con s\u1ED1 `pe-2` (8px) tr\u00F4ng c\u00F3 v\u1EBB h\u1EE5t, nh\u01B0ng n\u00FAt xo\u00E1 l\u00E0 `size-6` (24px)\n b\u1ECDc icon 16px n\u00EAn d\u01B0 4px m\u1ED7i b\u00EAn \u2014 8 + 4 = 12px t\u1EDBi SVG th\u1EADt. \u0110o b\u1EB1ng\n m\u00E9p SVG ch\u1EE9 kh\u00F4ng m\u00E9p n\u00FAt, v\u00EC m\u1EAFt ng\u01B0\u1EDDi nh\u00ECn th\u1EA5y SVG.\n\n `justify-end` ch\u1EE9 kh\u00F4ng `justify-center`: c\u0103n gi\u1EEFa th\u00EC icon tr\u00F4i theo\n b\u1EC1 r\u1ED9ng \u00F4, \u0111\u1ED5i `w-*` l\u00E0 l\u1EC7ch l\u1EA1i.\n -->\n <div class=\"absolute inset-y-0 inset-e-0 flex w-10 items-center justify-end pe-3.5\">\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (clearable() && hasContent() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"pointer-events-none shrink-0 text-body-subtle transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n\n <!--\n DANH S\u00C1CH. Token \u0111\u1ECDc th\u1EB3ng t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147),\n bi\u1EBFn th\u1EC3 `Type=Default` \u2014 \u0111\u00F3 l\u00E0 dropdown chu\u1EA9n c\u1EE7a design system:\n\n bo g\u00F3c 12px -> rounded-md\n \u0111\u1EC7m 8px -> p-2 (tr\u01B0\u1EDBc l\u00E0 p-1.5 = 6px)\n n\u1EC1n bg-neutral-primary-medium (tr\u01B0\u1EDBc l\u00E0 bg-neutral-primary)\n vi\u1EC1n border-default-medium (tr\u01B0\u1EDBc l\u00E0 border-default)\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng (#ffffff / #e5e7eb) n\u00EAn \u0111\u1ED5i\n hay kh\u00F4ng nh\u00ECn c\u0169ng th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I th\u00EC kh\u00E1c h\u1EB3n:\n\n bg-primary gray-950 \u2190 \u0111ang d\u00F9ng, g\u1EA7n \u0111en, panel CH\u00CCM v\u00E0o n\u1EC1n\n bg-primary-medium gray-800 \u2190 Figma, panel n\u1ED5i l\u00EAn\n border-default gray-800\n border-default-medium gray-700\n\n \u0110\u00E2y \u0111\u00FAng lo\u1EA1i l\u1ED7i ch\u1EC9 l\u1ED9 \u1EDF ch\u1EBF \u0111\u1ED9 t\u1ED1i m\u00E0 ch\u1EBF \u0111\u1ED9 s\u00E1ng kh\u00F4ng bao gi\u1EDD th\u1EA5y.\n -->\n @if (isOpen()) {\n <!--\n \u00D4 T\u00CCM \u1EDE \u0110\u1EA6U PANEL (`searchPosition=\"panel\"`).\n \u0110\u1EB7t NGO\u00C0I th\u1EBB `role=\"listbox\"`: m\u1ED9t `<input>` n\u1EB1m trong listbox l\u00E0 sai\n ARIA \u2014 m\u1ECDi con c\u1EE7a listbox ph\u1EA3i l\u00E0 `option` ho\u1EB7c `group`. \u0110\u00E2y l\u00E0 l\u1ED1i\n `ol-command` v\u00E0 combobox c\u1EE7a reui d\u00F9ng.\n -->\n <!--\n H\u1ED8P B\u1ECCC mang `#panel` v\u00E0 `position: absolute`.\n\n `#panel` ph\u1EA3i \u1EDF \u0110\u00C2Y ch\u1EE9 kh\u00F4ng \u1EDF th\u1EBB `role=\"listbox\"`: `appendTo=\"body\"`\n \u0111\u1EA9y ch\u00EDnh ph\u1EA7n t\u1EED `#panel` ra `<body>`. \u0110\u1EC3 n\u00F3 \u1EDF listbox th\u00EC \u00F4 t\u00ECm \u1EDF l\u1EA1i\n trong c\u00E2y DOM c\u0169 c\u00F2n danh s\u00E1ch bay \u0111i \u2014 h\u1ED9p tho\u1EA1i v\u1EE1 l\u00E0m \u0111\u00F4i.\n -->\n <div\n #panel\n class=\"absolute z-dropdown overflow-hidden rounded-md border border-default-medium bg-neutral-primary-medium shadow-lg flex flex-col max-h-[var(--ol-overlay-max-height,24rem)]\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (searchable() && searchPosition() === 'panel') {\n <div class=\"border-b border-default-subtle p-2\">\n <!-- Icon/ch\u1EEF \u0111\u1EE9ng \u0111\u1EA7u \u00F4 T\u00CCM \u2014 c\u1EE7a vi\u1EC7c T\u00CCM, kh\u00F4ng ph\u1EA3i c\u1EE7a m\u1EE5c\n \u0111ang ch\u1ECDn. B\u1ECDc trong khung c\u00F3 vi\u1EC1n \u0111\u1EC3 icon n\u1EB1m TRONG \u00F4, kh\u00F4ng\n \u0111\u1EE9ng r\u1EDDi b\u00EAn ngo\u00E0i nh\u01B0 m\u1ED9t nh\u00E3n. -->\n <div\n class=\"flex items-center gap-2 rounded border border-default bg-neutral-primary px-2.5 py-1.5 focus-within:border-brand\"\n >\n @if (searchIcon()) {\n <ol-icon [name]=\"searchIcon()\" size=\"sm\" class=\"shrink-0 text-body-subtle\" />\n } @else if (searchPrefix()) {\n <span class=\"shrink-0 text-xs font-medium text-body-subtle\">{{\n searchPrefix()\n }}</span>\n }\n <input\n #oTimPanel\n type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n [attr.aria-controls]=\"listboxId()\"\n autocomplete=\"off\"\n class=\"w-full min-w-0 border-none bg-transparent p-0 text-sm outline-none placeholder:text-body-subtle\"\n />\n </div>\n </div>\n }\n <!--\n `overflow-x-hidden` KHAI R\u00D5: khai `overflow-y-auto` m\u1ED9t m\u00ECnh th\u00EC tr\u00ECnh\n duy\u1EC7t t\u1EF1 t\u00EDnh `overflow-x` th\u00E0nh `auto` (spec CSS: m\u1ED9t tr\u1EE5c `visible`\n c\u00F2n tr\u1EE5c kia kh\u00E1c `visible` l\u00E0 kh\u00F4ng h\u1EE3p l\u1EC7). \u0110o \u0111\u01B0\u1EE3c `scrollWidth\n 765 > 620` khi option c\u00F3 `white-space: nowrap`.\n\n Ch\u1EC9 ch\u1EB7n \u1EDF PANEL CH\u1ECCN c\u00F3 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh \u2014 n\u1ED9i dung ph\u1EA3i v\u1EEBa b\u1EC1 ngang.\n KH\u00D4NG ch\u1EB7n \u1EDF khung ch\u1EE9a n\u1ED9i dung t\u1EF1 do (`device-mockup`,\n `chat-container`, `drawer`): \u1EDF \u0111\u00F3 ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu n\u1ED9i dung b\u1EA5t k\u1EF3\n v\u00E0o, ch\u1EB7n l\u00E0 c\u1EAFt m\u1EA5t ph\u1EA7n r\u1ED9ng c\u1EE7a h\u1ECD.\n -->\n <div\n #listboxRef\n role=\"listbox\"\n [id]=\"listboxId()\"\n [attr.aria-multiselectable]=\"multiple() ? 'true' : null\"\n (scroll)=\"onPanelScroll($event)\"\n class=\"flex-1 min-h-0 overflow-y-auto overflow-x-hidden p-2\"\n >\n @if (waitingForTerm()) {\n <!-- Ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`: \"kh\u00F4ng t\u00ECm th\u1EA5y\" l\u00E0 sai s\u1EF1 th\u1EADt, v\u00EC\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i `typeToSearchText` c\u1EE7a ng-select. -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">{{ typeToSearchText() }}</div>\n } @else if (rows().length === 0) {\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ emptyText() }}</p>\n @if (canCreate()) {\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"mt-2 inline-flex items-center gap-1.5 rounded-lg bg-brand-softer px-3 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-subtle\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>{{ createLabel() }}</span>\n </button>\n }\n </div>\n } @else {\n <!-- \u0110\u1EC7m tr\u00EAn: gi\u1EEF thanh cu\u1ED9n d\u00E0i \u0111\u00FAng nh\u01B0 khi v\u1EBD h\u1EBFt danh s\u00E1ch. -->\n @if (viewport().padTop > 0) {\n <div [style.height.px]=\"viewport().padTop\" aria-hidden=\"true\"></div>\n }\n\n @for (row of viewport().rows; track row.kind + '-' + row.index + '-' + row.label) {\n @if (row.kind === 'group') {\n <!-- `dauTien` t\u00EDnh khi d\u1EF1ng danh s\u00E1ch, KH\u00D4NG d\u00F9ng `$first` c\u1EE7a\n `@for`: khi `virtualScroll` b\u1EADt, `$first` l\u00E0 d\u00F2ng \u0111\u1EA7u c\u1EE7a\n khung nh\u00ECn n\u00EAn cu\u1ED9n xu\u1ED1ng l\u00E0 m\u1EA5t \u0111\u01B0\u1EDDng ng\u0103n. -->\n @if (groupSeparator() && !row.isFirstGroup) {\n <div role=\"presentation\" class=\"my-1 border-t border-default-subtle\"></div>\n }\n <div\n role=\"presentation\"\n class=\"px-2.5 pt-2 pb-0 text-2xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ row.label }}\n </div>\n } @else {\n <!--\n `rounded-sm` (6px) ch\u1EE9 kh\u00F4ng `rounded-md` (12px): Figma v\u1EBD\n `ol-autocomplete-item` bo **8px**, m\u00E0 thang token c\u1EE7a lib kh\u00F4ng\n c\u00F3 b\u1EADc 8 (xs 4 \u00B7 sm 6 \u00B7 md/base 12 \u00B7 lg 16). L\u1EA5y b\u1EADc G\u1EA6N NH\u1EA4T\n thay v\u00EC vi\u1EBFt `rounded-[8px]` \u2014 gi\u00E1 tr\u1ECB th\u00F4 n\u1EB1m ngo\u00E0i h\u1EC7 token\n th\u00EC \u0111\u1ED5i theme kh\u00F4ng theo \u0111\u01B0\u1EE3c.\n\n `ol-dropdown-item` c\u0169ng \u0111\u00E3 ch\u1ED1t `rounded-sm` cho m\u1EE5c menu, n\u00EAn\n \u0111\u00E2y c\u00F2n l\u00E0 gi\u1EEF nh\u1EA5t qu\u00E1n trong lib.\n -->\n <!--\n `min-w-0` cho ph\u00E9p h\u00E0ng CO l\u1EA1i, \u0111i\u1EC1u ki\u1EC7n c\u1EA7n \u0111\u1EC3 `truncate`\n c\u1EE7a ng\u01B0\u1EDDi d\u00F9ng ch\u1EA1y. Nh\u01B0ng n\u00F3 KH\u00D4NG t\u1EF1 ch\u1EB7n tr\u00E0n: \u0111o b\u1EB1ng c\u00E1ch\n ti\u00EAm `white-space: nowrap` v\u00E0o h\u00E0ng \u2014 panel v\u1EABn `scrollWidth\n 1763 > 831`. Ch\u1EC9 `overflow: hidden` \u0111\u1EB7t tr\u00EAn CH\u00CDNH ph\u1EA7n t\u1EED n\u1ED9i\n dung m\u1EDBi ch\u1EB7n \u0111\u01B0\u1EE3c, m\u00E0 ph\u1EA7n t\u1EED \u0111\u00F3 do ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu v\u00E0o qua\n `optionTemplate` n\u00EAn lib kh\u00F4ng v\u1EDBi t\u1EDBi. `overflow-x-hidden` \u1EDF\n panel l\u00E0 l\u1EDBp ch\u1EB7n cu\u1ED1i.\n -->\n <div\n role=\"option\"\n [id]=\"optionId(row.index)\"\n [attr.data-option-index]=\"row.index\"\n [attr.aria-selected]=\"isSelected(row.item)\"\n [attr.aria-disabled]=\"row.item?.disabled ? 'true' : null\"\n (click)=\"selectItem(row.item); $event.stopPropagation()\"\n [style.height.px]=\"virtualScroll() ? itemHeight() : null\"\n class=\"flex min-w-0 items-center gap-2 rounded-sm px-3 py-2 text-sm transition-colors\"\n [class.cursor-pointer]=\"!row.item?.disabled\"\n [class.cursor-not-allowed]=\"row.item?.disabled\"\n [class.bg-neutral-tertiary-medium]=\"\n activeIndex() === row.index && !row.item?.disabled\n \"\n [class.hover:bg-neutral-tertiary-medium]=\"\n !row.item?.disabled && activeIndex() !== row.index\n \"\n [class.text-heading]=\"isSelected(row.item) || activeIndex() === row.index\"\n [class.hover:text-heading]=\"!row.item?.disabled\"\n [class.font-medium]=\"isSelected(row.item)\"\n [class.text-body]=\"!isSelected(row.item) && activeIndex() !== row.index\"\n [class.text-fg-disabled]=\"row.item?.disabled\"\n >\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: row.item }\"\n />\n } @else {\n <!-- Ph\u1EA7n \u0111\u1EA7u: `icon` \u01B0u ti\u00EAn, kh\u00F4ng c\u00F3 th\u00EC `prefix` (ch\u1EEF).\n `prefixClass` nh\u1EADn CLASS ch\u1EE9 kh\u00F4ng nh\u1EADn m\u00E3 m\u00E0u \u2014 m\u00E3 m\u00E0u\n c\u1EE9ng kh\u00F4ng \u0111\u1ED5i theo theme v\u00E0 theo ch\u1EBF \u0111\u1ED9 t\u1ED1i. -->\n @if (row.item?.icon) {\n <ol-icon\n [name]=\"row.item.icon\"\n size=\"sm\"\n class=\"shrink-0\"\n [class]=\"row.item.prefixClass || ''\"\n />\n } @else if (row.item?.prefix) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"row.item.prefixClass || 'text-body-subtle'\"\n >{{ row.item.prefix }}</span\n >\n }\n <span class=\"min-w-0 flex-1 truncate\">{{ row.label }}</span>\n }\n @if (isSelected(row.item)) {\n <ol-icon name=\"check\" size=\"sm\" class=\"shrink-0 text-fg-brand\" />\n }\n </div>\n }\n }\n\n <!-- \u0110\u1EC7m d\u01B0\u1EDBi \u2014 xem l\u00FD do \u1EDF \u0111\u1EC7m tr\u00EAn. -->\n @if (viewport().padBottom > 0) {\n <div [style.height.px]=\"viewport().padBottom\" aria-hidden=\"true\"></div>\n }\n\n <!-- Danh s\u00E1ch C\u00D2N m\u1EE5c nh\u01B0ng ch\u1EEF \u0111ang g\u00F5 ch\u01B0a tr\u00F9ng nh\u00E3n n\u00E0o: v\u1EABn ph\u1EA3i\n cho t\u1EA1o. V\u00ED d\u1EE5 \u0111\u00E3 c\u00F3 \"To\u00E1n 10\" m\u00E0 ng\u01B0\u1EDDi d\u00F9ng g\u00F5 \"To\u00E1n 11\" \u2014\n nh\u00E1nh r\u1ED7ng \u1EDF tr\u00EAn kh\u00F4ng ch\u1EA1y v\u00EC b\u1ED9 l\u1ECDc v\u1EABn ra m\u1ED9t m\u1EE5c. -->\n @if (canCreate()) {\n <div class=\"mt-1 border-t border-default-subtle p-2\">\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"inline-flex w-full items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-softer\"\n >\n <ol-icon name=\"plus\" size=\"xs\" class=\"shrink-0\" />\n <span class=\"truncate\">{{ createLabel() }}</span>\n </button>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n\n @if (helperText()) {\n <p\n class=\"mt-1.5 text-xs\"\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger-strong]=\"state() === 'error'\"\n [class.text-fg-success-strong]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n</div>\n" }]
|
|
10163
|
-
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], leftIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftIcon", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], menuClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuClass", required: false }] }], matchTriggerWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchTriggerWidth", required: false }] }], maxSelectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedItems", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], hideSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSelected", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], createText: [{ type: i0.Input, args: [{ isSignal: true, alias: "createText", required: false }] }], groupSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupSeparator", required: false }] }], searchPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPosition", required: false }] }], searchIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchIcon", required: false }] }], searchPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPrefix", required: false }] }], triggerIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerIcon", required: false }] }], triggerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerLabel", required: false }] }], remoteSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteSearch", required: false }] }], minTermLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTermLength", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], typeToSearchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeToSearchText", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], bufferAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "bufferAmount", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], optionTemplate: [{
|
|
11122
|
+
], template: "<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"resolvedSize()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n\n <div class=\"relative w-full\">\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"resolvedSize()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"hasValue()\"\n [open]=\"isOpen()\"\n >\n <!-- Icon tr\u00E1i neo theo KHUNG NH\u1EACP, kh\u00F4ng theo khung n\u1ED5i (c\u00F3 h\u00E0ng nh\u00E3n 20px). -->\n <div class=\"relative\">\n @if (leftIcon()) {\n <div\n class=\"absolute inset-y-0 inset-s-0 z-10 flex items-center pointer-events-none text-body-subtle\"\n [class]=\"leftIconPadClass()\"\n >\n <ng-content select=\"[leftIcon]\" />\n </div>\n }\n\n <!--\n \u00D4 \u0110I\u1EC0U KHI\u1EC2N \u2014 gi\u1EEF NGUY\u00CAN b\u1EA3ng class c\u1EE7a b\u1EA3n `<select>` c\u0169 \u0111\u1EC3 giao di\u1EC7n\n Figma kh\u00F4ng \u0111\u1ED5i m\u1ED9t pixel: c\u00F9ng vi\u1EC1n, n\u1EC1n, bo g\u00F3c, thang c\u1EE1 (h-9/h-10/h-12)\n v\u00E0 c\u00F9ng b\u1EA3ng m\u00E0u theo `state`.\n\n `dark:focus:ring-brand-950` GI\u1EEE b\u1EADc s\u1ED1: `brand` l\u00E0 b\u1EA3ng Figma C\u00D3 n\u00EAn b\u1EADc\n s\u1ED1 h\u1EE3p l\u1EC7, c\u00F2n `ring-brand-softer` (token duy nh\u1EA5t tr\u1ECF brand-950 \u1EDF ch\u1EBF \u0111\u1ED9\n t\u1ED1i) ch\u01B0a n\u01A1i n\u00E0o d\u00F9ng n\u00EAn Tailwind kh\u00F4ng sinh CSS \u2014 thay v\u00E0o l\u00E0 class\n CH\u1EBET m\u00E0 build v\u1EABn xanh (\u0111o 2026-09-15).\n\n Kh\u00E1c b\u1EA3n c\u0169 \u1EDF ch\u1ED7 \u0111\u00E2y l\u00E0 `<div role=\"combobox\">` ch\u1EE9 kh\u00F4ng ph\u1EA3i `<select>`\n g\u1ED1c: th\u1EBB g\u1ED1c kh\u00F4ng cho t\u00ECm ki\u1EBFm, \u0111a ch\u1ECDn hay t\u1EF1 v\u1EBD t\u1EEBng d\u00F2ng.\n -->\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n tabindex=\"0\"\n [attr.aria-labelledby]=\"label() ? labelId() : triggerLabel() ? triggerLabelId() : null\"\n [attr.aria-label]=\"label() || triggerLabel() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-disabled]=\"disabled() ? 'true' : null\"\n aria-haspopup=\"listbox\"\n (click)=\"toggle()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"ol-field-box relative flex w-full items-center gap-1.5 border shadow-xs outline-none font-normal\"\n [class.border-default-medium]=\"state() === 'default' || disabled()\"\n [class.rounded-base]=\"true\"\n [class]=\"paddingStartClass()\"\n [class.flex-wrap]=\"multiple()\"\n [class.content-center]=\"multiple()\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.text-sm]=\"resolvedSize() === 'sm' || resolvedSize() === 'base'\"\n [class.text-base]=\"resolvedSize() === 'lg' || resolvedSize() === 'xl'\"\n [class.cursor-pointer]=\"!disabled() && !readonly()\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled()\"\n [class.text-heading]=\"state() === 'default' && !disabled()\"\n [class.focus:border-brand]=\"state() === 'default' && !disabled()\"\n [class.focus:ring-2]=\"!disabled()\"\n [class.focus:ring-brand-subtle]=\"state() === 'default' && !disabled()\"\n [class.dark:focus:ring-brand-950]=\"state() === 'default' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.focus:border-danger]=\"state() === 'error' && !disabled()\"\n [class.focus:ring-danger-medium]=\"state() === 'error' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.focus:border-success]=\"state() === 'success' && !disabled()\"\n [class.focus:ring-success-medium]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled()\"\n [class.text-fg-disabled]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n >\n <!-- Th\u1EBB c\u1EE7a c\u00E1c gi\u00E1 tr\u1ECB \u0111\u00E3 ch\u1ECDn (\u0111a ch\u1ECDn) -->\n @if (multiple()) {\n @for (val of selectedValues(); track val) {\n <!-- `h-6` c\u1ED1 \u0111\u1ECBnh: \u0111\u1EC3 th\u1EBB co theo ch\u1EEF th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t ki\u1EC3u v\u00E0 \u00F4\n b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. C\u00F9ng c\u00E1ch \u0111\u00E3 ch\u1ED1t \u1EDF `ol-autocomplete`. -->\n <!-- Chip kho\u00E1 d\u00F9ng b\u1EA3ng m\u00E0u TRUNG T\u00CDNH: m\u1EA5t n\u00FAt \u00D7 l\u00E0 d\u1EA5u hi\u1EC7u qu\u00E1\n nh\u1EB9, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng n\u00FAt ch\u01B0a hi\u1EC7n. \u0110\u1ED5i m\u00E0u cho th\u1EA5y ngay \u0111\u00E2y\n l\u00E0 lo\u1EA1i chip kh\u00E1c. -->\n <span\n class=\"inline-flex h-6 shrink-0 items-center gap-1 rounded-md border px-2 text-xs font-medium\"\n [class.border-brand-subtle]=\"!isLocked(val)\"\n [class.bg-brand-softer]=\"!isLocked(val)\"\n [class.text-fg-brand-strong]=\"!isLocked(val)\"\n [class.border-default]=\"isLocked(val)\"\n [class.bg-neutral-tertiary]=\"isLocked(val)\"\n [class.text-body]=\"isLocked(val)\"\n >\n @if (isLocked(val)) {\n <ol-icon name=\"lucide:lock\" size=\"xs\" class=\"shrink-0 opacity-70\" />\n } @else if (optionOf(val)?.icon) {\n <ol-icon [name]=\"$any(optionOf(val)).icon\" size=\"xs\" class=\"shrink-0\" />\n } @else if (optionOf(val)?.prefix) {\n <span class=\"shrink-0 text-2xs font-semibold tabular-nums\">{{\n optionOf(val)?.prefix\n }}</span>\n }\n <span>{{ getDisplayLabel(val) }}</span>\n @if (!disabled() && !readonly() && !isLocked(val)) {\n <button\n type=\"button\"\n (click)=\"removeValue(val, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabel(val)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!--\n PH\u1EA6N C\u1ED0 \u0110\u1ECANH c\u1EE7a \u00F4 \u0111\u00F3ng \u2014 icon v\u00E0 nh\u00E3n LU\u00D4N hi\u1EC7n, d\u00F9 \u0111\u00E3 ch\u1ECDn hay ch\u01B0a,\n d\u00F9 danh s\u00E1ch \u0111ang m\u1EDF hay \u0111\u00F3ng. Ch\u1EC9 ph\u1EA7n GI\u00C1 TR\u1ECA b\u00EAn ph\u1EA3i m\u1EDBi \u0111\u1ED5i.\n\n \u0110\u1EB7t NGO\u00C0I c\u1EA3 ba nh\u00E1nh d\u01B0\u1EDBi (\u00F4 t\u00ECm / nh\u00E3n \u0111\u00E3 ch\u1ECDn / placeholder) ch\u00EDnh\n l\u00E0 \u0111i\u1EC1u l\u00E0m n\u00F3 c\u1ED1 \u0111\u1ECBnh \u2014 n\u1EB1m trong nh\u00E1nh n\u00E0o th\u00EC n\u00F3 bi\u1EBFn m\u1EA5t \u1EDF hai\n nh\u00E1nh kia.\n\n Kh\u00E1c `icon` c\u1EE7a M\u1EE4C: c\u00E1i \u0111\u00F3 \u0111\u1ED5i theo m\u1EE5c \u0111ang ch\u1ECDn. Khai c\u1EA3 hai th\u00EC\n `triggerIcon` th\u1EAFng, v\u00EC nh\u00E3n c\u1ED1 \u0111\u1ECBnh m\u00E0 nh\u1EA3y theo m\u1EE5c th\u00EC h\u1EBFt c\u1ED1 \u0111\u1ECBnh.\n -->\n @if (triggerIcon()) {\n <ol-icon [name]=\"triggerIcon()\" size=\"sm\" class=\"shrink-0 me-0.5 text-body-subtle\" />\n }\n @if (triggerLabel()) {\n <span [id]=\"triggerLabelId()\" class=\"shrink-0 text-sm font-medium text-body\">{{\n triggerLabel()\n }}</span>\n }\n\n <!--\n \u00D4 G\u00D5 T\u00CCM ch\u1EC9 hi\u1EC7n khi danh s\u00E1ch \u0110ANG M\u1EDE. \u0110\u00F3ng l\u1EA1i th\u00EC hi\u1EC7n nh\u00E3n t\u0129nh \u2014\n n\u1EBFu \u0111\u1EC3 \u00F4 nh\u1EADp lu\u00F4n \u0111\u00F3, ng\u01B0\u1EDDi d\u00F9ng t\u01B0\u1EDFng g\u00F5 th\u1EB3ng v\u00E0o \u0111\u01B0\u1EE3c, m\u00E0 gi\u00E1 tr\u1ECB\n l\u1EA1i kh\u00F4ng \u0111\u1ED5i theo ch\u1EEF h\u1ECD g\u00F5.\n -->\n @if (searchable() && isOpen() && searchPosition() === 'inline') {\n <input\n #inlineSearch\n type=\"text\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n autocomplete=\"off\"\n class=\"min-w-0 flex-1 border-none bg-transparent p-0 text-inherit outline-none placeholder:text-body-subtle\"\n />\n } @else if (!multiple() && hasValue()) {\n @if (labelTemplate) {\n <ng-container\n *ngTemplateOutlet=\"labelTemplate; context: { $implicit: selectedValues()[0] }\"\n />\n } @else {\n <!-- \u00D4 \u0111\u00F3ng hi\u1EC7n C\u1EA2 ph\u1EA7n \u0111\u1EA7u c\u1EE7a m\u1EE5c \u0111ang ch\u1ECDn, kh\u00F4ng ch\u1EC9 ch\u1EEF \u2014 \u0111\u00E2y\n l\u00E0 \u0111i\u1EC3m kh\u00E1c c\u1EE7a combobox reui so v\u1EDBi \u00F4 ch\u1ECDn th\u01B0\u1EDDng. -->\n @if (selectedItem()?.icon && !triggerIcon()) {\n <ol-icon\n [name]=\"$any(selectedItem()).icon\"\n size=\"sm\"\n class=\"shrink-0 me-0.5\"\n [class]=\"selectedItem()?.prefixClass || ''\"\n />\n } @else if (selectedItem()?.prefix && !triggerIcon()) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"selectedItem()?.prefixClass || 'text-body-subtle'\"\n >{{ selectedItem()?.prefix }}</span\n >\n }\n <span class=\"truncate\">{{ singleLabel() }}</span>\n }\n } @else if (!hasValue()) {\n <!--\n \u1EDE tr\u1EA1ng th\u00E1i L\u1ED6I, n\u1EC1n \u0111\u1ED5i sang `bg-danger-soft` (#fef0f2) v\u00E0\n `text-body-subtle` ch\u1EC9 c\u00F2n **4,36** \u2014 d\u01B0\u1EDBi chu\u1EA9n 4,5. N\u1EC1n `success`\n (#ecfdf5) cho 4,59, v\u1EEBa \u0111\u1EE7, n\u00EAn ch\u1EC9 nh\u00E1nh l\u1ED7i c\u1EA7n \u0111\u1EADm h\u01A1n.\n `text-body` tr\u00EAn n\u1EC1n \u0111\u00F3 ra 6,82 (s\u00E1ng) v\u00E0 10,66 (t\u1ED1i).\n -->\n <span\n class=\"truncate\"\n data-ol-field-text\n data-ol-field-placeholder\n [class.text-body-subtle]=\"state() !== 'error'\"\n [class.text-body]=\"state() === 'error'\"\n >{{ placeholder() }}</span\n >\n }\n\n <!--\n M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0110\u1ED4I VAI theo tr\u1EA1ng th\u00E1i \u2014 kh\u00F4ng x\u1EBFp c\u1EA1nh nhau.\n B\u1EA3n \u0111\u1EA7u c\u1EE7a t\u00F4i \u0111\u1EC3 n\u00FAt xo\u00E1 v\u00E0 `chevron-down` C\u00D9NG hi\u1EC7n: \u0111o 2026-09-17\n tr\u00EAn `/forms` \u0111\u1EBFm **2 icon b\u00EAn ph\u1EA3i \u1EDF M\u1ECCI tr\u1EA1ng th\u00E1i**, k\u1EC3 c\u1EA3 khi danh\n s\u00E1ch \u0111ang m\u1EDF. C\u1EE5m b\u00EAn ph\u1EA3i ch\u1EADt, hai icon 16px chen nhau tr\u00F4ng r\u1ED1i.\n\n `ol-autocomplete` \u0111\u00E3 ch\u1ED1t l\u1ED1i n\u00E0y t\u1EEB tr\u01B0\u1EDBc (\u00F4 `w-7` \u0111\u1ED5i vai\n spinner / xo\u00E1 / chevron); `ol-select` ph\u1EA3i theo cho nh\u1EA5t qu\u00E1n.\n\n C\u1EE5m n\u00E0y `absolute` n\u00EAn BUNG KH\u1ECEI LU\u1ED2NG: l\u1EC1 ph\u1EA3i l\u1EA5y theo c\u1EE1\n (`controlPadClass`), c\u00F2n khung ch\u1EEBa ch\u1ED7 b\u1EB1ng `FIELD_ICON_RESERVE_END`\n \u0111\u1EC3 ch\u1EEF d\u00E0i d\u1EEBng c\u00E1ch icon \u0111\u00FAng 8px. N\u00FAt xo\u00E1 b\u1ECDc `size-4` b\u1EB1ng icon, \u0111o\n theo m\u00E9p SVG \u2014 th\u1EE9 m\u1EAFt ng\u01B0\u1EDDi nh\u00ECn th\u1EA5y.\n -->\n <div\n class=\"absolute inset-y-0 inset-e-0 flex items-center justify-end\"\n [class]=\"controlPadClass()\"\n >\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (clearable() && hasContent() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"pointer-events-none shrink-0 text-body-subtle transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n </div>\n </ol-field-frame>\n\n <!--\n DANH S\u00C1CH. Token \u0111\u1ECDc th\u1EB3ng t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147),\n bi\u1EBFn th\u1EC3 `Type=Default` \u2014 \u0111\u00F3 l\u00E0 dropdown chu\u1EA9n c\u1EE7a design system:\n\n bo g\u00F3c 12px -> rounded-md\n \u0111\u1EC7m 8px -> p-2 (tr\u01B0\u1EDBc l\u00E0 p-1.5 = 6px)\n n\u1EC1n bg-neutral-primary-medium (tr\u01B0\u1EDBc l\u00E0 bg-neutral-primary)\n vi\u1EC1n border-default-medium (tr\u01B0\u1EDBc l\u00E0 border-default)\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng (#ffffff / #e5e7eb) n\u00EAn \u0111\u1ED5i\n hay kh\u00F4ng nh\u00ECn c\u0169ng th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I th\u00EC kh\u00E1c h\u1EB3n:\n\n bg-primary gray-950 \u2190 \u0111ang d\u00F9ng, g\u1EA7n \u0111en, panel CH\u00CCM v\u00E0o n\u1EC1n\n bg-primary-medium gray-800 \u2190 Figma, panel n\u1ED5i l\u00EAn\n border-default gray-800\n border-default-medium gray-700\n\n \u0110\u00E2y \u0111\u00FAng lo\u1EA1i l\u1ED7i ch\u1EC9 l\u1ED9 \u1EDF ch\u1EBF \u0111\u1ED9 t\u1ED1i m\u00E0 ch\u1EBF \u0111\u1ED9 s\u00E1ng kh\u00F4ng bao gi\u1EDD th\u1EA5y.\n -->\n @if (isOpen()) {\n <!--\n \u00D4 T\u00CCM \u1EDE \u0110\u1EA6U PANEL (`searchPosition=\"panel\"`).\n \u0110\u1EB7t NGO\u00C0I th\u1EBB `role=\"listbox\"`: m\u1ED9t `<input>` n\u1EB1m trong listbox l\u00E0 sai\n ARIA \u2014 m\u1ECDi con c\u1EE7a listbox ph\u1EA3i l\u00E0 `option` ho\u1EB7c `group`. \u0110\u00E2y l\u00E0 l\u1ED1i\n `ol-command` v\u00E0 combobox c\u1EE7a reui d\u00F9ng.\n -->\n <!--\n H\u1ED8P B\u1ECCC mang `#panel` v\u00E0 `position: absolute`.\n\n `#panel` ph\u1EA3i \u1EDF \u0110\u00C2Y ch\u1EE9 kh\u00F4ng \u1EDF th\u1EBB `role=\"listbox\"`: `appendTo=\"body\"`\n \u0111\u1EA9y ch\u00EDnh ph\u1EA7n t\u1EED `#panel` ra `<body>`. \u0110\u1EC3 n\u00F3 \u1EDF listbox th\u00EC \u00F4 t\u00ECm \u1EDF l\u1EA1i\n trong c\u00E2y DOM c\u0169 c\u00F2n danh s\u00E1ch bay \u0111i \u2014 h\u1ED9p tho\u1EA1i v\u1EE1 l\u00E0m \u0111\u00F4i.\n -->\n <div\n #panel\n class=\"absolute z-dropdown overflow-hidden rounded-md border border-default-medium bg-neutral-primary-medium shadow-lg flex flex-col max-h-[var(--ol-overlay-max-height,24rem)]\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (searchable() && searchPosition() === 'panel') {\n <div class=\"border-b border-default-subtle p-2\">\n <!-- Icon/ch\u1EEF \u0111\u1EE9ng \u0111\u1EA7u \u00F4 T\u00CCM \u2014 c\u1EE7a vi\u1EC7c T\u00CCM, kh\u00F4ng ph\u1EA3i c\u1EE7a m\u1EE5c\n \u0111ang ch\u1ECDn. B\u1ECDc trong khung c\u00F3 vi\u1EC1n \u0111\u1EC3 icon n\u1EB1m TRONG \u00F4, kh\u00F4ng\n \u0111\u1EE9ng r\u1EDDi b\u00EAn ngo\u00E0i nh\u01B0 m\u1ED9t nh\u00E3n. -->\n <div\n class=\"flex items-center gap-2 rounded border border-default bg-neutral-primary px-2.5 py-1.5 focus-within:border-brand\"\n >\n @if (searchIcon()) {\n <ol-icon [name]=\"searchIcon()\" size=\"sm\" class=\"shrink-0 text-body-subtle\" />\n } @else if (searchPrefix()) {\n <span class=\"shrink-0 text-xs font-medium text-body-subtle\">{{\n searchPrefix()\n }}</span>\n }\n <input\n #oTimPanel\n type=\"text\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchInput($event)\"\n (click)=\"$event.stopPropagation()\"\n (keydown)=\"onKeyDown($event)\"\n [placeholder]=\"searchPlaceholder()\"\n [attr.aria-label]=\"searchPlaceholder()\"\n [attr.aria-controls]=\"listboxId()\"\n autocomplete=\"off\"\n class=\"w-full min-w-0 border-none bg-transparent p-0 text-sm outline-none placeholder:text-body-subtle\"\n />\n </div>\n </div>\n }\n <!--\n `overflow-x-hidden` KHAI R\u00D5: khai `overflow-y-auto` m\u1ED9t m\u00ECnh th\u00EC tr\u00ECnh\n duy\u1EC7t t\u1EF1 t\u00EDnh `overflow-x` th\u00E0nh `auto` (spec CSS: m\u1ED9t tr\u1EE5c `visible`\n c\u00F2n tr\u1EE5c kia kh\u00E1c `visible` l\u00E0 kh\u00F4ng h\u1EE3p l\u1EC7). \u0110o \u0111\u01B0\u1EE3c `scrollWidth\n 765 > 620` khi option c\u00F3 `white-space: nowrap`.\n\n Ch\u1EC9 ch\u1EB7n \u1EDF PANEL CH\u1ECCN c\u00F3 b\u1EC1 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh \u2014 n\u1ED9i dung ph\u1EA3i v\u1EEBa b\u1EC1 ngang.\n KH\u00D4NG ch\u1EB7n \u1EDF khung ch\u1EE9a n\u1ED9i dung t\u1EF1 do (`device-mockup`,\n `chat-container`, `drawer`): \u1EDF \u0111\u00F3 ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu n\u1ED9i dung b\u1EA5t k\u1EF3\n v\u00E0o, ch\u1EB7n l\u00E0 c\u1EAFt m\u1EA5t ph\u1EA7n r\u1ED9ng c\u1EE7a h\u1ECD.\n -->\n <div\n #listboxRef\n role=\"listbox\"\n [id]=\"listboxId()\"\n [attr.aria-multiselectable]=\"multiple() ? 'true' : null\"\n (scroll)=\"onPanelScroll($event)\"\n class=\"flex-1 min-h-0 overflow-y-auto overflow-x-hidden p-2\"\n >\n <!--\n B\u1ED1n tr\u1EA1ng th\u00E1i c\u1EE7a danh s\u00E1ch l\u1EA5y t\u1EEB m\u00E1y ch\u1EE7 (Figma `ol-dropdown`\n 5266:29107): \u0110ang t\u1EA3i \u00B7 Ch\u01B0a g\u00F5 \u0111\u1EE7 \u00B7 R\u1ED7ng/Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3 \u00B7 c\u00F3 m\u1EE5c.\n \u0110ang t\u1EA3i \u0111\u1EE9ng TR\u01AF\u1EDAC r\u1ED7ng: b\u1EA3n c\u0169 b\u00E1o \"kh\u00F4ng t\u00ECm th\u1EA5y\" trong l\u00FAc\n c\u00E2u tr\u1EA3 l\u1EDDi c\u00F2n ch\u01B0a v\u1EC1 \u2014 sai s\u1EF1 th\u1EADt.\n -->\n @if (loading()) {\n <ol-dropdown-state type=\"loading\" />\n } @else if (waitingForTerm()) {\n <!-- Ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`: \"kh\u00F4ng t\u00ECm th\u1EA5y\" l\u00E0 sai s\u1EF1 th\u1EADt, v\u00EC\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i `typeToSearchText` c\u1EE7a ng-select. -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">{{ typeToSearchText() }}</div>\n } @else if (rows().length === 0) {\n <div class=\"text-center\">\n <ol-dropdown-state\n [type]=\"searchQuery().trim() ? 'no-results' : 'empty'\"\n [query]=\"searchQuery().trim()\"\n [heading]=\"emptyText()\"\n />\n @if (canCreate()) {\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"-mt-2 mb-4 inline-flex items-center gap-1.5 rounded-lg bg-brand-softer px-3 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-subtle\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>{{ createLabel() }}</span>\n </button>\n }\n </div>\n } @else {\n <!-- \u0110\u1EC7m tr\u00EAn: gi\u1EEF thanh cu\u1ED9n d\u00E0i \u0111\u00FAng nh\u01B0 khi v\u1EBD h\u1EBFt danh s\u00E1ch. -->\n @if (viewport().padTop > 0) {\n <div [style.height.px]=\"viewport().padTop\" aria-hidden=\"true\"></div>\n }\n\n @for (row of viewport().rows; track row.kind + '-' + row.index + '-' + row.label) {\n @if (row.kind === 'group') {\n <!-- `dauTien` t\u00EDnh khi d\u1EF1ng danh s\u00E1ch, KH\u00D4NG d\u00F9ng `$first` c\u1EE7a\n `@for`: khi `virtualScroll` b\u1EADt, `$first` l\u00E0 d\u00F2ng \u0111\u1EA7u c\u1EE7a\n khung nh\u00ECn n\u00EAn cu\u1ED9n xu\u1ED1ng l\u00E0 m\u1EA5t \u0111\u01B0\u1EDDng ng\u0103n. -->\n @if (groupSeparator() && !row.isFirstGroup) {\n <div role=\"presentation\" class=\"my-1 border-t border-default-subtle\"></div>\n }\n <div\n role=\"presentation\"\n class=\"px-2.5 pt-2 pb-0 text-2xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ row.label }}\n </div>\n } @else {\n <!--\n `rounded-sm` (6px) ch\u1EE9 kh\u00F4ng `rounded-md` (12px): Figma v\u1EBD\n `ol-autocomplete-item` bo **8px**, m\u00E0 thang token c\u1EE7a lib kh\u00F4ng\n c\u00F3 b\u1EADc 8 (xs 4 \u00B7 sm 6 \u00B7 md/base 12 \u00B7 lg 16). L\u1EA5y b\u1EADc G\u1EA6N NH\u1EA4T\n thay v\u00EC vi\u1EBFt `rounded-[8px]` \u2014 gi\u00E1 tr\u1ECB th\u00F4 n\u1EB1m ngo\u00E0i h\u1EC7 token\n th\u00EC \u0111\u1ED5i theme kh\u00F4ng theo \u0111\u01B0\u1EE3c.\n\n `ol-dropdown-item` c\u0169ng \u0111\u00E3 ch\u1ED1t `rounded-sm` cho m\u1EE5c menu, n\u00EAn\n \u0111\u00E2y c\u00F2n l\u00E0 gi\u1EEF nh\u1EA5t qu\u00E1n trong lib.\n -->\n <!--\n `min-w-0` cho ph\u00E9p h\u00E0ng CO l\u1EA1i, \u0111i\u1EC1u ki\u1EC7n c\u1EA7n \u0111\u1EC3 `truncate`\n c\u1EE7a ng\u01B0\u1EDDi d\u00F9ng ch\u1EA1y. Nh\u01B0ng n\u00F3 KH\u00D4NG t\u1EF1 ch\u1EB7n tr\u00E0n: \u0111o b\u1EB1ng c\u00E1ch\n ti\u00EAm `white-space: nowrap` v\u00E0o h\u00E0ng \u2014 panel v\u1EABn `scrollWidth\n 1763 > 831`. Ch\u1EC9 `overflow: hidden` \u0111\u1EB7t tr\u00EAn CH\u00CDNH ph\u1EA7n t\u1EED n\u1ED9i\n dung m\u1EDBi ch\u1EB7n \u0111\u01B0\u1EE3c, m\u00E0 ph\u1EA7n t\u1EED \u0111\u00F3 do ng\u01B0\u1EDDi d\u00F9ng chi\u1EBFu v\u00E0o qua\n `optionTemplate` n\u00EAn lib kh\u00F4ng v\u1EDBi t\u1EDBi. `overflow-x-hidden` \u1EDF\n panel l\u00E0 l\u1EDBp ch\u1EB7n cu\u1ED1i.\n -->\n <div\n role=\"option\"\n [id]=\"optionId(row.index)\"\n [attr.data-option-index]=\"row.index\"\n [attr.aria-selected]=\"isSelected(row.item)\"\n [attr.aria-disabled]=\"row.item?.disabled ? 'true' : null\"\n (click)=\"selectItem(row.item); $event.stopPropagation()\"\n [style.height.px]=\"virtualScroll() ? itemHeight() : null\"\n class=\"flex min-w-0 items-center gap-2 rounded-sm px-3 py-2 text-sm transition-colors\"\n [class.cursor-pointer]=\"!row.item?.disabled\"\n [class.cursor-not-allowed]=\"row.item?.disabled\"\n [class.bg-neutral-tertiary-medium]=\"\n activeIndex() === row.index && !row.item?.disabled\n \"\n [class.hover:bg-neutral-tertiary-medium]=\"\n !row.item?.disabled && activeIndex() !== row.index\n \"\n [class.text-heading]=\"isSelected(row.item) || activeIndex() === row.index\"\n [class.hover:text-heading]=\"!row.item?.disabled\"\n [class.font-medium]=\"isSelected(row.item)\"\n [class.text-body]=\"!isSelected(row.item) && activeIndex() !== row.index\"\n [class.text-fg-disabled]=\"row.item?.disabled\"\n >\n @if (optionTemplate) {\n <ng-container\n *ngTemplateOutlet=\"optionTemplate; context: { $implicit: row.item }\"\n />\n } @else {\n <!-- Ph\u1EA7n \u0111\u1EA7u: `icon` \u01B0u ti\u00EAn, kh\u00F4ng c\u00F3 th\u00EC `prefix` (ch\u1EEF).\n `prefixClass` nh\u1EADn CLASS ch\u1EE9 kh\u00F4ng nh\u1EADn m\u00E3 m\u00E0u \u2014 m\u00E3 m\u00E0u\n c\u1EE9ng kh\u00F4ng \u0111\u1ED5i theo theme v\u00E0 theo ch\u1EBF \u0111\u1ED9 t\u1ED1i. -->\n @if (row.item?.icon) {\n <ol-icon\n [name]=\"row.item.icon\"\n size=\"sm\"\n class=\"shrink-0\"\n [class]=\"row.item.prefixClass || ''\"\n />\n } @else if (row.item?.prefix) {\n <span\n class=\"shrink-0 text-2xs font-semibold tabular-nums\"\n [class]=\"row.item.prefixClass || 'text-body-subtle'\"\n >{{ row.item.prefix }}</span\n >\n }\n <span class=\"min-w-0 flex-1 truncate\">{{ row.label }}</span>\n }\n @if (isSelected(row.item)) {\n <ol-icon name=\"check\" size=\"sm\" class=\"shrink-0 text-fg-brand\" />\n }\n </div>\n }\n }\n\n <!-- \u0110\u1EC7m d\u01B0\u1EDBi \u2014 xem l\u00FD do \u1EDF \u0111\u1EC7m tr\u00EAn. -->\n @if (viewport().padBottom > 0) {\n <div [style.height.px]=\"viewport().padBottom\" aria-hidden=\"true\"></div>\n }\n\n <!-- Danh s\u00E1ch C\u00D2N m\u1EE5c nh\u01B0ng ch\u1EEF \u0111ang g\u00F5 ch\u01B0a tr\u00F9ng nh\u00E3n n\u00E0o: v\u1EABn ph\u1EA3i\n cho t\u1EA1o. V\u00ED d\u1EE5 \u0111\u00E3 c\u00F3 \"To\u00E1n 10\" m\u00E0 ng\u01B0\u1EDDi d\u00F9ng g\u00F5 \"To\u00E1n 11\" \u2014\n nh\u00E1nh r\u1ED7ng \u1EDF tr\u00EAn kh\u00F4ng ch\u1EA1y v\u00EC b\u1ED9 l\u1ECDc v\u1EABn ra m\u1ED9t m\u1EE5c. -->\n @if (canCreate()) {\n <div class=\"mt-1 border-t border-default-subtle p-2\">\n <button\n type=\"button\"\n (click)=\"createOption()\"\n class=\"inline-flex w-full items-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium text-fg-brand transition-colors hover:bg-brand-softer\"\n >\n <ol-icon name=\"plus\" size=\"xs\" class=\"shrink-0\" />\n <span class=\"truncate\">{{ createLabel() }}</span>\n </button>\n </div>\n }\n }\n </div>\n </div>\n }\n </div>\n\n @if (helperText()) {\n <p\n class=\"mt-1.5 text-xs\"\n [class.text-body]=\"state() === 'default'\"\n [class.text-fg-danger-strong]=\"state() === 'error'\"\n [class.text-fg-success-strong]=\"state() === 'success'\"\n >\n {{ helperText() }}\n </p>\n }\n</div>\n" }]
|
|
11123
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], leftIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "leftIcon", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], menuClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuClass", required: false }] }], matchTriggerWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchTriggerWidth", required: false }] }], maxSelectedItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSelectedItems", required: false }] }], closeOnSelect: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnSelect", required: false }] }], hideSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideSelected", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], createText: [{ type: i0.Input, args: [{ isSignal: true, alias: "createText", required: false }] }], groupSeparator: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupSeparator", required: false }] }], searchPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPosition", required: false }] }], searchIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchIcon", required: false }] }], searchPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPrefix", required: false }] }], triggerIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerIcon", required: false }] }], triggerLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "triggerLabel", required: false }] }], remoteSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteSearch", required: false }] }], minTermLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTermLength", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], typeToSearchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeToSearchText", required: false }] }], virtualScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualScroll", required: false }] }], itemHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemHeight", required: false }] }], bufferAmount: [{ type: i0.Input, args: [{ isSignal: true, alias: "bufferAmount", required: false }] }], search: [{ type: i0.Output, args: ["search"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], optionTemplate: [{
|
|
10164
11124
|
type: ContentChild,
|
|
10165
11125
|
args: ['optionTemplate']
|
|
10166
11126
|
}], labelTemplate: [{
|
|
10167
11127
|
type: ContentChild,
|
|
10168
11128
|
args: ['labelTemplate']
|
|
10169
|
-
}], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], listboxRef: [{ type: i0.ViewChild, args: ['listboxRef', { isSignal: true }] }], oTimPanel: [{ type: i0.ViewChild, args: ['oTimPanel', { isSignal: true }] }], onDocumentClick: [{
|
|
11129
|
+
}], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], listboxRef: [{ type: i0.ViewChild, args: ['listboxRef', { isSignal: true }] }], oTimPanel: [{ type: i0.ViewChild, args: ['oTimPanel', { isSignal: true }] }], inlineSearch: [{ type: i0.ViewChild, args: ['inlineSearch', { isSignal: true }] }], onDocumentClick: [{
|
|
10170
11130
|
type: HostListener,
|
|
10171
11131
|
args: ['document:click', ['$event']]
|
|
10172
11132
|
}] } });
|
|
@@ -10578,6 +11538,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
10578
11538
|
}]
|
|
10579
11539
|
}], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }] } });
|
|
10580
11540
|
|
|
11541
|
+
/**
|
|
11542
|
+
* Mục đang chọn theo màu. Nền `-soft` (mức 50) gần trùng rãnh xám `#f3f4f6`,
|
|
11543
|
+
* nên kèm viền trong cùng tông `ring-*-subtle` mới tách được. Toàn token ngữ
|
|
11544
|
+
* nghĩa có khối `.dark` — không ghim `dark:`.
|
|
11545
|
+
*/
|
|
11546
|
+
const SELECTED_COLOR = {
|
|
11547
|
+
neutral: 'bg-neutral-primary text-heading shadow-xs',
|
|
11548
|
+
brand: 'bg-brand-soft text-fg-brand-strong shadow-xs ring-1 ring-inset ring-brand-subtle',
|
|
11549
|
+
success: 'bg-success-soft text-fg-success-strong shadow-xs ring-1 ring-inset ring-success-subtle',
|
|
11550
|
+
warning: 'bg-warning-soft text-fg-warning-strong shadow-xs ring-1 ring-inset ring-warning-subtle',
|
|
11551
|
+
danger: 'bg-danger-soft text-fg-danger-strong shadow-xs ring-1 ring-inset ring-danger-subtle',
|
|
11552
|
+
};
|
|
10581
11553
|
/**
|
|
10582
11554
|
* Chọn MỘT trong vài chế độ loại trừ nhau, đặt cạnh nhau trên một rãnh xám;
|
|
10583
11555
|
* mục đang chọn nổi lên nền trắng (segmented control).
|
|
@@ -10631,7 +11603,7 @@ class OlSegmented {
|
|
|
10631
11603
|
const grow = this.fullWidth() ? 'flex-1 min-w-0' : 'shrink-0';
|
|
10632
11604
|
const base = `${size} ${grow} inline-flex items-center justify-center gap-1.5 rounded whitespace-nowrap transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-subtle`;
|
|
10633
11605
|
if (option.value === this.value()) {
|
|
10634
|
-
return `${base}
|
|
11606
|
+
return `${base} font-semibold ${SELECTED_COLOR[option.color ?? 'neutral']}`;
|
|
10635
11607
|
}
|
|
10636
11608
|
const blocked = option.disabled || this.disabled();
|
|
10637
11609
|
return `${base} text-body font-medium ${blocked ? 'cursor-not-allowed opacity-50' : 'cursor-pointer hover:text-heading'}`;
|
|
@@ -11998,122 +12970,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
11998
12970
|
], template: "<!-- N\u00FAm k\u00E9o theo Figma `ol-range-thumb` (#1149:54373):\n n\u1EC1n `bg-neutral-primary-medium`, KH\u00D4NG ph\u1EA3i `bg-neutral-primary` \u2014 `bg-neutral-primary` gi\u1EEF\n nguy\u00EAn #FFFFFF \u1EDF ch\u1EBF \u0111\u1ED9 t\u1ED1i n\u00EAn n\u00FAm ch\u00F3i tr\u00EAn r\u00E3nh t\u1ED1i, c\u00F2n\n `--bg-primary-medium` \u0111\u1ED5i sang gray-800. Vi\u1EC1n theo tr\u1EE5c State:\n Initial `border-default-medium`, Active `border-brand`; code tr\u01B0\u1EDBc \u0111\u00E2y\n d\u00F9ng border-brand cho c\u1EA3 hai. -->\n<div class=\"w-full\">\n @if (label() || showValue()) {\n <div class=\"flex items-center justify-between mb-2 text-sm font-medium\">\n @if (label()) {\n <label [for]=\"dualRange() ? minThumbId() : inputId()\" class=\"text-heading\">{{\n label()\n }}</label>\n }\n @if (showValue()) {\n <span class=\"text-fg-brand font-semibold ms-auto\">\n @if (dualRange()) {\n {{ minValue() }}{{ unit() }} - {{ maxValue() }}{{ unit() }}\n } @else {\n {{ value() }}{{ unit() }}\n }\n </span>\n }\n </div>\n }\n\n <!--\n Range Slider Container.\n\n Hai l\u00FD do `ol-range` KH\u00D4NG k\u00E9o \u0111\u01B0\u1EE3c tr\u00EAn di \u0111\u1ED9ng, \u0111o 2026-09-17 \u1EDF viewport\n 390\u00D7844 (mobile + touch):\n\n 1. `touch-action: auto` \u2014 m\u1EB7c \u0111\u1ECBnh c\u1EE7a tr\u00ECnh duy\u1EC7t. Ng\u00F3n tay vu\u1ED1t tr\u00EAn thanh\n tr\u01B0\u1EE3t b\u1ECB Chrome hi\u1EC3u l\u00E0 c\u1EED ch\u1EC9 cu\u1ED9n/pan trang, n\u00F3 thu l\u1EA5y thao t\u00E1c v\u00E0\n input kh\u00F4ng nh\u1EADn \u0111\u01B0\u1EE3c g\u00EC. `touch-none` tr\u1EA3 quy\u1EC1n x\u1EED l\u00FD v\u1EC1 cho input.\n\n 2. Ch\u00EDnh th\u1EBB `<input type=range>` cao `h-2` (8px) \u2014 d\u01B0\u1EDBi xa m\u1EE9c 24px t\u1ED1i\n thi\u1EC3u cho ng\u00F3n tay. N\u00E2ng l\u00EAn `h-6` (24px); r\u00E3nh NH\u00CCN TH\u1EA4Y v\u1EABn 8px v\u00EC n\u00F3\n l\u00E0 `<div>` ri\u00EAng v\u1EBD ph\u00EDa sau, ch\u1EC9 v\u00F9ng CH\u1EA0M r\u1ED9ng ra.\n\n K\u00E8m `::-moz-range-thumb` song song v\u1EDBi `::-webkit-slider-thumb`: b\u1EA3n c\u0169 ch\u1EC9\n c\u00F3 webkit n\u00EAn Firefox m\u1EA5t ho\u00E0n to\u00E0n ki\u1EC3u n\u00FAm k\u00E9o.\n -->\n <div class=\"relative flex items-center gap-3 w-full my-2\">\n <!-- Prefix Icon Slot -->\n <ng-content select=\"[slot=prefix]\" />\n\n <div class=\"relative flex-1 flex items-center h-6\">\n <!-- Background Track -->\n <div class=\"absolute w-full h-2 bg-neutral-tertiary-medium rounded-full\"></div>\n\n @if (dualRange()) {\n <!-- Dual Range Active Bar -->\n <div\n class=\"absolute h-2 bg-brand rounded-full\"\n [style.left.%]=\"dualMinPercentage()\"\n [style.width.%]=\"dualMaxPercentage() - dualMinPercentage()\"\n ></div>\n\n <!-- Min Input Thumb -->\n <input\n [id]=\"minThumbId()\"\n [attr.aria-label]=\"minThumbLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n type=\"range\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [ngModel]=\"minValue()\"\n (ngModelChange)=\"onMinChange($event)\"\n [disabled]=\"disabled()\"\n class=\"absolute w-full h-6 touch-none bg-transparent appearance-none disabled:cursor-not-allowed disabled:opacity-50 pointer-events-none cursor-pointer [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:bg-neutral-primary-medium [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-default-medium [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:bg-neutral-primary-medium [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-default-medium [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:cursor-pointer [&:hover::-webkit-slider-thumb]:border-brand [&:focus::-webkit-slider-thumb]:border-brand [&:active::-webkit-slider-thumb]:border-brand [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110\"\n />\n\n <!-- Max Input Thumb -->\n <input\n [id]=\"maxThumbId()\"\n [attr.aria-label]=\"maxThumbLabel()\"\n [attr.aria-describedby]=\"describedBy()\"\n type=\"range\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [ngModel]=\"maxValue()\"\n (ngModelChange)=\"onMaxChange($event)\"\n [disabled]=\"disabled()\"\n class=\"absolute w-full h-6 touch-none bg-transparent appearance-none disabled:cursor-not-allowed disabled:opacity-50 pointer-events-none cursor-pointer [&::-webkit-slider-thumb]:pointer-events-auto [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:bg-neutral-primary-medium [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-default-medium [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:bg-neutral-primary-medium [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-default-medium [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:cursor-pointer [&:hover::-webkit-slider-thumb]:border-brand [&:focus::-webkit-slider-thumb]:border-brand [&:active::-webkit-slider-thumb]:border-brand [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110\"\n />\n } @else {\n <!-- Single Active Fill Bar -->\n <div class=\"absolute h-2 bg-brand rounded-full\" [style.width.%]=\"singlePercentage()\"></div>\n\n <!-- Single Input Thumb -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n type=\"range\"\n [min]=\"min()\"\n [max]=\"max()\"\n [step]=\"step()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onSingleChange($event)\"\n [disabled]=\"disabled()\"\n class=\"absolute w-full h-6 touch-none bg-transparent appearance-none disabled:cursor-not-allowed disabled:opacity-50 cursor-pointer [&::-webkit-slider-thumb]:w-5 [&::-webkit-slider-thumb]:h-5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:bg-neutral-primary-medium [&::-webkit-slider-thumb]:border-2 [&::-webkit-slider-thumb]:border-default-medium [&::-moz-range-thumb]:w-5 [&::-moz-range-thumb]:h-5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:bg-neutral-primary-medium [&::-moz-range-thumb]:border-2 [&::-moz-range-thumb]:border-default-medium [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:cursor-pointer [&:hover::-webkit-slider-thumb]:border-brand [&:focus::-webkit-slider-thumb]:border-brand [&:active::-webkit-slider-thumb]:border-brand [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:shadow-md [&::-webkit-slider-thumb]:transition-transform [&::-webkit-slider-thumb]:hover:scale-110\"\n />\n\n @if (showTooltip()) {\n <!-- Floating Value Tooltip -->\n <div\n class=\"absolute -top-7 transform -translate-x-1/2 px-2 py-0.5 bg-dark text-white text-xs font-semibold rounded-md shadow-md pointer-events-none transition-all\"\n [style.left.%]=\"singlePercentage()\"\n >\n {{ value() }}{{ unit() }}\n <div\n class=\"absolute left-1/2 -bottom-1 transform -translate-x-1/2 border-4 border-transparent border-t-gray-900\"\n ></div>\n </div>\n }\n }\n </div>\n\n <!-- Suffix Icon Slot -->\n <ng-content select=\"[slot=suffix]\" />\n </div>\n\n @if (showMinMaxLabels()) {\n <div class=\"flex items-center justify-between text-xs text-body-subtle font-medium mt-1\">\n <span>{{ min() }}{{ unit() }}</span>\n <span>{{ max() }}{{ unit() }}</span>\n </div>\n }\n\n @if (helperText()) {\n <!-- [id] B\u1EAET BU\u1ED8C: `aria-describedby` c\u1EE7a \u00F4 nh\u1EADp tr\u1ECF v\u00E0o `helperId()`,\n thi\u1EBFu id th\u00EC n\u00F3 tr\u1ECF v\u00E0o h\u01B0 kh\u00F4ng v\u00E0 ph\u1EA7n m\u00F4 t\u1EA3 kh\u00F4ng bao gi\u1EDD \u0111\u01B0\u1EE3c\n \u0111\u1ECDc. `ol-input` \u0111\u00E3 g\u1EAFn t\u1EEB \u0111\u1EA7u, `ol-range` qu\u00EAn. -->\n <p [id]=\"helperId()\" class=\"mt-1 text-xs text-body-subtle\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
11999
12971
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], minValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "minValue", required: false }] }, { type: i0.Output, args: ["minValueChange"] }], maxValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxValue", required: false }] }, { type: i0.Output, args: ["maxValueChange"] }], dualRange: [{ type: i0.Input, args: [{ isSignal: true, alias: "dualRange", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], showTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTooltip", required: false }] }], showMinMaxLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "showMinMaxLabels", required: false }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
12000
12972
|
|
|
12001
|
-
let uniqueFloatingId = 0;
|
|
12002
|
-
class OlFloatingLabel {
|
|
12003
|
-
value = model('', /* @ts-ignore */
|
|
12004
|
-
...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
12005
|
-
label = input('Nhãn nổi', /* @ts-ignore */
|
|
12006
|
-
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
12007
|
-
type = input('text', /* @ts-ignore */
|
|
12008
|
-
...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
|
|
12009
|
-
variant = input('outlined', /* @ts-ignore */
|
|
12010
|
-
...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
12011
|
-
size = input('base', /* @ts-ignore */
|
|
12012
|
-
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
12013
|
-
state = input('default', /* @ts-ignore */
|
|
12014
|
-
...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
|
|
12015
|
-
/**
|
|
12016
|
-
* Khai bằng `model()` thay vì `input()` để Reactive Forms khoá được component:
|
|
12017
|
-
* `setDisabledState()` cần `set()` giá trị, mà `input()` chỉ đọc. Cách dùng từ
|
|
12018
|
-
* ngoài không đổi — `[disabled]="true"` và `disabled()` trong template vẫn như cũ.
|
|
12019
|
-
*/
|
|
12020
|
-
disabled = model(false, /* @ts-ignore */
|
|
12021
|
-
...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
12022
|
-
readonly = input(false, /* @ts-ignore */
|
|
12023
|
-
...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
12024
|
-
helperText = input('', /* @ts-ignore */
|
|
12025
|
-
...(ngDevMode ? [{ debugName: "helperText" }] : /* istanbul ignore next */ []));
|
|
12026
|
-
errorText = input('', /* @ts-ignore */
|
|
12027
|
-
...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
|
|
12028
|
-
id = input('', /* @ts-ignore */
|
|
12029
|
-
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
12030
|
-
autocomplete = input('off', /* @ts-ignore */
|
|
12031
|
-
...(ngDevMode ? [{ debugName: "autocomplete" }] : /* istanbul ignore next */ []));
|
|
12032
|
-
labelBg = input('', /* @ts-ignore */
|
|
12033
|
-
...(ngDevMode ? [{ debugName: "labelBg" }] : /* istanbul ignore next */ []));
|
|
12034
|
-
// Icon inputs
|
|
12035
|
-
prefixIcon = input('', /* @ts-ignore */
|
|
12036
|
-
...(ngDevMode ? [{ debugName: "prefixIcon" }] : /* istanbul ignore next */ []));
|
|
12037
|
-
suffixIcon = input('', /* @ts-ignore */
|
|
12038
|
-
...(ngDevMode ? [{ debugName: "suffixIcon" }] : /* istanbul ignore next */ []));
|
|
12039
|
-
prefixVariant = input('linear', /* @ts-ignore */
|
|
12040
|
-
...(ngDevMode ? [{ debugName: "prefixVariant" }] : /* istanbul ignore next */ []));
|
|
12041
|
-
suffixVariant = input('linear', /* @ts-ignore */
|
|
12042
|
-
...(ngDevMode ? [{ debugName: "suffixVariant" }] : /* istanbul ignore next */ []));
|
|
12043
|
-
hasPrefix = computed(() => !!this.prefixIcon(), /* @ts-ignore */
|
|
12044
|
-
...(ngDevMode ? [{ debugName: "hasPrefix" }] : /* istanbul ignore next */ []));
|
|
12045
|
-
hasSuffix = computed(() => !!this.suffixIcon(), /* @ts-ignore */
|
|
12046
|
-
...(ngDevMode ? [{ debugName: "hasSuffix" }] : /* istanbul ignore next */ []));
|
|
12047
|
-
isFocused = signal(false, /* @ts-ignore */
|
|
12048
|
-
...(ngDevMode ? [{ debugName: "isFocused" }] : /* istanbul ignore next */ []));
|
|
12049
|
-
isFloated = computed(() => {
|
|
12050
|
-
const val = this.value();
|
|
12051
|
-
return this.isFocused() || (val !== '' && val !== null && val !== undefined);
|
|
12052
|
-
}, /* @ts-ignore */
|
|
12053
|
-
...(ngDevMode ? [{ debugName: "isFloated" }] : /* istanbul ignore next */ []));
|
|
12054
|
-
generatedId = `ol-floating-${uniqueFloatingId++}`;
|
|
12055
|
-
inputId = computed(() => this.id() || this.generatedId, /* @ts-ignore */
|
|
12056
|
-
...(ngDevMode ? [{ debugName: "inputId" }] : /* istanbul ignore next */ []));
|
|
12057
|
-
helperId = computed(() => `${this.inputId()}-helper`, /* @ts-ignore */
|
|
12058
|
-
...(ngDevMode ? [{ debugName: "helperId" }] : /* istanbul ignore next */ []));
|
|
12059
|
-
describedBy = computed(() => (this.helperText() ? this.helperId() : null), /* @ts-ignore */
|
|
12060
|
-
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
12061
|
-
ariaInvalid = computed(() => (this.state() === 'error' ? 'true' : null), /* @ts-ignore */
|
|
12062
|
-
...(ngDevMode ? [{ debugName: "ariaInvalid" }] : /* istanbul ignore next */ []));
|
|
12063
|
-
onChange = () => { };
|
|
12064
|
-
onTouched = () => { };
|
|
12065
|
-
onFocus() {
|
|
12066
|
-
if (this.disabled() || this.readonly())
|
|
12067
|
-
return;
|
|
12068
|
-
this.isFocused.set(true);
|
|
12069
|
-
}
|
|
12070
|
-
onBlur() {
|
|
12071
|
-
this.isFocused.set(false);
|
|
12072
|
-
this.onTouched();
|
|
12073
|
-
}
|
|
12074
|
-
onModelChange(val) {
|
|
12075
|
-
this.value.set(val);
|
|
12076
|
-
this.onChange(val);
|
|
12077
|
-
}
|
|
12078
|
-
// CVA Implementation
|
|
12079
|
-
writeValue(val) {
|
|
12080
|
-
this.value.set(val || '');
|
|
12081
|
-
}
|
|
12082
|
-
registerOnChange(fn) {
|
|
12083
|
-
this.onChange = fn;
|
|
12084
|
-
}
|
|
12085
|
-
registerOnTouched(fn) {
|
|
12086
|
-
this.onTouched = fn;
|
|
12087
|
-
}
|
|
12088
|
-
setDisabledState(isDisabled) {
|
|
12089
|
-
this.disabled.set(isDisabled);
|
|
12090
|
-
}
|
|
12091
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFloatingLabel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12092
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlFloatingLabel, isStandalone: true, selector: "ol-floating-label", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, autocomplete: { classPropertyName: "autocomplete", publicName: "autocomplete", isSignal: true, isRequired: false, transformFunction: null }, labelBg: { classPropertyName: "labelBg", publicName: "labelBg", isSignal: true, isRequired: false, transformFunction: null }, prefixIcon: { classPropertyName: "prefixIcon", publicName: "prefixIcon", isSignal: true, isRequired: false, transformFunction: null }, suffixIcon: { classPropertyName: "suffixIcon", publicName: "suffixIcon", isSignal: true, isRequired: false, transformFunction: null }, prefixVariant: { classPropertyName: "prefixVariant", publicName: "prefixVariant", isSignal: true, isRequired: false, transformFunction: null }, suffixVariant: { classPropertyName: "suffixVariant", publicName: "suffixVariant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange" }, host: { classAttribute: "block w-full" }, providers: [
|
|
12093
|
-
{
|
|
12094
|
-
provide: NG_VALUE_ACCESSOR,
|
|
12095
|
-
useExisting: forwardRef(() => OlFloatingLabel),
|
|
12096
|
-
multi: true,
|
|
12097
|
-
},
|
|
12098
|
-
], ngImport: i0, template: "<div class=\"relative w-full\">\n @if (variant() === 'outlined') {\n <!-- 1. Outlined Floating Label -->\n <div class=\"relative w-full\">\n @if (prefixIcon()) {\n <div\n class=\"absolute inset-s-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"prefixIcon()\"\n [variant]=\"prefixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [type]=\"type()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onModelChange($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [autocomplete]=\"autocomplete()\"\n class=\"block w-full text-heading bg-transparent border rounded-md appearance-none focus:outline-hidden focus:border-brand disabled:bg-neutral-tertiary disabled:text-fg-disabled disabled:cursor-not-allowed transition-colors\"\n [class.h-9]=\"size() === 'sm'\"\n [class.px-4]=\"size() === 'sm' && !hasPrefix() && !hasSuffix()\"\n [class.ps-10]=\"size() === 'sm' && hasPrefix()\"\n [class.pe-10]=\"size() === 'sm' && hasSuffix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.h-10]=\"size() === 'base'\"\n [class.px-5]=\"size() === 'base' && !hasPrefix() && !hasSuffix()\"\n [class.ps-11]=\"size() === 'base' && hasPrefix()\"\n [class.pe-11]=\"size() === 'base' && hasSuffix()\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.px-6]=\"(size() === 'lg' || size() === 'xl') && !hasPrefix() && !hasSuffix()\"\n [class.ps-12]=\"(size() === 'lg' || size() === 'xl') && hasPrefix()\"\n [class.pe-12]=\"(size() === 'lg' || size() === 'xl') && hasSuffix()\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.border-default]=\"state() === 'default' && !disabled()\"\n [class.border-brand]=\"isFocused() && state() === 'default' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n />\n\n <label\n [for]=\"inputId()\"\n class=\"absolute pointer-events-none transition-all duration-200 transform origin-left z-10 select-none px-1.5\"\n [class.rounded-md]=\"isFloated()\"\n [class.inset-s-4]=\"size() === 'sm' && (isFloated() || !hasPrefix())\"\n [class.inset-s-5]=\"size() === 'base' && (isFloated() || !hasPrefix())\"\n [class.inset-s-6]=\"(size() === 'lg' || size() === 'xl') && (isFloated() || !hasPrefix())\"\n [class.inset-s-10]=\"!isFloated() && hasPrefix() && size() === 'sm'\"\n [class.inset-s-11]=\"!isFloated() && hasPrefix() && size() === 'base'\"\n [class.inset-s-12]=\"!isFloated() && hasPrefix() && size() === 'lg'\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base' || size() === 'lg'\"\n [class.top-2]=\"isFloated()\"\n [class.-translate-y-4]=\"isFloated()\"\n [class.scale-75]=\"isFloated()\"\n [class.bg-neutral-primary-soft]=\"isFloated() && !disabled() && !labelBg()\"\n [class.bg-neutral-tertiary]=\"isFloated() && disabled() && !labelBg()\"\n [class.text-fg-brand]=\"isFloated() && isFocused() && state() === 'default' && !disabled()\"\n [class.text-body-subtle]=\"\n (isFloated() && !isFocused() && state() === 'default' && !disabled()) || !isFloated()\n \"\n [class.text-fg-danger]=\"isFloated() && state() === 'error' && !disabled()\"\n [class.text-fg-success]=\"isFloated() && state() === 'success' && !disabled()\"\n [class.text-fg-disabled]=\"isFloated() && disabled()\"\n [class.top-1/2]=\"!isFloated()\"\n [class.-translate-y-1/2]=\"!isFloated()\"\n [class.scale-100]=\"!isFloated()\"\n [class.bg-transparent]=\"!isFloated()\"\n [ngClass]=\"isFloated() && labelBg() ? labelBg() : ''\"\n >\n {{ label() }}\n </label>\n\n @if (suffixIcon()) {\n <div\n class=\"absolute inset-e-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"suffixIcon()\"\n [variant]=\"suffixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n </div>\n } @else if (variant() === 'filled') {\n <!-- 2. Filled Floating Label -->\n <div class=\"relative w-full\">\n @if (prefixIcon()) {\n <div\n class=\"absolute inset-s-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"prefixIcon()\"\n [variant]=\"prefixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [type]=\"type()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onModelChange($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [autocomplete]=\"autocomplete()\"\n class=\"block w-full text-heading bg-neutral-tertiary/80 border-0 border-b-2 appearance-none focus:outline-hidden focus:border-brand rounded-t-md transition-colors disabled:opacity-60 disabled:cursor-not-allowed\"\n [class.pt-4]=\"size() === 'sm'\"\n [class.pb-1]=\"size() === 'sm'\"\n [class.px-4]=\"size() === 'sm' && !hasPrefix() && !hasSuffix()\"\n [class.ps-10]=\"size() === 'sm' && hasPrefix()\"\n [class.pe-10]=\"size() === 'sm' && hasSuffix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.pt-5]=\"size() === 'base'\"\n [class.px-5]=\"size() === 'base' && !hasPrefix() && !hasSuffix()\"\n [class.ps-11]=\"size() === 'base' && hasPrefix()\"\n [class.pe-11]=\"size() === 'base' && hasSuffix()\"\n [class.pt-6]=\"size() === 'lg' || size() === 'xl'\"\n [class.pb-2]=\"size() === 'lg' || size() === 'xl'\"\n [class.px-6]=\"(size() === 'lg' || size() === 'xl') && !hasPrefix() && !hasSuffix()\"\n [class.ps-12]=\"(size() === 'lg' || size() === 'xl') && hasPrefix()\"\n [class.pe-12]=\"(size() === 'lg' || size() === 'xl') && hasSuffix()\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.border-default]=\"state() === 'default' && !disabled()\"\n [class.border-brand]=\"isFocused() && state() === 'default' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [ngClass]=\"{ 'pb-1.5': size() === 'base' }\"\n />\n\n <label\n [for]=\"inputId()\"\n class=\"absolute pointer-events-none transition-all duration-200 transform origin-left z-10 select-none\"\n [class.inset-s-4]=\"size() === 'sm' && (isFloated() || !hasPrefix())\"\n [class.inset-s-5]=\"size() === 'base' && (isFloated() || !hasPrefix())\"\n [class.inset-s-6]=\"(size() === 'lg' || size() === 'xl') && (isFloated() || !hasPrefix())\"\n [class.inset-s-10]=\"!isFloated() && hasPrefix() && size() === 'sm'\"\n [class.inset-s-11]=\"!isFloated() && hasPrefix() && size() === 'base'\"\n [class.inset-s-12]=\"!isFloated() && hasPrefix() && size() === 'lg'\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base' || size() === 'lg'\"\n [class.top-3]=\"isFloated()\"\n [class.-translate-y-3]=\"isFloated()\"\n [class.scale-75]=\"isFloated()\"\n [class.text-fg-brand]=\"isFloated() && isFocused() && state() === 'default' && !disabled()\"\n [class.text-body-subtle]=\"\n (isFloated() && !isFocused() && state() === 'default' && !disabled()) || !isFloated()\n \"\n [class.text-fg-danger]=\"isFloated() && state() === 'error' && !disabled()\"\n [class.text-fg-success]=\"isFloated() && state() === 'success' && !disabled()\"\n [class.text-fg-disabled]=\"isFloated() && disabled()\"\n [class.top-1/2]=\"!isFloated()\"\n [class.-translate-y-1/2]=\"!isFloated()\"\n [class.scale-100]=\"!isFloated()\"\n >\n {{ label() }}\n </label>\n\n @if (suffixIcon()) {\n <div\n class=\"absolute inset-e-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"suffixIcon()\"\n [variant]=\"suffixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n </div>\n } @else {\n <!-- 3. Standard / Underline Floating Label -->\n <div class=\"relative w-full\">\n @if (prefixIcon()) {\n <div\n class=\"absolute inset-s-0 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"prefixIcon()\"\n [variant]=\"prefixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [type]=\"type()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onModelChange($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [autocomplete]=\"autocomplete()\"\n class=\"block w-full text-heading bg-transparent border-0 border-b-2 appearance-none focus:outline-hidden focus:border-brand transition-colors disabled:opacity-60 disabled:cursor-not-allowed\"\n [class.pt-3]=\"size() === 'sm'\"\n [class.pb-1]=\"size() === 'sm'\"\n [class.ps-6]=\"hasPrefix()\"\n [class.pe-6]=\"hasSuffix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.pt-4]=\"size() === 'base'\"\n [class.pt-5]=\"size() === 'lg' || size() === 'xl'\"\n [class.pb-2]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.border-default]=\"state() === 'default' && !disabled()\"\n [class.border-brand]=\"isFocused() && state() === 'default' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [ngClass]=\"{ 'pb-1.5': size() === 'base' }\"\n />\n\n <label\n [for]=\"inputId()\"\n class=\"absolute pointer-events-none transition-all duration-200 transform origin-left z-10 select-none\"\n [class.inset-s-0]=\"!hasPrefix() || isFloated()\"\n [class.inset-s-6]=\"!isFloated() && hasPrefix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base' || size() === 'lg'\"\n [class.top-2]=\"isFloated()\"\n [class.-translate-y-5]=\"isFloated()\"\n [class.scale-75]=\"isFloated()\"\n [class.text-fg-brand]=\"isFloated() && isFocused() && state() === 'default' && !disabled()\"\n [class.text-body-subtle]=\"\n (isFloated() && !isFocused() && state() === 'default' && !disabled()) || !isFloated()\n \"\n [class.text-fg-danger]=\"isFloated() && state() === 'error' && !disabled()\"\n [class.text-fg-success]=\"isFloated() && state() === 'success' && !disabled()\"\n [class.text-fg-disabled]=\"isFloated() && disabled()\"\n [class.top-1/2]=\"!isFloated()\"\n [class.-translate-y-1/2]=\"!isFloated()\"\n [class.scale-100]=\"!isFloated()\"\n >\n {{ label() }}\n </label>\n\n @if (suffixIcon()) {\n <div\n class=\"absolute inset-e-0 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"suffixIcon()\"\n [variant]=\"suffixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger font-medium\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body-subtle\">{{ helperText() }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12099
|
-
}
|
|
12100
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlFloatingLabel, decorators: [{
|
|
12101
|
-
type: Component,
|
|
12102
|
-
args: [{ selector: 'ol-floating-label', standalone: true, imports: [CommonModule, FormsModule, OlIcon], host: {
|
|
12103
|
-
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
12104
|
-
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
12105
|
-
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
12106
|
-
// thêm nút xoá. Sửa từng cái là vá lẻ, nên chốt cho cả họ.
|
|
12107
|
-
class: 'block w-full',
|
|
12108
|
-
}, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
12109
|
-
{
|
|
12110
|
-
provide: NG_VALUE_ACCESSOR,
|
|
12111
|
-
useExisting: forwardRef(() => OlFloatingLabel),
|
|
12112
|
-
multi: true,
|
|
12113
|
-
},
|
|
12114
|
-
], template: "<div class=\"relative w-full\">\n @if (variant() === 'outlined') {\n <!-- 1. Outlined Floating Label -->\n <div class=\"relative w-full\">\n @if (prefixIcon()) {\n <div\n class=\"absolute inset-s-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"prefixIcon()\"\n [variant]=\"prefixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [type]=\"type()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onModelChange($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [autocomplete]=\"autocomplete()\"\n class=\"block w-full text-heading bg-transparent border rounded-md appearance-none focus:outline-hidden focus:border-brand disabled:bg-neutral-tertiary disabled:text-fg-disabled disabled:cursor-not-allowed transition-colors\"\n [class.h-9]=\"size() === 'sm'\"\n [class.px-4]=\"size() === 'sm' && !hasPrefix() && !hasSuffix()\"\n [class.ps-10]=\"size() === 'sm' && hasPrefix()\"\n [class.pe-10]=\"size() === 'sm' && hasSuffix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.h-10]=\"size() === 'base'\"\n [class.px-5]=\"size() === 'base' && !hasPrefix() && !hasSuffix()\"\n [class.ps-11]=\"size() === 'base' && hasPrefix()\"\n [class.pe-11]=\"size() === 'base' && hasSuffix()\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.px-6]=\"(size() === 'lg' || size() === 'xl') && !hasPrefix() && !hasSuffix()\"\n [class.ps-12]=\"(size() === 'lg' || size() === 'xl') && hasPrefix()\"\n [class.pe-12]=\"(size() === 'lg' || size() === 'xl') && hasSuffix()\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.border-default]=\"state() === 'default' && !disabled()\"\n [class.border-brand]=\"isFocused() && state() === 'default' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n />\n\n <label\n [for]=\"inputId()\"\n class=\"absolute pointer-events-none transition-all duration-200 transform origin-left z-10 select-none px-1.5\"\n [class.rounded-md]=\"isFloated()\"\n [class.inset-s-4]=\"size() === 'sm' && (isFloated() || !hasPrefix())\"\n [class.inset-s-5]=\"size() === 'base' && (isFloated() || !hasPrefix())\"\n [class.inset-s-6]=\"(size() === 'lg' || size() === 'xl') && (isFloated() || !hasPrefix())\"\n [class.inset-s-10]=\"!isFloated() && hasPrefix() && size() === 'sm'\"\n [class.inset-s-11]=\"!isFloated() && hasPrefix() && size() === 'base'\"\n [class.inset-s-12]=\"!isFloated() && hasPrefix() && size() === 'lg'\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base' || size() === 'lg'\"\n [class.top-2]=\"isFloated()\"\n [class.-translate-y-4]=\"isFloated()\"\n [class.scale-75]=\"isFloated()\"\n [class.bg-neutral-primary-soft]=\"isFloated() && !disabled() && !labelBg()\"\n [class.bg-neutral-tertiary]=\"isFloated() && disabled() && !labelBg()\"\n [class.text-fg-brand]=\"isFloated() && isFocused() && state() === 'default' && !disabled()\"\n [class.text-body-subtle]=\"\n (isFloated() && !isFocused() && state() === 'default' && !disabled()) || !isFloated()\n \"\n [class.text-fg-danger]=\"isFloated() && state() === 'error' && !disabled()\"\n [class.text-fg-success]=\"isFloated() && state() === 'success' && !disabled()\"\n [class.text-fg-disabled]=\"isFloated() && disabled()\"\n [class.top-1/2]=\"!isFloated()\"\n [class.-translate-y-1/2]=\"!isFloated()\"\n [class.scale-100]=\"!isFloated()\"\n [class.bg-transparent]=\"!isFloated()\"\n [ngClass]=\"isFloated() && labelBg() ? labelBg() : ''\"\n >\n {{ label() }}\n </label>\n\n @if (suffixIcon()) {\n <div\n class=\"absolute inset-e-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"suffixIcon()\"\n [variant]=\"suffixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n </div>\n } @else if (variant() === 'filled') {\n <!-- 2. Filled Floating Label -->\n <div class=\"relative w-full\">\n @if (prefixIcon()) {\n <div\n class=\"absolute inset-s-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"prefixIcon()\"\n [variant]=\"prefixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [type]=\"type()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onModelChange($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [autocomplete]=\"autocomplete()\"\n class=\"block w-full text-heading bg-neutral-tertiary/80 border-0 border-b-2 appearance-none focus:outline-hidden focus:border-brand rounded-t-md transition-colors disabled:opacity-60 disabled:cursor-not-allowed\"\n [class.pt-4]=\"size() === 'sm'\"\n [class.pb-1]=\"size() === 'sm'\"\n [class.px-4]=\"size() === 'sm' && !hasPrefix() && !hasSuffix()\"\n [class.ps-10]=\"size() === 'sm' && hasPrefix()\"\n [class.pe-10]=\"size() === 'sm' && hasSuffix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.pt-5]=\"size() === 'base'\"\n [class.px-5]=\"size() === 'base' && !hasPrefix() && !hasSuffix()\"\n [class.ps-11]=\"size() === 'base' && hasPrefix()\"\n [class.pe-11]=\"size() === 'base' && hasSuffix()\"\n [class.pt-6]=\"size() === 'lg' || size() === 'xl'\"\n [class.pb-2]=\"size() === 'lg' || size() === 'xl'\"\n [class.px-6]=\"(size() === 'lg' || size() === 'xl') && !hasPrefix() && !hasSuffix()\"\n [class.ps-12]=\"(size() === 'lg' || size() === 'xl') && hasPrefix()\"\n [class.pe-12]=\"(size() === 'lg' || size() === 'xl') && hasSuffix()\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.border-default]=\"state() === 'default' && !disabled()\"\n [class.border-brand]=\"isFocused() && state() === 'default' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [ngClass]=\"{ 'pb-1.5': size() === 'base' }\"\n />\n\n <label\n [for]=\"inputId()\"\n class=\"absolute pointer-events-none transition-all duration-200 transform origin-left z-10 select-none\"\n [class.inset-s-4]=\"size() === 'sm' && (isFloated() || !hasPrefix())\"\n [class.inset-s-5]=\"size() === 'base' && (isFloated() || !hasPrefix())\"\n [class.inset-s-6]=\"(size() === 'lg' || size() === 'xl') && (isFloated() || !hasPrefix())\"\n [class.inset-s-10]=\"!isFloated() && hasPrefix() && size() === 'sm'\"\n [class.inset-s-11]=\"!isFloated() && hasPrefix() && size() === 'base'\"\n [class.inset-s-12]=\"!isFloated() && hasPrefix() && size() === 'lg'\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base' || size() === 'lg'\"\n [class.top-3]=\"isFloated()\"\n [class.-translate-y-3]=\"isFloated()\"\n [class.scale-75]=\"isFloated()\"\n [class.text-fg-brand]=\"isFloated() && isFocused() && state() === 'default' && !disabled()\"\n [class.text-body-subtle]=\"\n (isFloated() && !isFocused() && state() === 'default' && !disabled()) || !isFloated()\n \"\n [class.text-fg-danger]=\"isFloated() && state() === 'error' && !disabled()\"\n [class.text-fg-success]=\"isFloated() && state() === 'success' && !disabled()\"\n [class.text-fg-disabled]=\"isFloated() && disabled()\"\n [class.top-1/2]=\"!isFloated()\"\n [class.-translate-y-1/2]=\"!isFloated()\"\n [class.scale-100]=\"!isFloated()\"\n >\n {{ label() }}\n </label>\n\n @if (suffixIcon()) {\n <div\n class=\"absolute inset-e-3.5 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"suffixIcon()\"\n [variant]=\"suffixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n </div>\n } @else {\n <!-- 3. Standard / Underline Floating Label -->\n <div class=\"relative w-full\">\n @if (prefixIcon()) {\n <div\n class=\"absolute inset-s-0 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"prefixIcon()\"\n [variant]=\"prefixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [type]=\"type()\"\n [ngModel]=\"value()\"\n (ngModelChange)=\"onModelChange($event)\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [autocomplete]=\"autocomplete()\"\n class=\"block w-full text-heading bg-transparent border-0 border-b-2 appearance-none focus:outline-hidden focus:border-brand transition-colors disabled:opacity-60 disabled:cursor-not-allowed\"\n [class.pt-3]=\"size() === 'sm'\"\n [class.pb-1]=\"size() === 'sm'\"\n [class.ps-6]=\"hasPrefix()\"\n [class.pe-6]=\"hasSuffix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.pt-4]=\"size() === 'base'\"\n [class.pt-5]=\"size() === 'lg' || size() === 'xl'\"\n [class.pb-2]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.border-default]=\"state() === 'default' && !disabled()\"\n [class.border-brand]=\"isFocused() && state() === 'default' && !disabled()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [ngClass]=\"{ 'pb-1.5': size() === 'base' }\"\n />\n\n <label\n [for]=\"inputId()\"\n class=\"absolute pointer-events-none transition-all duration-200 transform origin-left z-10 select-none\"\n [class.inset-s-0]=\"!hasPrefix() || isFloated()\"\n [class.inset-s-6]=\"!isFloated() && hasPrefix()\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base' || size() === 'lg'\"\n [class.top-2]=\"isFloated()\"\n [class.-translate-y-5]=\"isFloated()\"\n [class.scale-75]=\"isFloated()\"\n [class.text-fg-brand]=\"isFloated() && isFocused() && state() === 'default' && !disabled()\"\n [class.text-body-subtle]=\"\n (isFloated() && !isFocused() && state() === 'default' && !disabled()) || !isFloated()\n \"\n [class.text-fg-danger]=\"isFloated() && state() === 'error' && !disabled()\"\n [class.text-fg-success]=\"isFloated() && state() === 'success' && !disabled()\"\n [class.text-fg-disabled]=\"isFloated() && disabled()\"\n [class.top-1/2]=\"!isFloated()\"\n [class.-translate-y-1/2]=\"!isFloated()\"\n [class.scale-100]=\"!isFloated()\"\n >\n {{ label() }}\n </label>\n\n @if (suffixIcon()) {\n <div\n class=\"absolute inset-e-0 top-1/2 -translate-y-1/2 flex items-center pointer-events-none text-body-subtle z-10\"\n >\n <ol-icon\n [name]=\"suffixIcon()\"\n [variant]=\"suffixVariant()\"\n [size]=\"size() === 'sm' ? 16 : 20\"\n />\n </div>\n }\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger font-medium\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body-subtle\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
12115
|
-
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], autocomplete: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocomplete", required: false }] }], labelBg: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelBg", required: false }] }], prefixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixIcon", required: false }] }], suffixIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixIcon", required: false }] }], prefixVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "prefixVariant", required: false }] }], suffixVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "suffixVariant", required: false }] }] } });
|
|
12116
|
-
|
|
12117
12973
|
let nextInputCounterId = 0;
|
|
12118
12974
|
class OlInputCounter {
|
|
12119
12975
|
/** Id tự sinh khi nơi gọi không truyền id — để <label for> và
|
|
@@ -12513,6 +13369,32 @@ class OlTagInput {
|
|
|
12513
13369
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
12514
13370
|
size = input('base', /* @ts-ignore */
|
|
12515
13371
|
...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
13372
|
+
/**
|
|
13373
|
+
* Nhãn nổi: `label` nằm TRONG khung ở dòng đầu, nổi lên khi focus, đang gõ
|
|
13374
|
+
* hoặc đã có thẻ. Khung giãn nhiều dòng — nhãn nổi không đẩy thẻ.
|
|
13375
|
+
*/
|
|
13376
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
13377
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
13378
|
+
/**
|
|
13379
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
13380
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
13381
|
+
*/
|
|
13382
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
13383
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
13384
|
+
/**
|
|
13385
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
13386
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
13387
|
+
* mép ô thẳng hàng.
|
|
13388
|
+
*/
|
|
13389
|
+
labelWidth = input('', /* @ts-ignore */
|
|
13390
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
13391
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
13392
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
13393
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
13394
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
13395
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
13396
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
13397
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
12516
13398
|
/**
|
|
12517
13399
|
* Gom chiều cao + padding vào MỘT chuỗi thay vì nhiều `[class.X]`: hai class
|
|
12518
13400
|
* 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
|
|
@@ -12627,17 +13509,17 @@ class OlTagInput {
|
|
|
12627
13509
|
this.onTouched();
|
|
12628
13510
|
}
|
|
12629
13511
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlTagInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
12630
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlTagInput, isStandalone: true, selector: "ol-tag-input", inputs: { tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, maxTags: { classPropertyName: "maxTags", publicName: "maxTags", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tags: "tagsChange", disabled: "disabledChange" }, host: { classAttribute: "block w-full" }, providers: [
|
|
13512
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlTagInput, isStandalone: true, selector: "ol-tag-input", inputs: { tags: { classPropertyName: "tags", publicName: "tags", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, maxTags: { classPropertyName: "maxTags", publicName: "maxTags", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { tags: "tagsChange", disabled: "disabledChange" }, host: { classAttribute: "block w-full" }, providers: [
|
|
12631
13513
|
{
|
|
12632
13514
|
provide: NG_VALUE_ACCESSOR,
|
|
12633
13515
|
useExisting: forwardRef(() => OlTagInput),
|
|
12634
13516
|
multi: true,
|
|
12635
13517
|
},
|
|
12636
|
-
], viewQueries: [{ propertyName: "tagInputEl", first: true, predicate: ["tagInputField"], descendants: true }], ngImport: i0, template: "<!--\n B\u1EADc s\u1ED1 `brand-*` \u1EDF \u0111\u00E2y GI\u1EEE c\u00F3 ch\u1EE7 \u0111\u00EDch \u2014 `brand` l\u00E0 b\u1EA3ng Figma C\u00D3:\n dark:focus-within:ring-brand-950 token ring-brand-softer kh\u00F4ng sinh CSS\n dark:bg-brand-950/50 token kh\u00F4ng mang \u0111\u01B0\u1EE3c opacity /50\n dark:border-brand-800/60 c\u00F9ng l\u00FD do\n-->\n<div class=\"w-full\">\n @if (label()) {\n <label [
|
|
13518
|
+
], viewQueries: [{ propertyName: "tagInputEl", first: true, predicate: ["tagInputField"], descendants: true }], ngImport: i0, template: "<!--\n B\u1EADc s\u1ED1 `brand-*` \u1EDF \u0111\u00E2y GI\u1EEE c\u00F3 ch\u1EE7 \u0111\u00EDch \u2014 `brand` l\u00E0 b\u1EA3ng Figma C\u00D3:\n dark:focus-within:ring-brand-950 token ring-brand-softer kh\u00F4ng sinh CSS\n dark:bg-brand-950/50 token kh\u00F4ng mang \u0111\u01B0\u1EE3c opacity /50\n dark:border-brand-800/60 c\u00F9ng l\u00FD do\n-->\n<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label [beside]=\"labelBeside()\" [size]=\"size()\" [text]=\"label()\" [forId]=\"inputId()\" />\n }\n\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [hasValue]=\"tags().length > 0 || inputValue() !== ''\"\n >\n <!-- C\u00F9ng n\u1EC1n v\u1EDBi ol-input (#F9FAFB) \u2014 xem l\u00FD do \u1EDF ol-otp-input. -->\n <div\n (click)=\"focusInput()\"\n class=\"ol-field-box flex flex-wrap items-center gap-1.5 border border-default-medium rounded-md shadow-xs focus-within:ring-2 focus-within:ring-brand-subtle dark:focus-within:ring-brand-950 focus-within:border-brand cursor-text\"\n [class]=\"boxSizeClass()\"\n [class.bg-neutral-secondary-medium]=\"!disabled()\"\n [class.bg-neutral-tertiary]=\"disabled()\"\n [class.dark:bg-dark]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n >\n @for (tag of tags(); track tag; let i = $index) {\n <span\n class=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium bg-brand-softer text-fg-brand-medium dark:bg-brand-950/50 dark:text-fg-brand border border-brand-subtle dark:border-brand-800/60\"\n >\n <span>{{ tag }}</span>\n @if (!disabled()) {\n <button\n type=\"button\"\n (click)=\"removeTag(i); $event.stopPropagation()\"\n [attr.aria-label]=\"'G\u1EE1 ' + tag\"\n class=\"text-fg-brand hover:text-fg-brand-medium dark:text-fg-brand dark:hover:text-fg-brand-subtle p-0.5 rounded-md hover:bg-brand-soft dark:hover:bg-brand-900/60\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n\n <input\n #tagInputField\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n type=\"text\"\n [disabled]=\"disabled()\"\n [placeholder]=\"tags().length === 0 ? placeholder() : ''\"\n [ngModel]=\"inputValue()\"\n (ngModelChange)=\"inputValue.set($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"markTouched()\"\n class=\"flex-1 min-w-30 bg-transparent border-0 text-heading outline-hidden py-1 px-1 placeholder:text-body-subtle\"\n [class]=\"fieldTextClass()\"\n />\n </div>\n </ol-field-frame>\n\n @if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
12637
13519
|
}
|
|
12638
13520
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlTagInput, decorators: [{
|
|
12639
13521
|
type: Component,
|
|
12640
|
-
args: [{ selector: 'ol-tag-input', standalone: true, imports: [CommonModule, FormsModule, OlIcon], host: {
|
|
13522
|
+
args: [{ selector: 'ol-tag-input', standalone: true, imports: [CommonModule, FormsModule, OlIcon, OlFieldFrame, OlFieldLabel], host: {
|
|
12641
13523
|
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
12642
13524
|
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
12643
13525
|
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
@@ -12649,8 +13531,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
12649
13531
|
useExisting: forwardRef(() => OlTagInput),
|
|
12650
13532
|
multi: true,
|
|
12651
13533
|
},
|
|
12652
|
-
], template: "<!--\n B\u1EADc s\u1ED1 `brand-*` \u1EDF \u0111\u00E2y GI\u1EEE c\u00F3 ch\u1EE7 \u0111\u00EDch \u2014 `brand` l\u00E0 b\u1EA3ng Figma C\u00D3:\n dark:focus-within:ring-brand-950 token ring-brand-softer kh\u00F4ng sinh CSS\n dark:bg-brand-950/50 token kh\u00F4ng mang \u0111\u01B0\u1EE3c opacity /50\n dark:border-brand-800/60 c\u00F9ng l\u00FD do\n-->\n<div class=\"w-full\">\n @if (label()) {\n <label [
|
|
12653
|
-
}], ctorParameters: () => [], propDecorators: { tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }, { type: i0.Output, args: ["tagsChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], maxTags: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTags", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], tagInputEl: [{
|
|
13534
|
+
], template: "<!--\n B\u1EADc s\u1ED1 `brand-*` \u1EDF \u0111\u00E2y GI\u1EEE c\u00F3 ch\u1EE7 \u0111\u00EDch \u2014 `brand` l\u00E0 b\u1EA3ng Figma C\u00D3:\n dark:focus-within:ring-brand-950 token ring-brand-softer kh\u00F4ng sinh CSS\n dark:bg-brand-950/50 token kh\u00F4ng mang \u0111\u01B0\u1EE3c opacity /50\n dark:border-brand-800/60 c\u00F9ng l\u00FD do\n-->\n<div class=\"w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label [beside]=\"labelBeside()\" [size]=\"size()\" [text]=\"label()\" [forId]=\"inputId()\" />\n }\n\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [disabled]=\"disabled()\"\n [hasValue]=\"tags().length > 0 || inputValue() !== ''\"\n >\n <!-- C\u00F9ng n\u1EC1n v\u1EDBi ol-input (#F9FAFB) \u2014 xem l\u00FD do \u1EDF ol-otp-input. -->\n <div\n (click)=\"focusInput()\"\n class=\"ol-field-box flex flex-wrap items-center gap-1.5 border border-default-medium rounded-md shadow-xs focus-within:ring-2 focus-within:ring-brand-subtle dark:focus-within:ring-brand-950 focus-within:border-brand cursor-text\"\n [class]=\"boxSizeClass()\"\n [class.bg-neutral-secondary-medium]=\"!disabled()\"\n [class.bg-neutral-tertiary]=\"disabled()\"\n [class.dark:bg-dark]=\"disabled()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n >\n @for (tag of tags(); track tag; let i = $index) {\n <span\n class=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-medium bg-brand-softer text-fg-brand-medium dark:bg-brand-950/50 dark:text-fg-brand border border-brand-subtle dark:border-brand-800/60\"\n >\n <span>{{ tag }}</span>\n @if (!disabled()) {\n <button\n type=\"button\"\n (click)=\"removeTag(i); $event.stopPropagation()\"\n [attr.aria-label]=\"'G\u1EE1 ' + tag\"\n class=\"text-fg-brand hover:text-fg-brand-medium dark:text-fg-brand dark:hover:text-fg-brand-subtle p-0.5 rounded-md hover:bg-brand-soft dark:hover:bg-brand-900/60\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n\n <input\n #tagInputField\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n type=\"text\"\n [disabled]=\"disabled()\"\n [placeholder]=\"tags().length === 0 ? placeholder() : ''\"\n [ngModel]=\"inputValue()\"\n (ngModelChange)=\"inputValue.set($event)\"\n (keydown)=\"onKeyDown($event)\"\n (blur)=\"markTouched()\"\n class=\"flex-1 min-w-30 bg-transparent border-0 text-heading outline-hidden py-1 px-1 placeholder:text-body-subtle\"\n [class]=\"fieldTextClass()\"\n />\n </div>\n </ol-field-frame>\n\n @if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
13535
|
+
}], ctorParameters: () => [], propDecorators: { tags: [{ type: i0.Input, args: [{ isSignal: true, alias: "tags", required: false }] }, { type: i0.Output, args: ["tagsChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], maxTags: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTags", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], tagInputEl: [{
|
|
12654
13536
|
type: ViewChild,
|
|
12655
13537
|
args: ['tagInputField']
|
|
12656
13538
|
}] } });
|
|
@@ -12798,6 +13680,32 @@ class OlTimepicker {
|
|
|
12798
13680
|
*/
|
|
12799
13681
|
ariaLabel = input('', /* @ts-ignore */
|
|
12800
13682
|
...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
13683
|
+
/**
|
|
13684
|
+
* Nhãn nổi: `label` nằm TRONG khung (sau icon đồng hồ), nổi lên khi focus,
|
|
13685
|
+
* mở bảng chọn hoặc đã có giờ. Có addon thì nhãn nằm trong ô, không trùm addon.
|
|
13686
|
+
*/
|
|
13687
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
13688
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
13689
|
+
/**
|
|
13690
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
13691
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
13692
|
+
*/
|
|
13693
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
13694
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
13695
|
+
/**
|
|
13696
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
13697
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
13698
|
+
* mép ô thẳng hàng.
|
|
13699
|
+
*/
|
|
13700
|
+
labelWidth = input('', /* @ts-ignore */
|
|
13701
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
13702
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
13703
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
13704
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
13705
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
13706
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
13707
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
13708
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
12801
13709
|
placeholder = input('00:00', /* @ts-ignore */
|
|
12802
13710
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
12803
13711
|
format = input('24h', /* @ts-ignore */
|
|
@@ -13164,17 +14072,17 @@ class OlTimepicker {
|
|
|
13164
14072
|
this.disabled.set(isDisabled);
|
|
13165
14073
|
}
|
|
13166
14074
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlTimepicker, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
13167
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlTimepicker, isStandalone: true, selector: "ol-timepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, showPresets: { classPropertyName: "showPresets", publicName: "showPresets", isSignal: true, isRequired: false, transformFunction: null }, addonType: { classPropertyName: "addonType", publicName: "addonType", isSignal: true, isRequired: false, transformFunction: null }, addonText: { classPropertyName: "addonText", publicName: "addonText", isSignal: true, isRequired: false, transformFunction: null }, addonIcon: { classPropertyName: "addonIcon", publicName: "addonIcon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", timeChange: "timeChange", timestampChange: "timestampChange", isoChange: "isoChange", timeObjectChange: "timeObjectChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block w-full" }, providers: [
|
|
14075
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlTimepicker, isStandalone: true, selector: "ol-timepicker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, showPresets: { classPropertyName: "showPresets", publicName: "showPresets", isSignal: true, isRequired: false, transformFunction: null }, addonType: { classPropertyName: "addonType", publicName: "addonType", isSignal: true, isRequired: false, transformFunction: null }, addonText: { classPropertyName: "addonText", publicName: "addonText", isSignal: true, isRequired: false, transformFunction: null }, addonIcon: { classPropertyName: "addonIcon", publicName: "addonIcon", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", timeChange: "timeChange", timestampChange: "timestampChange", isoChange: "isoChange", timeObjectChange: "timeObjectChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block w-full" }, providers: [
|
|
13168
14076
|
{
|
|
13169
14077
|
provide: NG_VALUE_ACCESSOR,
|
|
13170
14078
|
useExisting: forwardRef(() => OlTimepicker),
|
|
13171
14079
|
multi: true,
|
|
13172
14080
|
},
|
|
13173
|
-
], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "hourAreaRef", first: true, predicate: ["hourArea"], descendants: true, isSignal: true }, { propertyName: "minuteAreaRef", first: true, predicate: ["minuteArea"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Bo g\u00F3c: Figma `ol-timepicker` (#1149:52243) d\u00F9ng 12px cho C\u1EA2 B\u1ED0N size\n (rounded-md). Code tr\u01B0\u1EDBc \u0111\u00E2y d\u00F9ng rounded-lg 16px cho sm/base v\u00E0\n rounded-md 24px cho lg \u2014 c\u00F9ng l\u1ED7i \u0111\u00E3 s\u1EEDa \u1EDF `ol-input`. -->\n<div class=\"relative w-full\">\n @if (label()) {\n <div class=\"flex items-center justify-between mb-1.5\">\n <label\n [id]=\"labelId()\"\n [for]=\"inputId()\"\n class=\"inline-flex items-center gap-1 text-sm font-medium text-heading\"\n >\n <span>{{ label() }}</span>\n @if (required() || showAsterisk()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n </div>\n }\n\n <!-- Trigger Box Container with Add-on Support -->\n <div class=\"flex items-stretch w-full shadow-xs\">\n @if (addonType() === 'left') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-e-0 text-body shrink-0 rounded-s-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n [attr.aria-labelledby]=\"label() ? labelId() : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggleDropdown()\"\n (keydown.enter)=\"toggleDropdown()\"\n (keydown.space)=\"toggleDropdown(); $event.preventDefault()\"\n class=\"relative flex items-center justify-between flex-1 w-full border cursor-pointer select-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-brand-subtle\"\n [ngClass]=\"fieldTextClasses()\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"addonType() === 'none'\"\n [class.rounded-e-md]=\"addonType() === 'left'\"\n [class.rounded-s-md]=\"addonType() === 'right'\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger-subtle]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.border-success-subtle]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n >\n <div class=\"flex items-center gap-2.5 overflow-hidden min-w-0\">\n @if (addonType() === 'none') {\n <!-- Icon trong \u00F4 khi kh\u00F4ng c\u00F3 addon; `addonIcon` \u0111\u1ED5i \u0111\u01B0\u1EE3c. -->\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n <span\n class=\"truncate font-normal\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-body-subtle]=\"!value() && state() !== 'error'\"\n [class.text-body]=\"!value() && state() === 'error'\"\n [class.text-heading]=\"!!value()\"\n >\n {{ value() || placeholder() }}\n </span>\n </div>\n\n <div class=\"flex items-center gap-1.5 shrink-0 ms-1\">\n @if (value() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 gi\u1EDD \u0111\u00E3 ch\u1ECDn\"\n class=\"p-0.5 text-body-subtle hover:text-heading rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer flex items-center justify-center\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n <ol-icon name=\"chevron-down\" size=\"sm\" />\n </div>\n </div>\n\n @if (addonType() === 'right') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-s-0 text-body shrink-0 rounded-e-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n </div>\n\n <!-- Popover Panel -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown bg-neutral-primary border border-default rounded-md shadow-lg p-3 min-w-70 select-none overflow-y-auto overflow-x-hidden max-h-[var(--ol-overlay-max-height,28rem)] animate-in fade-in zoom-in-95 duration-150\"\n >\n @if (showPresets()) {\n <!-- Preset quick times -->\n <div class=\"mb-3 pb-2 border-b border-default\">\n <span class=\"text-xs font-semibold text-body-subtle block mb-1.5\">M\u1ED1c gi\u1EDD nhanh</span>\n <div class=\"grid grid-cols-4 gap-1 max-h-24 overflow-y-auto overflow-x-hidden pr-1\">\n @for (preset of presetTimes; track preset) {\n <button\n type=\"button\"\n (click)=\"selectPreset(preset)\"\n class=\"px-2 py-1 text-xs rounded-md text-center transition-colors font-medium\"\n [class.hover:bg-neutral-tertiary]=\"value() !== preset\"\n [class.bg-brand-softer]=\"value() === preset\"\n [class.text-fg-brand]=\"value() === preset\"\n [class.text-body]=\"value() !== preset\"\n >\n {{ preset }}\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Columns Time Picker -->\n <div class=\"flex items-center justify-center gap-2 py-2\">\n <!-- Hour Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Gi\u1EDD</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #hourArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @if (format() === '12h') {\n @for (h of hours12; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n } @else {\n @for (h of hours24; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n <span class=\"text-lg font-bold text-heading mt-4\">:</span>\n\n <!-- Minute Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Ph\u00FAt</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #minuteArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @for (m of minutes; track m) {\n @if (m % minuteStep() === 0) {\n <button\n type=\"button\"\n (click)=\"selectMinute(m)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedMinute() === m\"\n [class.text-white]=\"selectedMinute() === m\"\n [class.font-semibold]=\"selectedMinute() === m\"\n [class.scale-105]=\"selectedMinute() === m\"\n [class.shadow-xs]=\"selectedMinute() === m\"\n [class.hover:bg-neutral-tertiary]=\"selectedMinute() !== m\"\n [class.text-heading]=\"selectedMinute() !== m\"\n >\n {{ pad(m) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n @if (format() === '12h') {\n <!-- AM/PM Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Bu\u1ED5i</span>\n <div\n class=\"h-44 w-16 flex flex-col justify-center gap-2 border border-default rounded-lg p-1\"\n >\n <button\n type=\"button\"\n (click)=\"selectPeriod('AM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'AM'\"\n [class.text-white]=\"selectedPeriod() === 'AM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'AM'\"\n [class.text-heading]=\"selectedPeriod() !== 'AM'\"\n >\n AM\n </button>\n <button\n type=\"button\"\n (click)=\"selectPeriod('PM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'PM'\"\n [class.text-white]=\"selectedPeriod() === 'PM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'PM'\"\n [class.text-heading]=\"selectedPeriod() !== 'PM'\"\n >\n PM\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Action Footer -->\n <div class=\"flex items-center justify-between pt-3 mt-2 border-t border-default\">\n <button\n type=\"button\"\n (click)=\"cancel()\"\n class=\"px-3 py-1.5 text-xs font-medium text-body hover:text-heading hover:bg-neutral-tertiary rounded-lg transition-colors\"\n >\n H\u1EE7y\n </button>\n <button\n type=\"button\"\n (click)=\"apply()\"\n class=\"px-3 py-1.5 text-xs font-medium text-white bg-brand hover:bg-brand-strong rounded-lg shadow-xs transition-colors\"\n >\n \u00C1p d\u1EE5ng\n </button>\n </div>\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: OlScrollArea, selector: "ol-scroll-area", inputs: ["scrollbar", "axis", "smooth", "innerClass"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14081
|
+
], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "hourAreaRef", first: true, predicate: ["hourArea"], descendants: true, isSignal: true }, { propertyName: "minuteAreaRef", first: true, predicate: ["minuteArea"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Bo g\u00F3c: Figma `ol-timepicker` (#1149:52243) d\u00F9ng 12px cho C\u1EA2 B\u1ED0N size\n (rounded-md). Code tr\u01B0\u1EDBc \u0111\u00E2y d\u00F9ng rounded-lg 16px cho sm/base v\u00E0\n rounded-md 24px cho lg \u2014 c\u00F9ng l\u1ED7i \u0111\u00E3 s\u1EEDa \u1EDF `ol-input`. -->\n<div class=\"relative w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n\n <!-- Trigger Box Container with Add-on Support -->\n <!-- Nh\u00E3n n\u1ED5i ki\u1EC3u filled/standard th\u00EAm h\u00E0ng 20px ph\u00EDa tr\u00EAn khung: addon\n ph\u1EA3i b\u00E1m \u0110\u00C1Y cho th\u1EB3ng h\u00E0ng v\u1EDBi khung nh\u1EADp, kh\u00F4ng b\u00E1m h\u00E0ng nh\u00E3n. -->\n <div\n class=\"flex w-full shadow-xs\"\n [class.items-stretch]=\"!floatingLabel()\"\n [class.items-end]=\"floatingLabel()\"\n >\n @if (addonType() === 'left') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-e-0 text-body shrink-0 rounded-s-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n\n <ol-field-frame\n class=\"min-w-0 flex-1\"\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"!!value()\"\n [open]=\"isOpen()\"\n >\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n [attr.aria-labelledby]=\"label() ? labelId() : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggleDropdown()\"\n (keydown.enter)=\"toggleDropdown()\"\n (keydown.space)=\"toggleDropdown(); $event.preventDefault()\"\n class=\"ol-field-box relative flex items-center justify-between flex-1 w-full border cursor-pointer select-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-brand-subtle\"\n [ngClass]=\"fieldTextClasses()\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"addonType() === 'none'\"\n [class.rounded-e-md]=\"addonType() === 'left'\"\n [class.rounded-s-md]=\"addonType() === 'right'\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger-subtle]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.border-success-subtle]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n >\n <div class=\"flex items-center gap-2 overflow-hidden min-w-0\">\n @if (addonType() === 'none') {\n <!-- Icon trong \u00F4 khi kh\u00F4ng c\u00F3 addon; `addonIcon` \u0111\u1ED5i \u0111\u01B0\u1EE3c. -->\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n <span\n class=\"truncate font-normal\"\n data-ol-field-text\n [attr.data-ol-field-placeholder]=\"value() ? null : ''\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-body-subtle]=\"!value() && state() !== 'error'\"\n [class.text-body]=\"!value() && state() === 'error'\"\n [class.text-heading]=\"!!value()\"\n >\n {{ value() || placeholder() }}\n </span>\n </div>\n\n <div class=\"flex items-center gap-1.5 shrink-0 ms-1\">\n @if (value() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 gi\u1EDD \u0111\u00E3 ch\u1ECDn\"\n class=\"p-0.5 text-body-subtle hover:text-heading rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer flex items-center justify-center\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n <ol-icon name=\"chevron-down\" size=\"sm\" />\n </div>\n </div>\n </ol-field-frame>\n\n @if (addonType() === 'right') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-s-0 text-body shrink-0 rounded-e-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n </div>\n\n <!-- Popover Panel -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown bg-neutral-primary border border-default rounded-md shadow-lg p-3 min-w-70 select-none overflow-y-auto overflow-x-hidden max-h-[var(--ol-overlay-max-height,28rem)] animate-in fade-in zoom-in-95 duration-150\"\n >\n @if (showPresets()) {\n <!-- Preset quick times -->\n <div class=\"mb-3 pb-2 border-b border-default\">\n <span class=\"text-xs font-semibold text-body-subtle block mb-1.5\">M\u1ED1c gi\u1EDD nhanh</span>\n <div class=\"grid grid-cols-4 gap-1 max-h-24 overflow-y-auto overflow-x-hidden pr-1\">\n @for (preset of presetTimes; track preset) {\n <button\n type=\"button\"\n (click)=\"selectPreset(preset)\"\n class=\"px-2 py-1 text-xs rounded-md text-center transition-colors font-medium\"\n [class.hover:bg-neutral-tertiary]=\"value() !== preset\"\n [class.bg-brand-softer]=\"value() === preset\"\n [class.text-fg-brand]=\"value() === preset\"\n [class.text-body]=\"value() !== preset\"\n >\n {{ preset }}\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Columns Time Picker -->\n <div class=\"flex items-center justify-center gap-2 py-2\">\n <!-- Hour Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Gi\u1EDD</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #hourArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @if (format() === '12h') {\n @for (h of hours12; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n } @else {\n @for (h of hours24; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n <span class=\"text-lg font-bold text-heading mt-4\">:</span>\n\n <!-- Minute Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Ph\u00FAt</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #minuteArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @for (m of minutes; track m) {\n @if (m % minuteStep() === 0) {\n <button\n type=\"button\"\n (click)=\"selectMinute(m)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedMinute() === m\"\n [class.text-white]=\"selectedMinute() === m\"\n [class.font-semibold]=\"selectedMinute() === m\"\n [class.scale-105]=\"selectedMinute() === m\"\n [class.shadow-xs]=\"selectedMinute() === m\"\n [class.hover:bg-neutral-tertiary]=\"selectedMinute() !== m\"\n [class.text-heading]=\"selectedMinute() !== m\"\n >\n {{ pad(m) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n @if (format() === '12h') {\n <!-- AM/PM Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Bu\u1ED5i</span>\n <div\n class=\"h-44 w-16 flex flex-col justify-center gap-2 border border-default rounded-lg p-1\"\n >\n <button\n type=\"button\"\n (click)=\"selectPeriod('AM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'AM'\"\n [class.text-white]=\"selectedPeriod() === 'AM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'AM'\"\n [class.text-heading]=\"selectedPeriod() !== 'AM'\"\n >\n AM\n </button>\n <button\n type=\"button\"\n (click)=\"selectPeriod('PM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'PM'\"\n [class.text-white]=\"selectedPeriod() === 'PM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'PM'\"\n [class.text-heading]=\"selectedPeriod() !== 'PM'\"\n >\n PM\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Action Footer -->\n <div class=\"flex items-center justify-between pt-3 mt-2 border-t border-default\">\n <button\n type=\"button\"\n (click)=\"cancel()\"\n class=\"px-3 py-1.5 text-xs font-medium text-body hover:text-heading hover:bg-neutral-tertiary rounded-lg transition-colors\"\n >\n H\u1EE7y\n </button>\n <button\n type=\"button\"\n (click)=\"apply()\"\n class=\"px-3 py-1.5 text-xs font-medium text-white bg-brand hover:bg-brand-strong rounded-lg shadow-xs transition-colors\"\n >\n \u00C1p d\u1EE5ng\n </button>\n </div>\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: OlScrollArea, selector: "ol-scroll-area", inputs: ["scrollbar", "axis", "smooth", "innerClass"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13174
14082
|
}
|
|
13175
14083
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlTimepicker, decorators: [{
|
|
13176
14084
|
type: Component,
|
|
13177
|
-
args: [{ selector: 'ol-timepicker', standalone: true, imports: [CommonModule, FormsModule, OlScrollArea, OlIcon], host: {
|
|
14085
|
+
args: [{ selector: 'ol-timepicker', standalone: true, imports: [CommonModule, FormsModule, OlScrollArea, OlIcon, OlFieldFrame, OlFieldLabel], host: {
|
|
13178
14086
|
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
13179
14087
|
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
13180
14088
|
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
@@ -13186,8 +14094,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
13186
14094
|
useExisting: forwardRef(() => OlTimepicker),
|
|
13187
14095
|
multi: true,
|
|
13188
14096
|
},
|
|
13189
|
-
], template: "<!-- Bo g\u00F3c: Figma `ol-timepicker` (#1149:52243) d\u00F9ng 12px cho C\u1EA2 B\u1ED0N size\n (rounded-md). Code tr\u01B0\u1EDBc \u0111\u00E2y d\u00F9ng rounded-lg 16px cho sm/base v\u00E0\n rounded-md 24px cho lg \u2014 c\u00F9ng l\u1ED7i \u0111\u00E3 s\u1EEDa \u1EDF `ol-input`. -->\n<div class=\"relative w-full\">\n @if (label()) {\n <div class=\"flex items-center justify-between mb-1.5\">\n <label\n [id]=\"labelId()\"\n [for]=\"inputId()\"\n class=\"inline-flex items-center gap-1 text-sm font-medium text-heading\"\n >\n <span>{{ label() }}</span>\n @if (required() || showAsterisk()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n </div>\n }\n\n <!-- Trigger Box Container with Add-on Support -->\n <div class=\"flex items-stretch w-full shadow-xs\">\n @if (addonType() === 'left') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-e-0 text-body shrink-0 rounded-s-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n [attr.aria-labelledby]=\"label() ? labelId() : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggleDropdown()\"\n (keydown.enter)=\"toggleDropdown()\"\n (keydown.space)=\"toggleDropdown(); $event.preventDefault()\"\n class=\"relative flex items-center justify-between flex-1 w-full border cursor-pointer select-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-brand-subtle\"\n [ngClass]=\"fieldTextClasses()\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"addonType() === 'none'\"\n [class.rounded-e-md]=\"addonType() === 'left'\"\n [class.rounded-s-md]=\"addonType() === 'right'\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger-subtle]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.border-success-subtle]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n >\n <div class=\"flex items-center gap-2.5 overflow-hidden min-w-0\">\n @if (addonType() === 'none') {\n <!-- Icon trong \u00F4 khi kh\u00F4ng c\u00F3 addon; `addonIcon` \u0111\u1ED5i \u0111\u01B0\u1EE3c. -->\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n <span\n class=\"truncate font-normal\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-body-subtle]=\"!value() && state() !== 'error'\"\n [class.text-body]=\"!value() && state() === 'error'\"\n [class.text-heading]=\"!!value()\"\n >\n {{ value() || placeholder() }}\n </span>\n </div>\n\n <div class=\"flex items-center gap-1.5 shrink-0 ms-1\">\n @if (value() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 gi\u1EDD \u0111\u00E3 ch\u1ECDn\"\n class=\"p-0.5 text-body-subtle hover:text-heading rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer flex items-center justify-center\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n <ol-icon name=\"chevron-down\" size=\"sm\" />\n </div>\n </div>\n\n @if (addonType() === 'right') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-s-0 text-body shrink-0 rounded-e-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n </div>\n\n <!-- Popover Panel -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown bg-neutral-primary border border-default rounded-md shadow-lg p-3 min-w-70 select-none overflow-y-auto overflow-x-hidden max-h-[var(--ol-overlay-max-height,28rem)] animate-in fade-in zoom-in-95 duration-150\"\n >\n @if (showPresets()) {\n <!-- Preset quick times -->\n <div class=\"mb-3 pb-2 border-b border-default\">\n <span class=\"text-xs font-semibold text-body-subtle block mb-1.5\">M\u1ED1c gi\u1EDD nhanh</span>\n <div class=\"grid grid-cols-4 gap-1 max-h-24 overflow-y-auto overflow-x-hidden pr-1\">\n @for (preset of presetTimes; track preset) {\n <button\n type=\"button\"\n (click)=\"selectPreset(preset)\"\n class=\"px-2 py-1 text-xs rounded-md text-center transition-colors font-medium\"\n [class.hover:bg-neutral-tertiary]=\"value() !== preset\"\n [class.bg-brand-softer]=\"value() === preset\"\n [class.text-fg-brand]=\"value() === preset\"\n [class.text-body]=\"value() !== preset\"\n >\n {{ preset }}\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Columns Time Picker -->\n <div class=\"flex items-center justify-center gap-2 py-2\">\n <!-- Hour Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Gi\u1EDD</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #hourArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @if (format() === '12h') {\n @for (h of hours12; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n } @else {\n @for (h of hours24; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n <span class=\"text-lg font-bold text-heading mt-4\">:</span>\n\n <!-- Minute Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Ph\u00FAt</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #minuteArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @for (m of minutes; track m) {\n @if (m % minuteStep() === 0) {\n <button\n type=\"button\"\n (click)=\"selectMinute(m)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedMinute() === m\"\n [class.text-white]=\"selectedMinute() === m\"\n [class.font-semibold]=\"selectedMinute() === m\"\n [class.scale-105]=\"selectedMinute() === m\"\n [class.shadow-xs]=\"selectedMinute() === m\"\n [class.hover:bg-neutral-tertiary]=\"selectedMinute() !== m\"\n [class.text-heading]=\"selectedMinute() !== m\"\n >\n {{ pad(m) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n @if (format() === '12h') {\n <!-- AM/PM Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Bu\u1ED5i</span>\n <div\n class=\"h-44 w-16 flex flex-col justify-center gap-2 border border-default rounded-lg p-1\"\n >\n <button\n type=\"button\"\n (click)=\"selectPeriod('AM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'AM'\"\n [class.text-white]=\"selectedPeriod() === 'AM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'AM'\"\n [class.text-heading]=\"selectedPeriod() !== 'AM'\"\n >\n AM\n </button>\n <button\n type=\"button\"\n (click)=\"selectPeriod('PM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'PM'\"\n [class.text-white]=\"selectedPeriod() === 'PM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'PM'\"\n [class.text-heading]=\"selectedPeriod() !== 'PM'\"\n >\n PM\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Action Footer -->\n <div class=\"flex items-center justify-between pt-3 mt-2 border-t border-default\">\n <button\n type=\"button\"\n (click)=\"cancel()\"\n class=\"px-3 py-1.5 text-xs font-medium text-body hover:text-heading hover:bg-neutral-tertiary rounded-lg transition-colors\"\n >\n H\u1EE7y\n </button>\n <button\n type=\"button\"\n (click)=\"apply()\"\n class=\"px-3 py-1.5 text-xs font-medium text-white bg-brand hover:bg-brand-strong rounded-lg shadow-xs transition-colors\"\n >\n \u00C1p d\u1EE5ng\n </button>\n </div>\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
13190
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], showPresets: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPresets", required: false }] }], addonType: [{ type: i0.Input, args: [{ isSignal: true, alias: "addonType", required: false }] }], addonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "addonText", required: false }] }], addonIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "addonIcon", required: false }] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], hourAreaRef: [{ type: i0.ViewChild, args: ['hourArea', { isSignal: true }] }], minuteAreaRef: [{ type: i0.ViewChild, args: ['minuteArea', { isSignal: true }] }], timeChange: [{ type: i0.Output, args: ["timeChange"] }], timestampChange: [{ type: i0.Output, args: ["timestampChange"] }], isoChange: [{ type: i0.Output, args: ["isoChange"] }], timeObjectChange: [{ type: i0.Output, args: ["timeObjectChange"] }], onDocumentClick: [{
|
|
14097
|
+
], template: "<!-- Bo g\u00F3c: Figma `ol-timepicker` (#1149:52243) d\u00F9ng 12px cho C\u1EA2 B\u1ED0N size\n (rounded-md). Code tr\u01B0\u1EDBc \u0111\u00E2y d\u00F9ng rounded-lg 16px cho sm/base v\u00E0\n rounded-md 24px cho lg \u2014 c\u00F9ng l\u1ED7i \u0111\u00E3 s\u1EEDa \u1EDF `ol-input`. -->\n<div class=\"relative w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"size()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n\n <!-- Trigger Box Container with Add-on Support -->\n <!-- Nh\u00E3n n\u1ED5i ki\u1EC3u filled/standard th\u00EAm h\u00E0ng 20px ph\u00EDa tr\u00EAn khung: addon\n ph\u1EA3i b\u00E1m \u0110\u00C1Y cho th\u1EB3ng h\u00E0ng v\u1EDBi khung nh\u1EADp, kh\u00F4ng b\u00E1m h\u00E0ng nh\u00E3n. -->\n <div\n class=\"flex w-full shadow-xs\"\n [class.items-stretch]=\"!floatingLabel()\"\n [class.items-end]=\"floatingLabel()\"\n >\n @if (addonType() === 'left') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-e-0 text-body shrink-0 rounded-s-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n\n <ol-field-frame\n class=\"min-w-0 flex-1\"\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [labelId]=\"labelId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"size()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"!!value()\"\n [open]=\"isOpen()\"\n >\n <div\n [id]=\"inputId()\"\n role=\"combobox\"\n [attr.aria-labelledby]=\"label() ? labelId() : null\"\n [attr.aria-label]=\"label() ? null : ariaLabel() || null\"\n [attr.aria-expanded]=\"isOpen()\"\n aria-haspopup=\"listbox\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n (click)=\"toggleDropdown()\"\n (keydown.enter)=\"toggleDropdown()\"\n (keydown.space)=\"toggleDropdown(); $event.preventDefault()\"\n class=\"ol-field-box relative flex items-center justify-between flex-1 w-full border cursor-pointer select-none focus:outline-hidden focus-visible:ring-2 focus-visible:ring-brand-subtle\"\n [ngClass]=\"fieldTextClasses()\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"addonType() === 'none'\"\n [class.rounded-e-md]=\"addonType() === 'left'\"\n [class.rounded-s-md]=\"addonType() === 'right'\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger-subtle]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.text-fg-danger-strong]=\"state() === 'error' && !disabled()\"\n [class.border-success-subtle]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.text-fg-success-strong]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n >\n <div class=\"flex items-center gap-2 overflow-hidden min-w-0\">\n @if (addonType() === 'none') {\n <!-- Icon trong \u00F4 khi kh\u00F4ng c\u00F3 addon; `addonIcon` \u0111\u1ED5i \u0111\u01B0\u1EE3c. -->\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n <span\n class=\"truncate font-normal\"\n data-ol-field-text\n [attr.data-ol-field-placeholder]=\"value() ? null : ''\"\n [class.text-sm]=\"size() === 'sm' || size() === 'base'\"\n [class.text-base]=\"size() === 'lg' || size() === 'xl'\"\n [class.text-body-subtle]=\"!value() && state() !== 'error'\"\n [class.text-body]=\"!value() && state() === 'error'\"\n [class.text-heading]=\"!!value()\"\n >\n {{ value() || placeholder() }}\n </span>\n </div>\n\n <div class=\"flex items-center gap-1.5 shrink-0 ms-1\">\n @if (value() && !disabled() && !readonly()) {\n <button\n type=\"button\"\n (click)=\"clear($event)\"\n aria-label=\"Xo\u00E1 gi\u1EDD \u0111\u00E3 ch\u1ECDn\"\n class=\"p-0.5 text-body-subtle hover:text-heading rounded-full hover:bg-neutral-tertiary-medium transition-colors cursor-pointer flex items-center justify-center\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n <ol-icon name=\"chevron-down\" size=\"sm\" />\n </div>\n </div>\n </ol-field-frame>\n\n @if (addonType() === 'right') {\n <div\n class=\"flex items-center justify-center px-3 bg-neutral-tertiary border border-default border-s-0 text-body shrink-0 rounded-e-md\"\n [class.h-9]=\"size() === 'sm'\"\n [class.h-10]=\"size() === 'base'\"\n [class.h-12]=\"size() === 'lg'\"\n [class.h-13]=\"size() === 'xl'\"\n >\n @if (addonText()) {\n <span class=\"text-xs font-medium\">{{ addonText() }}</span>\n } @else {\n <ol-icon [name]=\"addonIcon() || 'clock-circle'\" size=\"sm\" />\n }\n </div>\n }\n </div>\n\n <!-- Popover Panel -->\n @if (isOpen()) {\n <div\n #panel\n class=\"absolute z-dropdown bg-neutral-primary border border-default rounded-md shadow-lg p-3 min-w-70 select-none overflow-y-auto overflow-x-hidden max-h-[var(--ol-overlay-max-height,28rem)] animate-in fade-in zoom-in-95 duration-150\"\n >\n @if (showPresets()) {\n <!-- Preset quick times -->\n <div class=\"mb-3 pb-2 border-b border-default\">\n <span class=\"text-xs font-semibold text-body-subtle block mb-1.5\">M\u1ED1c gi\u1EDD nhanh</span>\n <div class=\"grid grid-cols-4 gap-1 max-h-24 overflow-y-auto overflow-x-hidden pr-1\">\n @for (preset of presetTimes; track preset) {\n <button\n type=\"button\"\n (click)=\"selectPreset(preset)\"\n class=\"px-2 py-1 text-xs rounded-md text-center transition-colors font-medium\"\n [class.hover:bg-neutral-tertiary]=\"value() !== preset\"\n [class.bg-brand-softer]=\"value() === preset\"\n [class.text-fg-brand]=\"value() === preset\"\n [class.text-body]=\"value() !== preset\"\n >\n {{ preset }}\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Columns Time Picker -->\n <div class=\"flex items-center justify-center gap-2 py-2\">\n <!-- Hour Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Gi\u1EDD</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #hourArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @if (format() === '12h') {\n @for (h of hours12; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n } @else {\n @for (h of hours24; track h) {\n <button\n type=\"button\"\n (click)=\"selectHour(h)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedHour() === h\"\n [class.text-white]=\"selectedHour() === h\"\n [class.font-semibold]=\"selectedHour() === h\"\n [class.scale-105]=\"selectedHour() === h\"\n [class.shadow-xs]=\"selectedHour() === h\"\n [class.hover:bg-neutral-tertiary]=\"selectedHour() !== h\"\n [class.text-heading]=\"selectedHour() !== h\"\n >\n {{ pad(h) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n <span class=\"text-lg font-bold text-heading mt-4\">:</span>\n\n <!-- Minute Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Ph\u00FAt</span>\n <div\n class=\"relative group/col w-16 h-44 rounded-lg overflow-hidden border border-default\"\n >\n <!-- Top Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 top-0 h-6 bg-gradient-to-b from-neutral-primary to-transparent z-10\"\n ></div>\n\n <ol-scroll-area\n #minuteArea\n scrollbar=\"hidden\"\n [smooth]=\"true\"\n innerClass=\"p-1 py-4 space-y-1\"\n >\n @for (m of minutes; track m) {\n @if (m % minuteStep() === 0) {\n <button\n type=\"button\"\n (click)=\"selectMinute(m)\"\n class=\"w-full py-1 text-center text-sm rounded-md transition-all font-medium\"\n [class.bg-brand]=\"selectedMinute() === m\"\n [class.text-white]=\"selectedMinute() === m\"\n [class.font-semibold]=\"selectedMinute() === m\"\n [class.scale-105]=\"selectedMinute() === m\"\n [class.shadow-xs]=\"selectedMinute() === m\"\n [class.hover:bg-neutral-tertiary]=\"selectedMinute() !== m\"\n [class.text-heading]=\"selectedMinute() !== m\"\n >\n {{ pad(m) }}\n </button>\n }\n }\n </ol-scroll-area>\n\n <!-- Bottom Fade Gradient -->\n <div\n class=\"pointer-events-none absolute inset-x-0 bottom-0 h-6 bg-gradient-to-t from-neutral-primary to-transparent z-10\"\n ></div>\n </div>\n </div>\n\n @if (format() === '12h') {\n <!-- AM/PM Column -->\n <div class=\"flex flex-col items-center\">\n <span class=\"text-xs font-semibold text-body-subtle mb-1\">Bu\u1ED5i</span>\n <div\n class=\"h-44 w-16 flex flex-col justify-center gap-2 border border-default rounded-lg p-1\"\n >\n <button\n type=\"button\"\n (click)=\"selectPeriod('AM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'AM'\"\n [class.text-white]=\"selectedPeriod() === 'AM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'AM'\"\n [class.text-heading]=\"selectedPeriod() !== 'AM'\"\n >\n AM\n </button>\n <button\n type=\"button\"\n (click)=\"selectPeriod('PM')\"\n class=\"w-full py-2 text-center text-xs rounded-md transition-colors font-semibold\"\n [class.bg-brand]=\"selectedPeriod() === 'PM'\"\n [class.text-white]=\"selectedPeriod() === 'PM'\"\n [class.hover:bg-neutral-tertiary]=\"selectedPeriod() !== 'PM'\"\n [class.text-heading]=\"selectedPeriod() !== 'PM'\"\n >\n PM\n </button>\n </div>\n </div>\n }\n </div>\n\n <!-- Action Footer -->\n <div class=\"flex items-center justify-between pt-3 mt-2 border-t border-default\">\n <button\n type=\"button\"\n (click)=\"cancel()\"\n class=\"px-3 py-1.5 text-xs font-medium text-body hover:text-heading hover:bg-neutral-tertiary rounded-lg transition-colors\"\n >\n H\u1EE7y\n </button>\n <button\n type=\"button\"\n (click)=\"apply()\"\n class=\"px-3 py-1.5 text-xs font-medium text-white bg-brand hover:bg-brand-strong rounded-lg shadow-xs transition-colors\"\n >\n \u00C1p d\u1EE5ng\n </button>\n </div>\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
14098
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], showPresets: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPresets", required: false }] }], addonType: [{ type: i0.Input, args: [{ isSignal: true, alias: "addonType", required: false }] }], addonText: [{ type: i0.Input, args: [{ isSignal: true, alias: "addonText", required: false }] }], addonIcon: [{ type: i0.Input, args: [{ isSignal: true, alias: "addonIcon", required: false }] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], hourAreaRef: [{ type: i0.ViewChild, args: ['hourArea', { isSignal: true }] }], minuteAreaRef: [{ type: i0.ViewChild, args: ['minuteArea', { isSignal: true }] }], timeChange: [{ type: i0.Output, args: ["timeChange"] }], timestampChange: [{ type: i0.Output, args: ["timestampChange"] }], isoChange: [{ type: i0.Output, args: ["isoChange"] }], timeObjectChange: [{ type: i0.Output, args: ["timeObjectChange"] }], onDocumentClick: [{
|
|
13191
14099
|
type: HostListener,
|
|
13192
14100
|
args: ['document:click', ['$event']]
|
|
13193
14101
|
}] } });
|
|
@@ -13201,6 +14109,39 @@ class OlAutocomplete {
|
|
|
13201
14109
|
...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
|
|
13202
14110
|
label = input('', /* @ts-ignore */
|
|
13203
14111
|
...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
|
|
14112
|
+
/**
|
|
14113
|
+
* Nhãn nổi: `label` nằm TRONG khung (sau icon kính lúp), nổi lên khi focus,
|
|
14114
|
+
* đang gõ, mở danh sách hoặc đã chọn. Đa chọn: nổi lên không đẩy thẻ.
|
|
14115
|
+
*/
|
|
14116
|
+
floatingLabel = input(false, /* @ts-ignore */
|
|
14117
|
+
...(ngDevMode ? [{ debugName: "floatingLabel" }] : /* istanbul ignore next */ []));
|
|
14118
|
+
/**
|
|
14119
|
+
* Nhãn `top` (trên ô, mặc định) hay `left` (cùng hàng, bên trái). Nhãn nổi
|
|
14120
|
+
* (`floatingLabel`) thắng — bật nó thì bỏ qua input này.
|
|
14121
|
+
*/
|
|
14122
|
+
labelPosition = input('top', /* @ts-ignore */
|
|
14123
|
+
...(ngDevMode ? [{ debugName: "labelPosition" }] : /* istanbul ignore next */ []));
|
|
14124
|
+
/**
|
|
14125
|
+
* Độ rộng cột nhãn khi `labelPosition="left"` — độ dài CSS bất kỳ: `140px`,
|
|
14126
|
+
* `10rem`, `30%`. Bỏ trống = 8rem. Các ô trong cùng form đặt cùng một số thì
|
|
14127
|
+
* mép ô thẳng hàng.
|
|
14128
|
+
*/
|
|
14129
|
+
labelWidth = input('', /* @ts-ignore */
|
|
14130
|
+
...(ngDevMode ? [{ debugName: "labelWidth" }] : /* istanbul ignore next */ []));
|
|
14131
|
+
labelLayout = computed(() => fieldLabelLayout(this.labelPosition(), this.floatingLabel()), /* @ts-ignore */
|
|
14132
|
+
...(ngDevMode ? [{ debugName: "labelLayout" }] : /* istanbul ignore next */ []));
|
|
14133
|
+
labelBeside = computed(() => this.labelLayout() !== '', /* @ts-ignore */
|
|
14134
|
+
...(ngDevMode ? [{ debugName: "labelBeside" }] : /* istanbul ignore next */ []));
|
|
14135
|
+
/** Kiểu nhãn nổi — Figma Bordered / With background / Default. */
|
|
14136
|
+
floatingVariant = input('outlined', /* @ts-ignore */
|
|
14137
|
+
...(ngDevMode ? [{ debugName: "floatingVariant" }] : /* istanbul ignore next */ []));
|
|
14138
|
+
/** Có giá trị để nhãn nổi: đã chọn (một hay nhiều) hoặc đang có chữ gõ. */
|
|
14139
|
+
hasFloatValue = computed(() => {
|
|
14140
|
+
const v = this.value();
|
|
14141
|
+
const chosen = Array.isArray(v) ? v.length > 0 : v !== null && v !== undefined && v !== '';
|
|
14142
|
+
return chosen || !!this.searchQuery();
|
|
14143
|
+
}, /* @ts-ignore */
|
|
14144
|
+
...(ngDevMode ? [{ debugName: "hasFloatValue" }] : /* istanbul ignore next */ []));
|
|
13204
14145
|
placeholder = input('Tìm kiếm hoặc chọn...', /* @ts-ignore */
|
|
13205
14146
|
...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
13206
14147
|
labelKey = input('label', /* @ts-ignore */
|
|
@@ -13232,7 +14173,7 @@ class OlAutocomplete {
|
|
|
13232
14173
|
...(ngDevMode ? [{ debugName: "resolvedSize" }] : /* istanbul ignore next */ []));
|
|
13233
14174
|
/**
|
|
13234
14175
|
* Đệm ngang theo BẢNG CHUẨN họ form. Chỉ dùng khi KHÔNG `multiple` —
|
|
13235
|
-
* chế độ nhiều thẻ
|
|
14176
|
+
* chế độ nhiều thẻ dùng `boxPaddingClass`.
|
|
13236
14177
|
*
|
|
13237
14178
|
* Ba lỗi cũ gộp làm một chỗ này (đo /autocomplete 2026-09-24):
|
|
13238
14179
|
* sm `[class.px-2.5]` -> Angular CẮT ở dấu chấm, render `px-2` = 8px
|
|
@@ -13242,6 +14183,39 @@ class OlAutocomplete {
|
|
|
13242
14183
|
*/
|
|
13243
14184
|
fieldPaddingClass = computed(() => FIELD_PADDING_X[this.resolvedSize()], /* @ts-ignore */
|
|
13244
14185
|
...(ngDevMode ? [{ debugName: "fieldPaddingClass" }] : /* istanbul ignore next */ []));
|
|
14186
|
+
/**
|
|
14187
|
+
* Đệm khung. Chọn nhiều: icon kính lúp vẫn là phần tử đầu nên lề đầu theo
|
|
14188
|
+
* cỡ như ô đơn (bản cũ `ps-1.5` để icon cách mép 6px, ô đơn 12px — hai ô
|
|
14189
|
+
* cạnh nhau lệch nhau rõ). Lề cuối chừa chỗ cụm nút absolute: lề cỡ + icon
|
|
14190
|
+
* 16px + `FIELD_ICON_GAP` 8px.
|
|
14191
|
+
*/
|
|
14192
|
+
boxPaddingClass = computed(() => {
|
|
14193
|
+
const size = this.resolvedSize();
|
|
14194
|
+
return this.multiple()
|
|
14195
|
+
? `${FIELD_PADDING_START[size]} ${FIELD_ICON_RESERVE_END[size]} ${multiValuePadY(this.floatingLabel() && this.floatingVariant() === 'outlined')}`
|
|
14196
|
+
: FIELD_PADDING_X[size];
|
|
14197
|
+
}, /* @ts-ignore */
|
|
14198
|
+
...(ngDevMode ? [{ debugName: "boxPaddingClass" }] : /* istanbul ignore next */ []));
|
|
14199
|
+
/**
|
|
14200
|
+
* Cụm nút cuối (xoá / chevron / spinner). Rộng icon 16px + `ps-0.5` để chữ
|
|
14201
|
+
* ↔ icon đúng 8px (`gap-1.5` của khung + 2px) — bản cũ `w-7` ra 18px.
|
|
14202
|
+
* `justify-end`: spinner 12px vẫn neo mép phải như icon.
|
|
14203
|
+
*/
|
|
14204
|
+
controlClass = computed(() => this.multiple()
|
|
14205
|
+
? `absolute inset-y-0 inset-e-0 flex items-center ${FIELD_PADDING_END[this.resolvedSize()]}`
|
|
14206
|
+
: 'ms-auto flex w-4.5 shrink-0 items-center justify-end ps-0.5', /* @ts-ignore */
|
|
14207
|
+
...(ngDevMode ? [{ debugName: "controlClass" }] : /* istanbul ignore next */ []));
|
|
14208
|
+
/**
|
|
14209
|
+
* Đã có thẻ (chọn nhiều): khi KHÔNG gõ, ô gõ co về 0 (`min-w-0`, vẫn
|
|
14210
|
+
* `flex-1`) nên luôn ở cùng dòng chip cuối — không đẻ ra một dòng trống.
|
|
14211
|
+
* Focus để gõ thì mới cần chỗ (`focus:min-w-16`), lúc đó xuống dòng là đúng.
|
|
14212
|
+
* Đo 2026-09-29: dòng chip cuối còn 43px, `min-w-12` (48px) vẫn rớt.
|
|
14213
|
+
*/
|
|
14214
|
+
hasChips = computed(() => {
|
|
14215
|
+
const v = this.value();
|
|
14216
|
+
return this.multiple() && Array.isArray(v) && v.length > 0;
|
|
14217
|
+
}, /* @ts-ignore */
|
|
14218
|
+
...(ngDevMode ? [{ debugName: "hasChips" }] : /* istanbul ignore next */ []));
|
|
13245
14219
|
/**
|
|
13246
14220
|
* Cỡ chữ theo BẢNG CHUẨN. Trước đây `[class.text-base]` chỉ khai cho `lg`,
|
|
13247
14221
|
* `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
|
|
@@ -13764,17 +14738,17 @@ class OlAutocomplete {
|
|
|
13764
14738
|
this.disabled.set(isDisabled);
|
|
13765
14739
|
}
|
|
13766
14740
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlAutocomplete, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
13767
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlAutocomplete, isStandalone: true, selector: "ol-autocomplete", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, menuClass: { classPropertyName: "menuClass", publicName: "menuClass", isSignal: true, isRequired: false, transformFunction: null }, matchTriggerWidth: { classPropertyName: "matchTriggerWidth", publicName: "matchTriggerWidth", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, remoteSearch: { classPropertyName: "remoteSearch", publicName: "remoteSearch", isSignal: true, isRequired: false, transformFunction: null }, minTermLength: { classPropertyName: "minTermLength", publicName: "minTermLength", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, typeToSearchText: { classPropertyName: "typeToSearchText", publicName: "typeToSearchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", createOption: "createOption", search: "search", opened: "opened", closed: "closed", cleared: "cleared", added: "added", removed: "removed", selectionChange: "selectionChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block w-full" }, providers: [
|
|
14741
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlAutocomplete, isStandalone: true, selector: "ol-autocomplete", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, floatingLabel: { classPropertyName: "floatingLabel", publicName: "floatingLabel", isSignal: true, isRequired: false, transformFunction: null }, labelPosition: { classPropertyName: "labelPosition", publicName: "labelPosition", isSignal: true, isRequired: false, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null }, floatingVariant: { classPropertyName: "floatingVariant", publicName: "floatingVariant", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, labelKey: { classPropertyName: "labelKey", publicName: "labelKey", isSignal: true, isRequired: false, transformFunction: null }, valueKey: { classPropertyName: "valueKey", publicName: "valueKey", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, state: { classPropertyName: "state", publicName: "state", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, showAsterisk: { classPropertyName: "showAsterisk", publicName: "showAsterisk", isSignal: true, isRequired: false, transformFunction: null }, helperText: { classPropertyName: "helperText", publicName: "helperText", isSignal: true, isRequired: false, transformFunction: null }, errorText: { classPropertyName: "errorText", publicName: "errorText", isSignal: true, isRequired: false, transformFunction: null }, id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, allowCreate: { classPropertyName: "allowCreate", publicName: "allowCreate", isSignal: true, isRequired: false, transformFunction: null }, emptyText: { classPropertyName: "emptyText", publicName: "emptyText", isSignal: true, isRequired: false, transformFunction: null }, appendTo: { classPropertyName: "appendTo", publicName: "appendTo", isSignal: true, isRequired: false, transformFunction: null }, menuClass: { classPropertyName: "menuClass", publicName: "menuClass", isSignal: true, isRequired: false, transformFunction: null }, matchTriggerWidth: { classPropertyName: "matchTriggerWidth", publicName: "matchTriggerWidth", isSignal: true, isRequired: false, transformFunction: null }, groupBy: { classPropertyName: "groupBy", publicName: "groupBy", isSignal: true, isRequired: false, transformFunction: null }, remoteSearch: { classPropertyName: "remoteSearch", publicName: "remoteSearch", isSignal: true, isRequired: false, transformFunction: null }, minTermLength: { classPropertyName: "minTermLength", publicName: "minTermLength", isSignal: true, isRequired: false, transformFunction: null }, debounceMs: { classPropertyName: "debounceMs", publicName: "debounceMs", isSignal: true, isRequired: false, transformFunction: null }, typeToSearchText: { classPropertyName: "typeToSearchText", publicName: "typeToSearchText", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", disabled: "disabledChange", createOption: "createOption", search: "search", opened: "opened", closed: "closed", cleared: "cleared", added: "added", removed: "removed", selectionChange: "selectionChange" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, classAttribute: "block w-full" }, providers: [
|
|
13768
14742
|
{
|
|
13769
14743
|
provide: NG_VALUE_ACCESSOR,
|
|
13770
14744
|
useExisting: forwardRef(() => OlAutocomplete),
|
|
13771
14745
|
multi: true,
|
|
13772
14746
|
},
|
|
13773
|
-
], queries: [{ propertyName: "optionTemplate", first: true, predicate: ["optionTemplate"], descendants: true }], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"relative w-full\">\n @if (label()) {\n <div class=\"flex items-center justify-between mb-1.5\">\n <label\n [for]=\"inputId()\"\n class=\"inline-flex items-center gap-1 text-sm font-medium text-heading\"\n >\n <span>{{ label() }}</span>\n @if (required() || showAsterisk()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n </div>\n }\n\n <!--\n Main Input Box Container.\n\n `py-1` ch\u1EE9 KH\u00D4NG ph\u1EA3i `p-1.5` cho ch\u1EBF \u0111\u1ED9 multiple: `p-1.5` \u0111\u1EC7m M\u1ECCI PH\u00CDA 6px,\n c\u1ED9ng v\u00E0o n\u1ED9i dung 34px th\u00E0nh 46px \u2014 v\u01B0\u1EE3t `min-h-10` (40px). \u0110o 2026-09-17:\n \u00F4 \u0111a ch\u1ECDn cao **46px** trong khi \u00F4 \u0111\u01A1n ch\u1ECDn ngay c\u1EA1nh cao **40px**, hai \u00F4\n c\u00F9ng m\u1ED9t h\u00E0ng l\u01B0\u1EDBi n\u00EAn l\u1EC7ch th\u1EA5y r\u00F5. \u0110\u1EC7m d\u1ECDc 4px v\u1EEBa \u0111\u1EE7 cho th\u1EBB cao 24px\n (4 + 24 + 4 = 32 < 40) m\u00E0 v\u1EABn c\u00F2n ch\u1ED7 khi th\u1EBB xu\u1ED1ng d\u00F2ng th\u1EE9 hai.\n\n `content-center` \u0111i k\u00E8m `items-center`: v\u1EDBi `flex-wrap`, `items-center` ch\u1EC9\n c\u0103n gi\u1EEFa TRONG T\u1EEANG D\u00D2NG, c\u1EA3 kh\u1ED1i nhi\u1EC1u d\u00F2ng v\u1EABn d\u00EDnh m\u00E9p tr\u00EAn.\n -->\n <div\n class=\"relative flex items-center content-center flex-wrap gap-1.5 w-full border shadow-xs\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"true\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n [ngClass]=\"multiple() ? 'px-1.5 py-1' : fieldPaddingClass()\"\n >\n <!--\n Icon k\u00EDnh l\u00FAp LU\u00D4N hi\u1EC7n. B\u1EA3n c\u0169 \u1EA9n n\u00F3 khi `multiple` \u0111\u00E3 c\u00F3 gi\u00E1 tr\u1ECB\n (`@if (!multiple() || !value() || value()?.length === 0)`) \u2014 ch\u1ECDn m\u1ED9t th\u1EBB\n l\u00E0 icon bi\u1EBFn m\u1EA5t, xo\u00E1 h\u1EBFt th\u1EBB th\u00EC n\u00F3 quay l\u1EA1i. \u0110o 2026-09-17 tr\u00EAn\n `/autocomplete`: \u00F4 \u0111\u01A1n ch\u1ECDn 1 icon `magnifer`, \u00F4 \u0111a ch\u1ECDn **0 icon** khi\n c\u00F3 2 th\u1EBB.\n\n Figma kh\u00F4ng c\u00F3 c\u01A1 s\u1EDF cho vi\u1EC7c \u1EA9n: \u0111\u1ECDc `ol-autocomplete-examples`\n (#1149:53126) th\u00EC **6/6 Type \u0111\u1EC1u c\u00F3 `search` trong `ol-input`**, kh\u00F4ng\n Type n\u00E0o b\u1ECF. Figma c\u0169ng kh\u00F4ng v\u1EBD bi\u1EBFn th\u1EC3 \u0111a ch\u1ECDn c\u00F3 th\u1EBB, n\u00EAn \u0111\u00E2y l\u00E0 l\u1EF1a\n ch\u1ECDn c\u1EE7a code ch\u1EE9 kh\u00F4ng ph\u1EA3i thi\u1EBFt k\u1EBF.\n\n `shrink-0` \u0111\u1EC3 icon kh\u00F4ng b\u1ECB c\u00E1c th\u1EBB \u00E9p b\u1EB9p khi \u00F4 ch\u1EADt.\n -->\n <ol-icon name=\"magnifer\" size=\"sm\" class=\"shrink-0\" />\n\n <!-- Multiple Selected Tags / Chips -->\n @if (multiple() && value() && value()?.length > 0) {\n @for (itemVal of value(); track itemVal) {\n <!--\n `h-6` c\u1ED1 \u0111\u1ECBnh (24px): \u0111\u1EC3 th\u1EBB t\u1EF1 co theo n\u1ED9i dung th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t\n ki\u1EC3u v\u00E0 \u00F4 nh\u1EADp b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. 24px + \u0111\u1EC7m d\u1ECDc 4\u00D72 = 32px,\n v\u1EABn n\u1EB1m trong `min-h-10` (40px) n\u00EAn \u00F4 \u0111a ch\u1ECDn cao \u0111\u00FAng b\u1EB1ng \u00F4 \u0111\u01A1n ch\u1ECDn.\n -->\n <span\n class=\"inline-flex h-6 items-center gap-1 px-2 text-xs font-medium bg-brand-softer text-fg-brand-strong rounded-md border border-brand-subtle shrink-0\"\n >\n <span>{{ getDisplayLabelForValue(itemVal) }}</span>\n @if (!disabled() && !readonly()) {\n <!-- Vu\u00F4ng nh\u01B0 n\u00FAt xo\u00E1 chung \u2014 xem l\u00FD do \u1EDF \u0111\u00F3. -->\n <button\n type=\"button\"\n (click)=\"removeTag(itemVal, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabelForValue(itemVal)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!-- Input text field -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n type=\"text\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"multiple() && value()?.length > 0 ? '' : placeholder()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchChange($event)\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"flex-1 min-w-20 bg-transparent border-0 outline-none placeholder:text-body-subtle font-normal text-heading\"\n [ngClass]=\"fieldTextClass()\"\n />\n\n <!-- M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0111\u1ED5i vai theo tr\u1EA1ng th\u00E1i \u2014 kh\u00F4ng x\u1EBFp c\u1EA1nh nhau.\n Tr\u01B0\u1EDBc \u0111\u00E2y n\u00FAt xo\u00E1 v\u00E0 `chevron-down` C\u00D9NG hi\u1EC7n: \u0111o /autocomplete ng\u00E0y\n 2026-09-16 \u0111\u1EBFm 3 ph\u1EA7n t\u1EED chen trong c\u1EE5m 44px, c\u1ED9ng spinner khi `loading`\n l\u00E0 b\u1ED1n. \u00D4 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh `w-7` \u0111\u1EC3 khung kh\u00F4ng nh\u1EA3y khi icon \u0111\u1ED5i vai.\n\n `justify-end` ch\u1EE9 KH\u00D4NG `justify-center`: \u00F4 r\u1ED9ng 28px m\u00E0 icon ch\u1EC9 16px\n n\u00EAn c\u0103n gi\u1EEFa \u0111\u1EC3 d\u01B0 6px m\u1ED7i b\u00EAn, \u0111\u1EA9y icon v\u00E0o trong. \u0110o 2026-09-17 tr\u00EAn\n 13 \u00F4 c\u1EE7a /autocomplete: l\u1EC1 tr\u00E1i 15px, l\u1EC1 ph\u1EA3i **21px** \u2014 l\u1EC7ch \u0111\u00FAng 6px\n \u1EDF M\u1ECCI \u00F4, c\u1EA3 \u0111\u01A1n ch\u1ECDn l\u1EABn \u0111a ch\u1ECDn. Con s\u1ED1 kh\u00F4ng \u0111\u1ED5i theo n\u1ED9i dung n\u00EAn\n \u0111\u00E2y l\u00E0 l\u1ED7i c\u1EA5u tr\u00FAc, kh\u00F4ng ph\u1EA3i do ch\u1EEF d\u00E0i ng\u1EAFn.\n\n `ol-select` \u0111\u00E3 ch\u1ED1t `justify-end` t\u1EEB l\u01B0\u1EE3t tr\u01B0\u1EDBc; \u0111\u00E2y l\u00E0 l\u1EA7n s\u1EEDa cho\n hai component kh\u1EDBp nhau. -->\n <div class=\"ms-auto flex w-7 shrink-0 items-center justify-end\">\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (\n (searchQuery() || (multiple() && value()?.length > 0) || (!multiple() && value())) &&\n !disabled() &&\n !readonly()\n ) {\n <!--\n N\u00FAt b\u1ECDc `size-4` \u0110\u00DANG B\u1EB0NG icon, kh\u00F4ng `size-6`.\n\n `size-6` (24px) b\u1ECDc SVG 16px th\u00EC d\u01B0 4px m\u1ED7i b\u00EAn, \u0111\u1EA9y icon v\u00E0o trong:\n \u0111o 2026-09-17 th\u1EA5y \u00F4 \u0111ang hi\u1EC7n n\u00FAt xo\u00E1 c\u00F3 l\u1EC1 ph\u1EA3i **19px** trong khi\n \u00F4 hi\u1EC7n chevron tr\u1EA7n ch\u1EC9 15px \u2014 c\u00F9ng m\u1ED9t component m\u00E0 hai tr\u1EA1ng th\u00E1i\n l\u1EC7ch nhau 4px, \u0111\u00FAng ch\u1ED7 anh ch\u1EC9 trong \u1EA3nh.\n\n V\u00F9ng b\u1EA5m v\u1EABn \u0111\u1EE7 l\u1EDBn nh\u1EDD `p-1` \u1EDF NGO\u00C0I khung \u0111o: `size-4` + \u0111\u1EC7m\n kh\u00F4ng c\u1ED9ng v\u00E0o b\u1EC1 r\u1ED9ng chi\u1EBFm ch\u1ED7 v\u00EC n\u00FAt n\u1EB1m trong \u00F4 `w-7` c\u1ED1 \u0111\u1ECBnh.\n -->\n <button\n type=\"button\"\n (click)=\"clearAll($event)\"\n aria-label=\"Xo\u00E1 to\u00E0n b\u1ED9 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n\n <!--\n Dropdown Suggestions Menu.\n\n Token \u0111\u1ECDc t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147, Type=Default):\n bo g\u00F3c 12px \u00B7 \u0111\u1EC7m 8px \u00B7 n\u1EC1n `bg-neutral-primary-medium` \u00B7 vi\u1EC1n\n `border-default-medium`.\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng n\u00EAn \u0111\u1ED5i hay kh\u00F4ng nh\u00ECn c\u0169ng\n th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I kh\u00E1c h\u1EB3n: `bg-primary` l\u00E0 gray-950 (g\u1EA7n \u0111en, panel\n ch\u00ECm v\u00E0o n\u1EC1n) c\u00F2n `bg-primary-medium` l\u00E0 gray-800 (panel n\u1ED5i l\u00EAn).\n `ol-select` c\u00F9ng l\u1EC7ch n\u00E0y, s\u1EEDa chung m\u1ED9t l\u01B0\u1EE3t.\n -->\n @if (isOpen()) {\n <div\n #panel\n [id]=\"listboxId()\"\n role=\"listbox\"\n [attr.aria-label]=\"label() || 'G\u1EE3i \u00FD'\"\n class=\"absolute z-dropdown bg-neutral-primary-medium border border-default-medium rounded-md shadow-lg max-h-[var(--ol-overlay-max-height,15rem)] overflow-y-auto overflow-x-hidden p-2 animate-in fade-in zoom-in-95 duration-150\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (filteredOptions().length > 0) {\n @for (item of filteredOptions(); track getItemValue(item); let idx = $index) {\n <!-- Ti\u00EAu \u0111\u1EC1 nh\u00F3m ch\u00E8n gi\u1EEFa v\u00F2ng l\u1EB7p ph\u1EB3ng, xem `groupNameAt`. -->\n @if (groupNameAt(idx); as groupName) {\n <div\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ groupName }}\n </div>\n }\n <div\n [id]=\"optionId(idx)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [attr.aria-disabled]=\"item?.disabled ? 'true' : null\"\n (click)=\"selectOption(item)\"\n class=\"flex items-center justify-between px-3 py-2 text-sm rounded-lg cursor-pointer transition-colors\"\n [class.bg-brand-softer]=\"isSelected(item) || activeIndex() === idx\"\n [class.hover:bg-neutral-tertiary-medium]=\"!isSelected(item) && activeIndex() !== idx\"\n [class.opacity-50]=\"item?.disabled\"\n [class.cursor-not-allowed]=\"item?.disabled\"\n >\n @if (optionTemplate) {\n <!-- Custom Option Template -->\n <ng-container\n *ngTemplateOutlet=\"\n optionTemplate;\n context: { $implicit: item, selected: isSelected(item) }\n \"\n />\n } @else {\n <!-- Default Option Display -->\n <div class=\"flex items-center gap-2.5 overflow-hidden\">\n @if (item?.avatar) {\n <img\n [src]=\"item.avatar\"\n alt=\"\u1EA2nh \u0111\u1EA1i di\u1EC7n\"\n class=\"w-7 h-7 rounded-full object-cover shrink-0\"\n />\n }\n <div class=\"truncate\">\n <span class=\"font-medium text-heading block truncate\">{{\n getItemLabel(item)\n }}</span>\n @if (item?.subLabel) {\n <span class=\"text-xs text-body-subtle block truncate\">{{ item.subLabel }}</span>\n }\n </div>\n </div>\n\n <div class=\"flex items-center gap-2 shrink-0 ms-2\">\n @if (item?.badge) {\n <span\n class=\"px-1.5 py-0.5 text-2xs font-medium rounded bg-neutral-tertiary-medium text-body\"\n >\n {{ item.badge }}\n </span>\n }\n @if (isSelected(item)) {\n <ol-icon name=\"check\" size=\"sm\" />\n }\n </div>\n }\n </div>\n }\n } @else if (waitingForTerm()) {\n <!--\n Ch\u1EBF \u0111\u1ED9 L\u1ECCC T\u1EEA XA, ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`. Ph\u1EA3i l\u00E0 th\u00F4ng b\u00E1o RI\u00CANG\n ch\u1EE9 kh\u00F4ng d\u00F9ng `emptyText`: \"Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3\" l\u00E0 sai s\u1EF1 th\u1EADt \u2014\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i v\u1EDBi `typeToSearchText` c\u1EE7a ng-select.\n -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ typeToSearchText() }}</p>\n </div>\n } @else {\n <!-- Empty State & Allow Create Option -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ emptyText() }}</p>\n @if (allowCreate() && searchQuery().trim()) {\n <button\n type=\"button\"\n (click)=\"onCreateNew()\"\n class=\"mt-2 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-fg-brand bg-brand-softer hover:bg-brand-subtle rounded-lg transition-colors\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>Th\u00EAm m\u1EDBi: \"{{ searchQuery().trim() }}\"</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlSpinner, selector: "ol-spinner", inputs: ["variant", "size", "color", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14747
|
+
], queries: [{ propertyName: "optionTemplate", first: true, predicate: ["optionTemplate"], descendants: true }], viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"relative w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"resolvedSize()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"resolvedSize()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"hasFloatValue()\"\n [open]=\"isOpen()\"\n >\n <!--\n Main Input Box Container.\n\n `py-1` ch\u1EE9 KH\u00D4NG ph\u1EA3i `p-1.5` cho ch\u1EBF \u0111\u1ED9 multiple: `p-1.5` \u0111\u1EC7m M\u1ECCI PH\u00CDA 6px,\n c\u1ED9ng v\u00E0o n\u1ED9i dung 34px th\u00E0nh 46px \u2014 v\u01B0\u1EE3t `min-h-10` (40px). \u0110o 2026-09-17:\n \u00F4 \u0111a ch\u1ECDn cao **46px** trong khi \u00F4 \u0111\u01A1n ch\u1ECDn ngay c\u1EA1nh cao **40px**, hai \u00F4\n c\u00F9ng m\u1ED9t h\u00E0ng l\u01B0\u1EDBi n\u00EAn l\u1EC7ch th\u1EA5y r\u00F5. \u0110\u1EC7m d\u1ECDc 4px v\u1EEBa \u0111\u1EE7 cho th\u1EBB cao 24px\n (4 + 24 + 4 = 32 < 40) m\u00E0 v\u1EABn c\u00F2n ch\u1ED7 khi th\u1EBB xu\u1ED1ng d\u00F2ng th\u1EE9 hai.\n\n `content-center` \u0111i k\u00E8m `items-center`: v\u1EDBi `flex-wrap`, `items-center` ch\u1EC9\n c\u0103n gi\u1EEFa TRONG T\u1EEANG D\u00D2NG, c\u1EA3 kh\u1ED1i nhi\u1EC1u d\u00F2ng v\u1EABn d\u00EDnh m\u00E9p tr\u00EAn.\n -->\n <div\n class=\"ol-field-box relative flex items-center content-center flex-wrap gap-1.5 w-full border shadow-xs\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"true\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n [ngClass]=\"boxPaddingClass()\"\n >\n <!--\n Icon k\u00EDnh l\u00FAp LU\u00D4N hi\u1EC7n. B\u1EA3n c\u0169 \u1EA9n n\u00F3 khi `multiple` \u0111\u00E3 c\u00F3 gi\u00E1 tr\u1ECB\n (`@if (!multiple() || !value() || value()?.length === 0)`) \u2014 ch\u1ECDn m\u1ED9t th\u1EBB\n l\u00E0 icon bi\u1EBFn m\u1EA5t, xo\u00E1 h\u1EBFt th\u1EBB th\u00EC n\u00F3 quay l\u1EA1i. \u0110o 2026-09-17 tr\u00EAn\n `/autocomplete`: \u00F4 \u0111\u01A1n ch\u1ECDn 1 icon `magnifer`, \u00F4 \u0111a ch\u1ECDn **0 icon** khi\n c\u00F3 2 th\u1EBB.\n\n Figma kh\u00F4ng c\u00F3 c\u01A1 s\u1EDF cho vi\u1EC7c \u1EA9n: \u0111\u1ECDc `ol-autocomplete-examples`\n (#1149:53126) th\u00EC **6/6 Type \u0111\u1EC1u c\u00F3 `search` trong `ol-input`**, kh\u00F4ng\n Type n\u00E0o b\u1ECF. Figma c\u0169ng kh\u00F4ng v\u1EBD bi\u1EBFn th\u1EC3 \u0111a ch\u1ECDn c\u00F3 th\u1EBB, n\u00EAn \u0111\u00E2y l\u00E0 l\u1EF1a\n ch\u1ECDn c\u1EE7a code ch\u1EE9 kh\u00F4ng ph\u1EA3i thi\u1EBFt k\u1EBF.\n\n `shrink-0` \u0111\u1EC3 icon kh\u00F4ng b\u1ECB c\u00E1c th\u1EBB \u00E9p b\u1EB9p khi \u00F4 ch\u1EADt.\n -->\n <ol-icon name=\"magnifer\" size=\"sm\" class=\"shrink-0 me-0.5\" />\n\n <!-- Multiple Selected Tags / Chips -->\n @if (multiple() && value() && value()?.length > 0) {\n @for (itemVal of value(); track itemVal) {\n <!--\n `h-6` c\u1ED1 \u0111\u1ECBnh (24px): \u0111\u1EC3 th\u1EBB t\u1EF1 co theo n\u1ED9i dung th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t\n ki\u1EC3u v\u00E0 \u00F4 nh\u1EADp b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. 24px + \u0111\u1EC7m d\u1ECDc 4\u00D72 = 32px,\n v\u1EABn n\u1EB1m trong `min-h-10` (40px) n\u00EAn \u00F4 \u0111a ch\u1ECDn cao \u0111\u00FAng b\u1EB1ng \u00F4 \u0111\u01A1n ch\u1ECDn.\n -->\n <span\n class=\"inline-flex h-6 items-center gap-1 px-2 text-xs font-medium bg-brand-softer text-fg-brand-strong rounded-md border border-brand-subtle shrink-0\"\n >\n <span>{{ getDisplayLabelForValue(itemVal) }}</span>\n @if (!disabled() && !readonly()) {\n <!-- Vu\u00F4ng nh\u01B0 n\u00FAt xo\u00E1 chung \u2014 xem l\u00FD do \u1EDF \u0111\u00F3. -->\n <button\n type=\"button\"\n (click)=\"removeTag(itemVal, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabelForValue(itemVal)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!-- Input text field -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n type=\"text\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"multiple() && value()?.length > 0 ? '' : placeholder()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchChange($event)\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"flex-1 bg-transparent border-0 outline-none placeholder:text-body-subtle font-normal text-heading\"\n [ngClass]=\"fieldTextClass() + ' ' + (hasChips() ? 'min-w-0 focus:min-w-16' : 'min-w-20')\"\n />\n\n <!--\n M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0111\u1ED5i vai theo tr\u1EA1ng th\u00E1i \u2014 n\u00FAt xo\u00E1, chevron ho\u1EB7c\n spinner, kh\u00F4ng x\u1EBFp c\u1EA1nh nhau (b\u1EA3n c\u0169 hi\u1EC7n c\u00F9ng l\u00FAc 3\u20134 ph\u1EA7n t\u1EED). \u00D4 r\u1ED9ng\n \u0111\u00FAng b\u1EB1ng icon (`controlClass`): ch\u1EEF \u2194 icon 8px nh\u01B0 Figma.\n -->\n <!--\n Ch\u1ECDn nhi\u1EC1u: c\u1EE5m n\u00FAt ra kh\u1ECFi lu\u1ED3ng (absolute, nh\u01B0 ol-select) v\u00E0 khung\n ch\u1EEBa pe-9. \u0110\u1EC3 trong lu\u1ED3ng th\u00EC c\u1EE5m n\u00FAt + \u00F4 g\u00F5 kh\u00F4ng v\u1EEBa ph\u1EA7n c\u00F2n l\u1EA1i c\u1EE7a\n d\u00F2ng chip cu\u1ED1i, c\u00F9ng r\u1EDBt xu\u1ED1ng m\u1ED9t d\u00F2ng m\u1EDBi ch\u1EC9 ch\u1EE9a kho\u1EA3ng tr\u1ED1ng \u2014 \u0111o\n 2026-09-29: d\u01B0 31\u201336px \u1EDF m\u1ECDi ki\u1EC3u v\u00E0 c\u1EE1.\n -->\n <div [class]=\"controlClass()\">\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (\n (searchQuery() || (multiple() && value()?.length > 0) || (!multiple() && value())) &&\n !disabled() &&\n !readonly()\n ) {\n <!--\n N\u00FAt b\u1ECDc `size-4` \u0110\u00DANG B\u1EB0NG icon, kh\u00F4ng `size-6`.\n\n `size-6` (24px) b\u1ECDc SVG 16px th\u00EC d\u01B0 4px m\u1ED7i b\u00EAn, \u0111\u1EA9y icon v\u00E0o trong:\n \u0111o 2026-09-17 th\u1EA5y \u00F4 \u0111ang hi\u1EC7n n\u00FAt xo\u00E1 c\u00F3 l\u1EC1 ph\u1EA3i **19px** trong khi\n \u00F4 hi\u1EC7n chevron tr\u1EA7n ch\u1EC9 15px \u2014 c\u00F9ng m\u1ED9t component m\u00E0 hai tr\u1EA1ng th\u00E1i\n l\u1EC7ch nhau 4px, \u0111\u00FAng ch\u1ED7 anh ch\u1EC9 trong \u1EA3nh.\n\n V\u00F9ng b\u1EA5m v\u1EABn \u0111\u1EE7 l\u1EDBn nh\u1EDD `p-1` \u1EDF NGO\u00C0I khung \u0111o: `size-4` + \u0111\u1EC7m\n kh\u00F4ng c\u1ED9ng v\u00E0o b\u1EC1 r\u1ED9ng chi\u1EBFm ch\u1ED7 v\u00EC n\u00FAt n\u1EB1m trong \u00F4 `w-7` c\u1ED1 \u0111\u1ECBnh.\n -->\n <button\n type=\"button\"\n (click)=\"clearAll($event)\"\n aria-label=\"Xo\u00E1 to\u00E0n b\u1ED9 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n </ol-field-frame>\n\n <!--\n Dropdown Suggestions Menu.\n\n Token \u0111\u1ECDc t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147, Type=Default):\n bo g\u00F3c 12px \u00B7 \u0111\u1EC7m 8px \u00B7 n\u1EC1n `bg-neutral-primary-medium` \u00B7 vi\u1EC1n\n `border-default-medium`.\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng n\u00EAn \u0111\u1ED5i hay kh\u00F4ng nh\u00ECn c\u0169ng\n th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I kh\u00E1c h\u1EB3n: `bg-primary` l\u00E0 gray-950 (g\u1EA7n \u0111en, panel\n ch\u00ECm v\u00E0o n\u1EC1n) c\u00F2n `bg-primary-medium` l\u00E0 gray-800 (panel n\u1ED5i l\u00EAn).\n `ol-select` c\u00F9ng l\u1EC7ch n\u00E0y, s\u1EEDa chung m\u1ED9t l\u01B0\u1EE3t.\n -->\n @if (isOpen()) {\n <div\n #panel\n [id]=\"listboxId()\"\n role=\"listbox\"\n [attr.aria-label]=\"label() || 'G\u1EE3i \u00FD'\"\n class=\"absolute z-dropdown bg-neutral-primary-medium border border-default-medium rounded-md shadow-lg max-h-[var(--ol-overlay-max-height,15rem)] overflow-y-auto overflow-x-hidden p-2 animate-in fade-in zoom-in-95 duration-150\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (filteredOptions().length > 0) {\n @for (item of filteredOptions(); track getItemValue(item); let idx = $index) {\n <!-- Ti\u00EAu \u0111\u1EC1 nh\u00F3m ch\u00E8n gi\u1EEFa v\u00F2ng l\u1EB7p ph\u1EB3ng, xem `groupNameAt`. -->\n @if (groupNameAt(idx); as groupName) {\n <div\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ groupName }}\n </div>\n }\n <div\n [id]=\"optionId(idx)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [attr.aria-disabled]=\"item?.disabled ? 'true' : null\"\n (click)=\"selectOption(item)\"\n class=\"flex items-center justify-between px-3 py-2 text-sm rounded-lg cursor-pointer transition-colors\"\n [class.bg-brand-softer]=\"isSelected(item) || activeIndex() === idx\"\n [class.hover:bg-neutral-tertiary-medium]=\"!isSelected(item) && activeIndex() !== idx\"\n [class.opacity-50]=\"item?.disabled\"\n [class.cursor-not-allowed]=\"item?.disabled\"\n >\n @if (optionTemplate) {\n <!-- Custom Option Template -->\n <ng-container\n *ngTemplateOutlet=\"\n optionTemplate;\n context: { $implicit: item, selected: isSelected(item) }\n \"\n />\n } @else {\n <!-- Default Option Display -->\n <div class=\"flex items-center gap-2.5 overflow-hidden\">\n @if (item?.avatar) {\n <img\n [src]=\"item.avatar\"\n alt=\"\u1EA2nh \u0111\u1EA1i di\u1EC7n\"\n class=\"w-7 h-7 rounded-full object-cover shrink-0\"\n />\n }\n <div class=\"truncate\">\n <span class=\"font-medium text-heading block truncate\">{{\n getItemLabel(item)\n }}</span>\n @if (item?.subLabel) {\n <span class=\"text-xs text-body-subtle block truncate\">{{ item.subLabel }}</span>\n }\n </div>\n </div>\n\n <div class=\"flex items-center gap-2 shrink-0 ms-2\">\n @if (item?.badge) {\n <span\n class=\"px-1.5 py-0.5 text-2xs font-medium rounded bg-neutral-tertiary-medium text-body\"\n >\n {{ item.badge }}\n </span>\n }\n @if (isSelected(item)) {\n <ol-icon name=\"check\" size=\"sm\" />\n }\n </div>\n }\n </div>\n }\n } @else if (waitingForTerm()) {\n <!--\n Ch\u1EBF \u0111\u1ED9 L\u1ECCC T\u1EEA XA, ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`. Ph\u1EA3i l\u00E0 th\u00F4ng b\u00E1o RI\u00CANG\n ch\u1EE9 kh\u00F4ng d\u00F9ng `emptyText`: \"Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3\" l\u00E0 sai s\u1EF1 th\u1EADt \u2014\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i v\u1EDBi `typeToSearchText` c\u1EE7a ng-select.\n -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ typeToSearchText() }}</p>\n </div>\n } @else {\n <!-- Empty State & Allow Create Option -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ emptyText() }}</p>\n @if (allowCreate() && searchQuery().trim()) {\n <button\n type=\"button\"\n (click)=\"onCreateNew()\"\n class=\"mt-2 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-fg-brand bg-brand-softer hover:bg-brand-subtle rounded-lg transition-colors\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>Th\u00EAm m\u1EDBi: \"{{ searchQuery().trim() }}\"</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }, { kind: "component", type: OlSpinner, selector: "ol-spinner", inputs: ["variant", "size", "color", "ariaLabel"] }, { kind: "component", type: OlFieldFrame, selector: "ol-field-frame", inputs: ["enabled", "label", "forId", "labelId", "variant", "size", "state", "disabled", "required", "hasValue", "open", "floatStart"] }, { kind: "component", type: OlFieldLabel, selector: "ol-field-label", inputs: ["text", "forId", "labelId", "required", "helpIcon", "helpTooltip", "flush", "beside", "size"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13774
14748
|
}
|
|
13775
14749
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlAutocomplete, decorators: [{
|
|
13776
14750
|
type: Component,
|
|
13777
|
-
args: [{ selector: 'ol-autocomplete', standalone: true, imports: [CommonModule, FormsModule, OlIcon, OlSpinner], host: {
|
|
14751
|
+
args: [{ selector: 'ol-autocomplete', standalone: true, imports: [CommonModule, FormsModule, OlIcon, OlSpinner, OlFieldFrame, OlFieldLabel], host: {
|
|
13778
14752
|
// Họ form dùng chung một quy ước: host `block w-full`. Không khai thì thẻ
|
|
13779
14753
|
// tuỳ biến mặc định `display: inline` và bề rộng chạy theo NỘI DUNG —
|
|
13780
14754
|
// `ol-autocomplete` phình 240 -> 268px khi gõ chữ vì cụm icon phải chèn
|
|
@@ -13786,8 +14760,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
13786
14760
|
useExisting: forwardRef(() => OlAutocomplete),
|
|
13787
14761
|
multi: true,
|
|
13788
14762
|
},
|
|
13789
|
-
], template: "<div class=\"relative w-full\">\n @if (label()) {\n <div class=\"flex items-center justify-between mb-1.5\">\n <label\n [for]=\"inputId()\"\n class=\"inline-flex items-center gap-1 text-sm font-medium text-heading\"\n >\n <span>{{ label() }}</span>\n @if (required() || showAsterisk()) {\n <span class=\"text-fg-danger font-bold\">*</span>\n }\n </label>\n </div>\n }\n\n <!--\n Main Input Box Container.\n\n `py-1` ch\u1EE9 KH\u00D4NG ph\u1EA3i `p-1.5` cho ch\u1EBF \u0111\u1ED9 multiple: `p-1.5` \u0111\u1EC7m M\u1ECCI PH\u00CDA 6px,\n c\u1ED9ng v\u00E0o n\u1ED9i dung 34px th\u00E0nh 46px \u2014 v\u01B0\u1EE3t `min-h-10` (40px). \u0110o 2026-09-17:\n \u00F4 \u0111a ch\u1ECDn cao **46px** trong khi \u00F4 \u0111\u01A1n ch\u1ECDn ngay c\u1EA1nh cao **40px**, hai \u00F4\n c\u00F9ng m\u1ED9t h\u00E0ng l\u01B0\u1EDBi n\u00EAn l\u1EC7ch th\u1EA5y r\u00F5. \u0110\u1EC7m d\u1ECDc 4px v\u1EEBa \u0111\u1EE7 cho th\u1EBB cao 24px\n (4 + 24 + 4 = 32 < 40) m\u00E0 v\u1EABn c\u00F2n ch\u1ED7 khi th\u1EBB xu\u1ED1ng d\u00F2ng th\u1EE9 hai.\n\n `content-center` \u0111i k\u00E8m `items-center`: v\u1EDBi `flex-wrap`, `items-center` ch\u1EC9\n c\u0103n gi\u1EEFa TRONG T\u1EEANG D\u00D2NG, c\u1EA3 kh\u1ED1i nhi\u1EC1u d\u00F2ng v\u1EABn d\u00EDnh m\u00E9p tr\u00EAn.\n -->\n <div\n class=\"relative flex items-center content-center flex-wrap gap-1.5 w-full border shadow-xs\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"true\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n [ngClass]=\"multiple() ? 'px-1.5 py-1' : fieldPaddingClass()\"\n >\n <!--\n Icon k\u00EDnh l\u00FAp LU\u00D4N hi\u1EC7n. B\u1EA3n c\u0169 \u1EA9n n\u00F3 khi `multiple` \u0111\u00E3 c\u00F3 gi\u00E1 tr\u1ECB\n (`@if (!multiple() || !value() || value()?.length === 0)`) \u2014 ch\u1ECDn m\u1ED9t th\u1EBB\n l\u00E0 icon bi\u1EBFn m\u1EA5t, xo\u00E1 h\u1EBFt th\u1EBB th\u00EC n\u00F3 quay l\u1EA1i. \u0110o 2026-09-17 tr\u00EAn\n `/autocomplete`: \u00F4 \u0111\u01A1n ch\u1ECDn 1 icon `magnifer`, \u00F4 \u0111a ch\u1ECDn **0 icon** khi\n c\u00F3 2 th\u1EBB.\n\n Figma kh\u00F4ng c\u00F3 c\u01A1 s\u1EDF cho vi\u1EC7c \u1EA9n: \u0111\u1ECDc `ol-autocomplete-examples`\n (#1149:53126) th\u00EC **6/6 Type \u0111\u1EC1u c\u00F3 `search` trong `ol-input`**, kh\u00F4ng\n Type n\u00E0o b\u1ECF. Figma c\u0169ng kh\u00F4ng v\u1EBD bi\u1EBFn th\u1EC3 \u0111a ch\u1ECDn c\u00F3 th\u1EBB, n\u00EAn \u0111\u00E2y l\u00E0 l\u1EF1a\n ch\u1ECDn c\u1EE7a code ch\u1EE9 kh\u00F4ng ph\u1EA3i thi\u1EBFt k\u1EBF.\n\n `shrink-0` \u0111\u1EC3 icon kh\u00F4ng b\u1ECB c\u00E1c th\u1EBB \u00E9p b\u1EB9p khi \u00F4 ch\u1EADt.\n -->\n <ol-icon name=\"magnifer\" size=\"sm\" class=\"shrink-0\" />\n\n <!-- Multiple Selected Tags / Chips -->\n @if (multiple() && value() && value()?.length > 0) {\n @for (itemVal of value(); track itemVal) {\n <!--\n `h-6` c\u1ED1 \u0111\u1ECBnh (24px): \u0111\u1EC3 th\u1EBB t\u1EF1 co theo n\u1ED9i dung th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t\n ki\u1EC3u v\u00E0 \u00F4 nh\u1EADp b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. 24px + \u0111\u1EC7m d\u1ECDc 4\u00D72 = 32px,\n v\u1EABn n\u1EB1m trong `min-h-10` (40px) n\u00EAn \u00F4 \u0111a ch\u1ECDn cao \u0111\u00FAng b\u1EB1ng \u00F4 \u0111\u01A1n ch\u1ECDn.\n -->\n <span\n class=\"inline-flex h-6 items-center gap-1 px-2 text-xs font-medium bg-brand-softer text-fg-brand-strong rounded-md border border-brand-subtle shrink-0\"\n >\n <span>{{ getDisplayLabelForValue(itemVal) }}</span>\n @if (!disabled() && !readonly()) {\n <!-- Vu\u00F4ng nh\u01B0 n\u00FAt xo\u00E1 chung \u2014 xem l\u00FD do \u1EDF \u0111\u00F3. -->\n <button\n type=\"button\"\n (click)=\"removeTag(itemVal, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabelForValue(itemVal)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!-- Input text field -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n type=\"text\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"multiple() && value()?.length > 0 ? '' : placeholder()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchChange($event)\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"flex-1 min-w-20 bg-transparent border-0 outline-none placeholder:text-body-subtle font-normal text-heading\"\n [ngClass]=\"fieldTextClass()\"\n />\n\n <!-- M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0111\u1ED5i vai theo tr\u1EA1ng th\u00E1i \u2014 kh\u00F4ng x\u1EBFp c\u1EA1nh nhau.\n Tr\u01B0\u1EDBc \u0111\u00E2y n\u00FAt xo\u00E1 v\u00E0 `chevron-down` C\u00D9NG hi\u1EC7n: \u0111o /autocomplete ng\u00E0y\n 2026-09-16 \u0111\u1EBFm 3 ph\u1EA7n t\u1EED chen trong c\u1EE5m 44px, c\u1ED9ng spinner khi `loading`\n l\u00E0 b\u1ED1n. \u00D4 r\u1ED9ng c\u1ED1 \u0111\u1ECBnh `w-7` \u0111\u1EC3 khung kh\u00F4ng nh\u1EA3y khi icon \u0111\u1ED5i vai.\n\n `justify-end` ch\u1EE9 KH\u00D4NG `justify-center`: \u00F4 r\u1ED9ng 28px m\u00E0 icon ch\u1EC9 16px\n n\u00EAn c\u0103n gi\u1EEFa \u0111\u1EC3 d\u01B0 6px m\u1ED7i b\u00EAn, \u0111\u1EA9y icon v\u00E0o trong. \u0110o 2026-09-17 tr\u00EAn\n 13 \u00F4 c\u1EE7a /autocomplete: l\u1EC1 tr\u00E1i 15px, l\u1EC1 ph\u1EA3i **21px** \u2014 l\u1EC7ch \u0111\u00FAng 6px\n \u1EDF M\u1ECCI \u00F4, c\u1EA3 \u0111\u01A1n ch\u1ECDn l\u1EABn \u0111a ch\u1ECDn. Con s\u1ED1 kh\u00F4ng \u0111\u1ED5i theo n\u1ED9i dung n\u00EAn\n \u0111\u00E2y l\u00E0 l\u1ED7i c\u1EA5u tr\u00FAc, kh\u00F4ng ph\u1EA3i do ch\u1EEF d\u00E0i ng\u1EAFn.\n\n `ol-select` \u0111\u00E3 ch\u1ED1t `justify-end` t\u1EEB l\u01B0\u1EE3t tr\u01B0\u1EDBc; \u0111\u00E2y l\u00E0 l\u1EA7n s\u1EEDa cho\n hai component kh\u1EDBp nhau. -->\n <div class=\"ms-auto flex w-7 shrink-0 items-center justify-end\">\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (\n (searchQuery() || (multiple() && value()?.length > 0) || (!multiple() && value())) &&\n !disabled() &&\n !readonly()\n ) {\n <!--\n N\u00FAt b\u1ECDc `size-4` \u0110\u00DANG B\u1EB0NG icon, kh\u00F4ng `size-6`.\n\n `size-6` (24px) b\u1ECDc SVG 16px th\u00EC d\u01B0 4px m\u1ED7i b\u00EAn, \u0111\u1EA9y icon v\u00E0o trong:\n \u0111o 2026-09-17 th\u1EA5y \u00F4 \u0111ang hi\u1EC7n n\u00FAt xo\u00E1 c\u00F3 l\u1EC1 ph\u1EA3i **19px** trong khi\n \u00F4 hi\u1EC7n chevron tr\u1EA7n ch\u1EC9 15px \u2014 c\u00F9ng m\u1ED9t component m\u00E0 hai tr\u1EA1ng th\u00E1i\n l\u1EC7ch nhau 4px, \u0111\u00FAng ch\u1ED7 anh ch\u1EC9 trong \u1EA3nh.\n\n V\u00F9ng b\u1EA5m v\u1EABn \u0111\u1EE7 l\u1EDBn nh\u1EDD `p-1` \u1EDF NGO\u00C0I khung \u0111o: `size-4` + \u0111\u1EC7m\n kh\u00F4ng c\u1ED9ng v\u00E0o b\u1EC1 r\u1ED9ng chi\u1EBFm ch\u1ED7 v\u00EC n\u00FAt n\u1EB1m trong \u00F4 `w-7` c\u1ED1 \u0111\u1ECBnh.\n -->\n <button\n type=\"button\"\n (click)=\"clearAll($event)\"\n aria-label=\"Xo\u00E1 to\u00E0n b\u1ED9 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n\n <!--\n Dropdown Suggestions Menu.\n\n Token \u0111\u1ECDc t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147, Type=Default):\n bo g\u00F3c 12px \u00B7 \u0111\u1EC7m 8px \u00B7 n\u1EC1n `bg-neutral-primary-medium` \u00B7 vi\u1EC1n\n `border-default-medium`.\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng n\u00EAn \u0111\u1ED5i hay kh\u00F4ng nh\u00ECn c\u0169ng\n th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I kh\u00E1c h\u1EB3n: `bg-primary` l\u00E0 gray-950 (g\u1EA7n \u0111en, panel\n ch\u00ECm v\u00E0o n\u1EC1n) c\u00F2n `bg-primary-medium` l\u00E0 gray-800 (panel n\u1ED5i l\u00EAn).\n `ol-select` c\u00F9ng l\u1EC7ch n\u00E0y, s\u1EEDa chung m\u1ED9t l\u01B0\u1EE3t.\n -->\n @if (isOpen()) {\n <div\n #panel\n [id]=\"listboxId()\"\n role=\"listbox\"\n [attr.aria-label]=\"label() || 'G\u1EE3i \u00FD'\"\n class=\"absolute z-dropdown bg-neutral-primary-medium border border-default-medium rounded-md shadow-lg max-h-[var(--ol-overlay-max-height,15rem)] overflow-y-auto overflow-x-hidden p-2 animate-in fade-in zoom-in-95 duration-150\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (filteredOptions().length > 0) {\n @for (item of filteredOptions(); track getItemValue(item); let idx = $index) {\n <!-- Ti\u00EAu \u0111\u1EC1 nh\u00F3m ch\u00E8n gi\u1EEFa v\u00F2ng l\u1EB7p ph\u1EB3ng, xem `groupNameAt`. -->\n @if (groupNameAt(idx); as groupName) {\n <div\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ groupName }}\n </div>\n }\n <div\n [id]=\"optionId(idx)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [attr.aria-disabled]=\"item?.disabled ? 'true' : null\"\n (click)=\"selectOption(item)\"\n class=\"flex items-center justify-between px-3 py-2 text-sm rounded-lg cursor-pointer transition-colors\"\n [class.bg-brand-softer]=\"isSelected(item) || activeIndex() === idx\"\n [class.hover:bg-neutral-tertiary-medium]=\"!isSelected(item) && activeIndex() !== idx\"\n [class.opacity-50]=\"item?.disabled\"\n [class.cursor-not-allowed]=\"item?.disabled\"\n >\n @if (optionTemplate) {\n <!-- Custom Option Template -->\n <ng-container\n *ngTemplateOutlet=\"\n optionTemplate;\n context: { $implicit: item, selected: isSelected(item) }\n \"\n />\n } @else {\n <!-- Default Option Display -->\n <div class=\"flex items-center gap-2.5 overflow-hidden\">\n @if (item?.avatar) {\n <img\n [src]=\"item.avatar\"\n alt=\"\u1EA2nh \u0111\u1EA1i di\u1EC7n\"\n class=\"w-7 h-7 rounded-full object-cover shrink-0\"\n />\n }\n <div class=\"truncate\">\n <span class=\"font-medium text-heading block truncate\">{{\n getItemLabel(item)\n }}</span>\n @if (item?.subLabel) {\n <span class=\"text-xs text-body-subtle block truncate\">{{ item.subLabel }}</span>\n }\n </div>\n </div>\n\n <div class=\"flex items-center gap-2 shrink-0 ms-2\">\n @if (item?.badge) {\n <span\n class=\"px-1.5 py-0.5 text-2xs font-medium rounded bg-neutral-tertiary-medium text-body\"\n >\n {{ item.badge }}\n </span>\n }\n @if (isSelected(item)) {\n <ol-icon name=\"check\" size=\"sm\" />\n }\n </div>\n }\n </div>\n }\n } @else if (waitingForTerm()) {\n <!--\n Ch\u1EBF \u0111\u1ED9 L\u1ECCC T\u1EEA XA, ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`. Ph\u1EA3i l\u00E0 th\u00F4ng b\u00E1o RI\u00CANG\n ch\u1EE9 kh\u00F4ng d\u00F9ng `emptyText`: \"Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3\" l\u00E0 sai s\u1EF1 th\u1EADt \u2014\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i v\u1EDBi `typeToSearchText` c\u1EE7a ng-select.\n -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ typeToSearchText() }}</p>\n </div>\n } @else {\n <!-- Empty State & Allow Create Option -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ emptyText() }}</p>\n @if (allowCreate() && searchQuery().trim()) {\n <button\n type=\"button\"\n (click)=\"onCreateNew()\"\n class=\"mt-2 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-fg-brand bg-brand-softer hover:bg-brand-subtle rounded-lg transition-colors\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>Th\u00EAm m\u1EDBi: \"{{ searchQuery().trim() }}\"</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
13790
|
-
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], menuClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuClass", required: false }] }], matchTriggerWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchTriggerWidth", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], remoteSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteSearch", required: false }] }], minTermLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTermLength", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], typeToSearchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeToSearchText", required: false }] }], createOption: [{ type: i0.Output, args: ["createOption"] }], search: [{ type: i0.Output, args: ["search"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], optionTemplate: [{
|
|
14763
|
+
], template: "<div class=\"relative w-full\" [class]=\"labelLayout()\" [style.--ol-label-w]=\"labelWidth() || null\">\n @if (label() && !floatingLabel()) {\n <ol-field-label\n [beside]=\"labelBeside()\"\n [size]=\"resolvedSize()\"\n [text]=\"label()\"\n [forId]=\"inputId()\"\n [required]=\"required() || showAsterisk()\"\n />\n }\n\n <ol-field-frame\n [enabled]=\"floatingLabel()\"\n [label]=\"label()\"\n [forId]=\"inputId()\"\n [variant]=\"floatingVariant()\"\n [size]=\"resolvedSize()\"\n [state]=\"state()\"\n [disabled]=\"disabled()\"\n [required]=\"required() || showAsterisk()\"\n [hasValue]=\"hasFloatValue()\"\n [open]=\"isOpen()\"\n >\n <!--\n Main Input Box Container.\n\n `py-1` ch\u1EE9 KH\u00D4NG ph\u1EA3i `p-1.5` cho ch\u1EBF \u0111\u1ED9 multiple: `p-1.5` \u0111\u1EC7m M\u1ECCI PH\u00CDA 6px,\n c\u1ED9ng v\u00E0o n\u1ED9i dung 34px th\u00E0nh 46px \u2014 v\u01B0\u1EE3t `min-h-10` (40px). \u0110o 2026-09-17:\n \u00F4 \u0111a ch\u1ECDn cao **46px** trong khi \u00F4 \u0111\u01A1n ch\u1ECDn ngay c\u1EA1nh cao **40px**, hai \u00F4\n c\u00F9ng m\u1ED9t h\u00E0ng l\u01B0\u1EDBi n\u00EAn l\u1EC7ch th\u1EA5y r\u00F5. \u0110\u1EC7m d\u1ECDc 4px v\u1EEBa \u0111\u1EE7 cho th\u1EBB cao 24px\n (4 + 24 + 4 = 32 < 40) m\u00E0 v\u1EABn c\u00F2n ch\u1ED7 khi th\u1EBB xu\u1ED1ng d\u00F2ng th\u1EE9 hai.\n\n `content-center` \u0111i k\u00E8m `items-center`: v\u1EDBi `flex-wrap`, `items-center` ch\u1EC9\n c\u0103n gi\u1EEFa TRONG T\u1EEANG D\u00D2NG, c\u1EA3 kh\u1ED1i nhi\u1EC1u d\u00F2ng v\u1EABn d\u00EDnh m\u00E9p tr\u00EAn.\n -->\n <div\n class=\"ol-field-box relative flex items-center content-center flex-wrap gap-1.5 w-full border shadow-xs\"\n [class.border-default-medium]=\"!isOpen() && state() === 'default'\"\n [class.rounded-md]=\"true\"\n [class.min-h-9]=\"resolvedSize() === 'sm'\"\n [class.min-h-10]=\"resolvedSize() === 'base'\"\n [class.min-h-12]=\"resolvedSize() === 'lg'\"\n [class.min-h-13]=\"resolvedSize() === 'xl'\"\n [class.bg-neutral-secondary-medium]=\"state() === 'default' && !disabled() && !readonly()\"\n [class.border-brand]=\"isOpen() && state() === 'default'\"\n [class.ring-2]=\"isOpen()\"\n [class.ring-brand-subtle]=\"isOpen()\"\n [class.border-danger]=\"state() === 'error' && !disabled()\"\n [class.bg-danger-soft]=\"state() === 'error' && !disabled()\"\n [class.border-success]=\"state() === 'success' && !disabled()\"\n [class.bg-success-soft]=\"state() === 'success' && !disabled()\"\n [class.bg-disabled]=\"disabled() || readonly()\"\n [class.cursor-not-allowed]=\"disabled()\"\n [class.opacity-60]=\"disabled()\"\n [ngClass]=\"boxPaddingClass()\"\n >\n <!--\n Icon k\u00EDnh l\u00FAp LU\u00D4N hi\u1EC7n. B\u1EA3n c\u0169 \u1EA9n n\u00F3 khi `multiple` \u0111\u00E3 c\u00F3 gi\u00E1 tr\u1ECB\n (`@if (!multiple() || !value() || value()?.length === 0)`) \u2014 ch\u1ECDn m\u1ED9t th\u1EBB\n l\u00E0 icon bi\u1EBFn m\u1EA5t, xo\u00E1 h\u1EBFt th\u1EBB th\u00EC n\u00F3 quay l\u1EA1i. \u0110o 2026-09-17 tr\u00EAn\n `/autocomplete`: \u00F4 \u0111\u01A1n ch\u1ECDn 1 icon `magnifer`, \u00F4 \u0111a ch\u1ECDn **0 icon** khi\n c\u00F3 2 th\u1EBB.\n\n Figma kh\u00F4ng c\u00F3 c\u01A1 s\u1EDF cho vi\u1EC7c \u1EA9n: \u0111\u1ECDc `ol-autocomplete-examples`\n (#1149:53126) th\u00EC **6/6 Type \u0111\u1EC1u c\u00F3 `search` trong `ol-input`**, kh\u00F4ng\n Type n\u00E0o b\u1ECF. Figma c\u0169ng kh\u00F4ng v\u1EBD bi\u1EBFn th\u1EC3 \u0111a ch\u1ECDn c\u00F3 th\u1EBB, n\u00EAn \u0111\u00E2y l\u00E0 l\u1EF1a\n ch\u1ECDn c\u1EE7a code ch\u1EE9 kh\u00F4ng ph\u1EA3i thi\u1EBFt k\u1EBF.\n\n `shrink-0` \u0111\u1EC3 icon kh\u00F4ng b\u1ECB c\u00E1c th\u1EBB \u00E9p b\u1EB9p khi \u00F4 ch\u1EADt.\n -->\n <ol-icon name=\"magnifer\" size=\"sm\" class=\"shrink-0 me-0.5\" />\n\n <!-- Multiple Selected Tags / Chips -->\n @if (multiple() && value() && value()?.length > 0) {\n @for (itemVal of value(); track itemVal) {\n <!--\n `h-6` c\u1ED1 \u0111\u1ECBnh (24px): \u0111\u1EC3 th\u1EBB t\u1EF1 co theo n\u1ED9i dung th\u00EC m\u1ED7i th\u1EBB cao m\u1ED9t\n ki\u1EC3u v\u00E0 \u00F4 nh\u1EADp b\u1ECB \u0111\u1EA9y theo th\u1EBB cao nh\u1EA5t. 24px + \u0111\u1EC7m d\u1ECDc 4\u00D72 = 32px,\n v\u1EABn n\u1EB1m trong `min-h-10` (40px) n\u00EAn \u00F4 \u0111a ch\u1ECDn cao \u0111\u00FAng b\u1EB1ng \u00F4 \u0111\u01A1n ch\u1ECDn.\n -->\n <span\n class=\"inline-flex h-6 items-center gap-1 px-2 text-xs font-medium bg-brand-softer text-fg-brand-strong rounded-md border border-brand-subtle shrink-0\"\n >\n <span>{{ getDisplayLabelForValue(itemVal) }}</span>\n @if (!disabled() && !readonly()) {\n <!-- Vu\u00F4ng nh\u01B0 n\u00FAt xo\u00E1 chung \u2014 xem l\u00FD do \u1EDF \u0111\u00F3. -->\n <button\n type=\"button\"\n (click)=\"removeTag(itemVal, $event)\"\n [attr.aria-label]=\"'G\u1EE1 ' + getDisplayLabelForValue(itemVal)\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-fg-brand hover:text-fg-brand-strong focus:outline-hidden\"\n >\n <ol-icon name=\"close\" size=\"xs\" />\n </button>\n }\n </span>\n }\n }\n\n <!-- Input text field -->\n <input\n [id]=\"inputId()\"\n [attr.aria-invalid]=\"ariaInvalid()\"\n [attr.aria-describedby]=\"describedBy()\"\n role=\"combobox\"\n aria-autocomplete=\"list\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-controls]=\"listboxId()\"\n [attr.aria-activedescendant]=\"activeOptionId()\"\n type=\"text\"\n [disabled]=\"disabled()\"\n [readonly]=\"readonly()\"\n [placeholder]=\"multiple() && value()?.length > 0 ? '' : placeholder()\"\n [ngModel]=\"searchQuery()\"\n (ngModelChange)=\"onSearchChange($event)\"\n (focus)=\"onInputFocus()\"\n (keydown)=\"onKeyDown($event)\"\n class=\"flex-1 bg-transparent border-0 outline-none placeholder:text-body-subtle font-normal text-heading\"\n [ngClass]=\"fieldTextClass() + ' ' + (hasChips() ? 'min-w-0 focus:min-w-16' : 'min-w-20')\"\n />\n\n <!--\n M\u1ED8T \u00F4 \u0111i\u1EC1u khi\u1EC3n, \u0111\u1ED5i vai theo tr\u1EA1ng th\u00E1i \u2014 n\u00FAt xo\u00E1, chevron ho\u1EB7c\n spinner, kh\u00F4ng x\u1EBFp c\u1EA1nh nhau (b\u1EA3n c\u0169 hi\u1EC7n c\u00F9ng l\u00FAc 3\u20134 ph\u1EA7n t\u1EED). \u00D4 r\u1ED9ng\n \u0111\u00FAng b\u1EB1ng icon (`controlClass`): ch\u1EEF \u2194 icon 8px nh\u01B0 Figma.\n -->\n <!--\n Ch\u1ECDn nhi\u1EC1u: c\u1EE5m n\u00FAt ra kh\u1ECFi lu\u1ED3ng (absolute, nh\u01B0 ol-select) v\u00E0 khung\n ch\u1EEBa pe-9. \u0110\u1EC3 trong lu\u1ED3ng th\u00EC c\u1EE5m n\u00FAt + \u00F4 g\u00F5 kh\u00F4ng v\u1EEBa ph\u1EA7n c\u00F2n l\u1EA1i c\u1EE7a\n d\u00F2ng chip cu\u1ED1i, c\u00F9ng r\u1EDBt xu\u1ED1ng m\u1ED9t d\u00F2ng m\u1EDBi ch\u1EC9 ch\u1EE9a kho\u1EA3ng tr\u1ED1ng \u2014 \u0111o\n 2026-09-29: d\u01B0 31\u201336px \u1EDF m\u1ECDi ki\u1EC3u v\u00E0 c\u1EE1.\n -->\n <div [class]=\"controlClass()\">\n @if (loading()) {\n <ol-spinner size=\"xs\" color=\"brand\" />\n } @else if (\n (searchQuery() || (multiple() && value()?.length > 0) || (!multiple() && value())) &&\n !disabled() &&\n !readonly()\n ) {\n <!--\n N\u00FAt b\u1ECDc `size-4` \u0110\u00DANG B\u1EB0NG icon, kh\u00F4ng `size-6`.\n\n `size-6` (24px) b\u1ECDc SVG 16px th\u00EC d\u01B0 4px m\u1ED7i b\u00EAn, \u0111\u1EA9y icon v\u00E0o trong:\n \u0111o 2026-09-17 th\u1EA5y \u00F4 \u0111ang hi\u1EC7n n\u00FAt xo\u00E1 c\u00F3 l\u1EC1 ph\u1EA3i **19px** trong khi\n \u00F4 hi\u1EC7n chevron tr\u1EA7n ch\u1EC9 15px \u2014 c\u00F9ng m\u1ED9t component m\u00E0 hai tr\u1EA1ng th\u00E1i\n l\u1EC7ch nhau 4px, \u0111\u00FAng ch\u1ED7 anh ch\u1EC9 trong \u1EA3nh.\n\n V\u00F9ng b\u1EA5m v\u1EABn \u0111\u1EE7 l\u1EDBn nh\u1EDD `p-1` \u1EDF NGO\u00C0I khung \u0111o: `size-4` + \u0111\u1EC7m\n kh\u00F4ng c\u1ED9ng v\u00E0o b\u1EC1 r\u1ED9ng chi\u1EBFm ch\u1ED7 v\u00EC n\u00FAt n\u1EB1m trong \u00F4 `w-7` c\u1ED1 \u0111\u1ECBnh.\n -->\n <button\n type=\"button\"\n (click)=\"clearAll($event)\"\n aria-label=\"Xo\u00E1 to\u00E0n b\u1ED9 l\u1EF1a ch\u1ECDn\"\n class=\"inline-flex size-4 shrink-0 items-center justify-center rounded-full text-body-subtle transition-colors hover:text-heading\"\n >\n <ol-icon name=\"close\" size=\"sm\" />\n </button>\n } @else {\n <ol-icon\n name=\"chevron-down\"\n size=\"sm\"\n class=\"transition-transform\"\n [class.rotate-180]=\"isOpen()\"\n />\n }\n </div>\n </div>\n </ol-field-frame>\n\n <!--\n Dropdown Suggestions Menu.\n\n Token \u0111\u1ECDc t\u1EEB Figma `ol-autocomplete-menu` (#1149:53147, Type=Default):\n bo g\u00F3c 12px \u00B7 \u0111\u1EC7m 8px \u00B7 n\u1EC1n `bg-neutral-primary-medium` \u00B7 vi\u1EC1n\n `border-default-medium`.\n\n Hai token n\u1EC1n/vi\u1EC1n TR\u00D9NG NHAU \u1EDF ch\u1EBF \u0111\u1ED9 s\u00E1ng n\u00EAn \u0111\u1ED5i hay kh\u00F4ng nh\u00ECn c\u0169ng\n th\u1EBF \u2014 nh\u01B0ng CH\u1EBE \u0110\u1ED8 T\u1ED0I kh\u00E1c h\u1EB3n: `bg-primary` l\u00E0 gray-950 (g\u1EA7n \u0111en, panel\n ch\u00ECm v\u00E0o n\u1EC1n) c\u00F2n `bg-primary-medium` l\u00E0 gray-800 (panel n\u1ED5i l\u00EAn).\n `ol-select` c\u00F9ng l\u1EC7ch n\u00E0y, s\u1EEDa chung m\u1ED9t l\u01B0\u1EE3t.\n -->\n @if (isOpen()) {\n <div\n #panel\n [id]=\"listboxId()\"\n role=\"listbox\"\n [attr.aria-label]=\"label() || 'G\u1EE3i \u00FD'\"\n class=\"absolute z-dropdown bg-neutral-primary-medium border border-default-medium rounded-md shadow-lg max-h-[var(--ol-overlay-max-height,15rem)] overflow-y-auto overflow-x-hidden p-2 animate-in fade-in zoom-in-95 duration-150\"\n [class.w-full]=\"matchTriggerWidth()\"\n [class]=\"menuClass()\"\n >\n @if (filteredOptions().length > 0) {\n @for (item of filteredOptions(); track getItemValue(item); let idx = $index) {\n <!-- Ti\u00EAu \u0111\u1EC1 nh\u00F3m ch\u00E8n gi\u1EEFa v\u00F2ng l\u1EB7p ph\u1EB3ng, xem `groupNameAt`. -->\n @if (groupNameAt(idx); as groupName) {\n <div\n role=\"presentation\"\n class=\"px-3 pt-2 pb-1 text-xs font-semibold uppercase tracking-wide text-body-subtle\"\n >\n {{ groupName }}\n </div>\n }\n <div\n [id]=\"optionId(idx)\"\n role=\"option\"\n [attr.aria-selected]=\"isSelected(item)\"\n [attr.aria-disabled]=\"item?.disabled ? 'true' : null\"\n (click)=\"selectOption(item)\"\n class=\"flex items-center justify-between px-3 py-2 text-sm rounded-lg cursor-pointer transition-colors\"\n [class.bg-brand-softer]=\"isSelected(item) || activeIndex() === idx\"\n [class.hover:bg-neutral-tertiary-medium]=\"!isSelected(item) && activeIndex() !== idx\"\n [class.opacity-50]=\"item?.disabled\"\n [class.cursor-not-allowed]=\"item?.disabled\"\n >\n @if (optionTemplate) {\n <!-- Custom Option Template -->\n <ng-container\n *ngTemplateOutlet=\"\n optionTemplate;\n context: { $implicit: item, selected: isSelected(item) }\n \"\n />\n } @else {\n <!-- Default Option Display -->\n <div class=\"flex items-center gap-2.5 overflow-hidden\">\n @if (item?.avatar) {\n <img\n [src]=\"item.avatar\"\n alt=\"\u1EA2nh \u0111\u1EA1i di\u1EC7n\"\n class=\"w-7 h-7 rounded-full object-cover shrink-0\"\n />\n }\n <div class=\"truncate\">\n <span class=\"font-medium text-heading block truncate\">{{\n getItemLabel(item)\n }}</span>\n @if (item?.subLabel) {\n <span class=\"text-xs text-body-subtle block truncate\">{{ item.subLabel }}</span>\n }\n </div>\n </div>\n\n <div class=\"flex items-center gap-2 shrink-0 ms-2\">\n @if (item?.badge) {\n <span\n class=\"px-1.5 py-0.5 text-2xs font-medium rounded bg-neutral-tertiary-medium text-body\"\n >\n {{ item.badge }}\n </span>\n }\n @if (isSelected(item)) {\n <ol-icon name=\"check\" size=\"sm\" />\n }\n </div>\n }\n </div>\n }\n } @else if (waitingForTerm()) {\n <!--\n Ch\u1EBF \u0111\u1ED9 L\u1ECCC T\u1EEA XA, ch\u01B0a g\u00F5 \u0111\u1EE7 `minTermLength`. Ph\u1EA3i l\u00E0 th\u00F4ng b\u00E1o RI\u00CANG\n ch\u1EE9 kh\u00F4ng d\u00F9ng `emptyText`: \"Kh\u00F4ng t\u00ECm th\u1EA5y k\u1EBFt qu\u1EA3\" l\u00E0 sai s\u1EF1 th\u1EADt \u2014\n ch\u01B0a t\u00ECm g\u00EC c\u1EA3. C\u00F9ng l\u1ED1i v\u1EDBi `typeToSearchText` c\u1EE7a ng-select.\n -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ typeToSearchText() }}</p>\n </div>\n } @else {\n <!-- Empty State & Allow Create Option -->\n <div class=\"p-3 text-center text-sm text-body-subtle\">\n <p>{{ emptyText() }}</p>\n @if (allowCreate() && searchQuery().trim()) {\n <button\n type=\"button\"\n (click)=\"onCreateNew()\"\n class=\"mt-2 inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-fg-brand bg-brand-softer hover:bg-brand-subtle rounded-lg transition-colors\"\n >\n <ol-icon name=\"plus\" size=\"xs\" />\n <span>Th\u00EAm m\u1EDBi: \"{{ searchQuery().trim() }}\"</span>\n </button>\n }\n </div>\n }\n </div>\n }\n\n <!-- Helper Text & Error Text -->\n @if (state() === 'error' && errorText()) {\n <p class=\"mt-1.5 text-xs text-fg-danger\">{{ errorText() }}</p>\n } @else if (helperText()) {\n <p class=\"mt-1.5 text-xs text-body\">{{ helperText() }}</p>\n }\n</div>\n" }]
|
|
14764
|
+
}], ctorParameters: () => [{ type: i0.ElementRef }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], floatingLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingLabel", required: false }] }], labelPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelPosition", required: false }] }], labelWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelWidth", required: false }] }], floatingVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "floatingVariant", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], labelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "labelKey", required: false }] }], valueKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "valueKey", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }, { type: i0.Output, args: ["disabledChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], state: [{ type: i0.Input, args: [{ isSignal: true, alias: "state", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], showAsterisk: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAsterisk", required: false }] }], helperText: [{ type: i0.Input, args: [{ isSignal: true, alias: "helperText", required: false }] }], errorText: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorText", required: false }] }], id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], allowCreate: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowCreate", required: false }] }], emptyText: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyText", required: false }] }], appendTo: [{ type: i0.Input, args: [{ isSignal: true, alias: "appendTo", required: false }] }], menuClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuClass", required: false }] }], matchTriggerWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchTriggerWidth", required: false }] }], groupBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupBy", required: false }] }], remoteSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "remoteSearch", required: false }] }], minTermLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minTermLength", required: false }] }], debounceMs: [{ type: i0.Input, args: [{ isSignal: true, alias: "debounceMs", required: false }] }], typeToSearchText: [{ type: i0.Input, args: [{ isSignal: true, alias: "typeToSearchText", required: false }] }], createOption: [{ type: i0.Output, args: ["createOption"] }], search: [{ type: i0.Output, args: ["search"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], added: [{ type: i0.Output, args: ["added"] }], removed: [{ type: i0.Output, args: ["removed"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], optionTemplate: [{
|
|
13791
14765
|
type: ContentChild,
|
|
13792
14766
|
args: ['optionTemplate']
|
|
13793
14767
|
}], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], onDocumentClick: [{
|
|
@@ -13944,12 +14918,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
13944
14918
|
}]
|
|
13945
14919
|
}], propDecorators: { layout: [{ type: i0.Input, args: [{ isSignal: true, alias: "layout", required: false }] }] } });
|
|
13946
14920
|
|
|
13947
|
-
/**
|
|
13948
|
-
* Mục menu xếp dọc trong ☰. Nơi gọi hay bọc mục trong một
|
|
13949
|
-
* `<div slot="navigation" class="flex items-center">` — wrapper đó cũng phải
|
|
13950
|
-
* xếp dọc, không thì mục vẫn nằm ngang và panel dropdown của mục thứ hai tràn
|
|
13951
|
-
* phải (đo /navbar ví dụ 6 trên màn 390px: panel 172→476px).
|
|
13952
|
-
*/
|
|
13953
14921
|
/**
|
|
13954
14922
|
* Panel ☰ NỔI dưới thanh, đè lên nội dung (báo 2026-09-28: "menu mobile phải
|
|
13955
14923
|
* đè lên nội dung chứ không đẩy nội dung xuống"). Khi khối mục menu cũng đang
|
|
@@ -13958,13 +14926,51 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
13958
14926
|
* Giới hạn cao theo màn và tự cuộn: menu dài trên điện thoại không bị cắt.
|
|
13959
14927
|
*/
|
|
13960
14928
|
const OVERLAY = 'absolute inset-x-0 top-[calc(100%+var(--ol-nav-stack-h,0px))] z-dropdown max-h-[calc(100dvh-5rem)] overflow-y-auto';
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
13965
|
-
|
|
13966
|
-
|
|
13967
|
-
|
|
14929
|
+
/**
|
|
14930
|
+
* Mục menu xếp dọc trong ☰, NỔI dưới thanh (absolute). Nơi gọi hay bọc mục
|
|
14931
|
+
* trong `<div slot="navigation" class="flex items-center">` — wrapper đó cũng
|
|
14932
|
+
* phải xếp dọc, không thì mục vẫn nằm ngang và panel dropdown của mục thứ hai
|
|
14933
|
+
* tràn phải (đo /navbar ví dụ 6 trên màn 390px: panel 172→476px).
|
|
14934
|
+
*/
|
|
14935
|
+
const STACKED = 'flex flex-col items-stretch gap-1 absolute inset-x-0 top-full z-dropdown bg-neutral-primary border-t border-default px-4 py-3 shadow-xl [&>[slot=navigation]]:flex-col [&>[slot=navigation]]:items-stretch';
|
|
14936
|
+
/**
|
|
14937
|
+
* Class theo từng mốc — VIẾT SẴN nguyên văn: Tailwind chỉ sinh CSS cho chuỗi
|
|
14938
|
+
* xuất hiện trong mã, ghép `${bp}:flex` lúc chạy là ra class không có luật nào.
|
|
14939
|
+
*/
|
|
14940
|
+
const BREAKPOINT_CLASSES = {
|
|
14941
|
+
md: {
|
|
14942
|
+
row: '@3xl:flex',
|
|
14943
|
+
wide: '@3xl:flex @3xl:flex-row @3xl:items-center @3xl:p-0 @3xl:border-0',
|
|
14944
|
+
stackReset: '@3xl:static @3xl:bg-transparent @3xl:shadow-none @3xl:[&>[slot=navigation]]:flex-row @3xl:[&>[slot=navigation]]:items-center',
|
|
14945
|
+
hide: '@3xl:hidden',
|
|
14946
|
+
},
|
|
14947
|
+
lg: {
|
|
14948
|
+
row: '@5xl:flex',
|
|
14949
|
+
wide: '@5xl:flex @5xl:flex-row @5xl:items-center @5xl:p-0 @5xl:border-0',
|
|
14950
|
+
stackReset: '@5xl:static @5xl:bg-transparent @5xl:shadow-none @5xl:[&>[slot=navigation]]:flex-row @5xl:[&>[slot=navigation]]:items-center',
|
|
14951
|
+
hide: '@5xl:hidden',
|
|
14952
|
+
},
|
|
14953
|
+
xl: {
|
|
14954
|
+
row: '@[78rem]:flex',
|
|
14955
|
+
wide: '@[78rem]:flex @[78rem]:flex-row @[78rem]:items-center @[78rem]:p-0 @[78rem]:border-0',
|
|
14956
|
+
stackReset: '@[78rem]:static @[78rem]:bg-transparent @[78rem]:shadow-none @[78rem]:[&>[slot=navigation]]:flex-row @[78rem]:[&>[slot=navigation]]:items-center',
|
|
14957
|
+
hide: '@[78rem]:hidden',
|
|
14958
|
+
},
|
|
14959
|
+
};
|
|
14960
|
+
const DENSITIES = ['full', 'medium', 'text', 'compact'];
|
|
14961
|
+
/**
|
|
14962
|
+
* `ol-nav-icon` / `ol-nav-label` do `ol-navbar-item`, `ol-nav-dropdown` gắn.
|
|
14963
|
+
* Nhãn chỉ mang `ol-nav-label` khi mục có icon — mục chỉ có chữ không bao giờ
|
|
14964
|
+
* mất chữ. Icon chỉ bị ẩn khi NGAY SAU nó có nhãn không rỗng (`:has(~ …)`) —
|
|
14965
|
+
* mục chỉ có icon không bao giờ mất icon, và chữ viết bằng nội dung chiếu vào
|
|
14966
|
+
* (không qua input `label`) cũng được tính.
|
|
14967
|
+
*
|
|
14968
|
+
* `hidden!` (important) cho icon: `ol-icon` khai `:host { display: inline-flex }`
|
|
14969
|
+
* trong CSS component — CSS ngoài layer luôn thắng `@layer utilities` bất kể độ
|
|
14970
|
+
* ưu tiên selector, nên `hidden` thường im lặng không có tác dụng (đo
|
|
14971
|
+
* 2026-09-28: luật có sinh, selector khớp, icon vẫn `display: flex`).
|
|
14972
|
+
*/
|
|
14973
|
+
const COMPACT_LABELS = 'group-data-[density=text]/navbar:[&_.ol-nav-icon:has(~.ol-nav-label:not(:empty))]:hidden! group-data-[density=compact]/navbar:[&_.ol-nav-label]:sr-only';
|
|
13968
14974
|
class OlNavbar {
|
|
13969
14975
|
brandLogo = input('', /* @ts-ignore */
|
|
13970
14976
|
...(ngDevMode ? [{ debugName: "brandLogo" }] : /* istanbul ignore next */ []));
|
|
@@ -13980,6 +14986,15 @@ class OlNavbar {
|
|
|
13980
14986
|
...(ngDevMode ? [{ debugName: "showSearch" }] : /* istanbul ignore next */ []));
|
|
13981
14987
|
searchPlaceholder = input('Tìm kiếm toàn cục (Ctrl + K)...', /* @ts-ignore */
|
|
13982
14988
|
...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
14989
|
+
/** Thanh hẹp hơn mốc này thì mục menu gập vào ☰. Xem `NavbarCollapseAt`. */
|
|
14990
|
+
collapseAt = input('xl', /* @ts-ignore */
|
|
14991
|
+
...(ngDevMode ? [{ debugName: "collapseAt" }] : /* istanbul ignore next */ []));
|
|
14992
|
+
/**
|
|
14993
|
+
* Tự co theo chỗ trống thật (`full` → `medium` → `compact`, xem
|
|
14994
|
+
* `NavbarDensity`) thay vì gập menu. Thường đi kèm `collapseAt="md"`/`"lg"`.
|
|
14995
|
+
*/
|
|
14996
|
+
compactItems = input(false, /* @ts-ignore */
|
|
14997
|
+
...(ngDevMode ? [{ debugName: "compactItems" }] : /* istanbul ignore next */ []));
|
|
13983
14998
|
isCollapsed = model(true, /* @ts-ignore */
|
|
13984
14999
|
...(ngDevMode ? [{ debugName: "isCollapsed" }] : /* istanbul ignore next */ []));
|
|
13985
15000
|
onSearch = output();
|
|
@@ -14001,6 +15016,7 @@ class OlNavbar {
|
|
|
14001
15016
|
afterNextRender(() => {
|
|
14002
15017
|
this.detectMobileSlots();
|
|
14003
15018
|
this.observeStackHeight();
|
|
15019
|
+
this.observeDensity();
|
|
14004
15020
|
});
|
|
14005
15021
|
}
|
|
14006
15022
|
/** Chiều cao khối mục xếp dọc (0 khi không xếp dọc) → `--ol-nav-stack-h`. */
|
|
@@ -14017,6 +15033,53 @@ class OlNavbar {
|
|
|
14017
15033
|
observer.observe(wrap);
|
|
14018
15034
|
this.destroyRef.onDestroy(() => observer.disconnect());
|
|
14019
15035
|
}
|
|
15036
|
+
observeDensity() {
|
|
15037
|
+
const root = this.elementRef.nativeElement;
|
|
15038
|
+
const row = this.navWrap()?.nativeElement.parentElement;
|
|
15039
|
+
if (!row || typeof ResizeObserver === 'undefined')
|
|
15040
|
+
return;
|
|
15041
|
+
const observer = new ResizeObserver(() => this.updateDensity());
|
|
15042
|
+
// Quan sát cả từng khối trong hàng: đổi năm học, đổi mục đang chọn (chữ
|
|
15043
|
+
// đậm hơn) làm nội dung rộng ra mà khung không đổi.
|
|
15044
|
+
observer.observe(root);
|
|
15045
|
+
for (const child of Array.from(row.children))
|
|
15046
|
+
observer.observe(child);
|
|
15047
|
+
this.destroyRef.onDestroy(() => observer.disconnect());
|
|
15048
|
+
this.updateDensity();
|
|
15049
|
+
}
|
|
15050
|
+
/**
|
|
15051
|
+
* 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
|
|
15052
|
+
* 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
|
|
15053
|
+
* — trạng thái cuối được gán trước khi vẽ.
|
|
15054
|
+
*
|
|
15055
|
+
* Đo bằng `scrollWidth`: logo có `min-w-0 truncate`, bị ép co thì
|
|
15056
|
+
* `offsetWidth` nhỏ đi và phép đo báo "vừa" sai.
|
|
15057
|
+
*/
|
|
15058
|
+
updateDensity() {
|
|
15059
|
+
const nav = this.elementRef.nativeElement.querySelector('nav');
|
|
15060
|
+
const wrap = this.navWrap()?.nativeElement;
|
|
15061
|
+
const row = wrap?.parentElement;
|
|
15062
|
+
if (!nav || !wrap || !row)
|
|
15063
|
+
return;
|
|
15064
|
+
const inline = getComputedStyle(wrap).display !== 'none' && !this.isStacked();
|
|
15065
|
+
const apply = (density) => {
|
|
15066
|
+
nav.dataset['density'] = density;
|
|
15067
|
+
nav.toggleAttribute('data-condensed', density !== 'full');
|
|
15068
|
+
};
|
|
15069
|
+
if (!this.compactItems() || !inline)
|
|
15070
|
+
return apply('full');
|
|
15071
|
+
const gap = parseFloat(getComputedStyle(row).columnGap) || 0;
|
|
15072
|
+
const fits = () => {
|
|
15073
|
+
const kids = Array.from(row.children).filter((el) => getComputedStyle(el).display !== 'none');
|
|
15074
|
+
const need = kids.reduce((sum, el) => sum + Math.max(el.offsetWidth, el.scrollWidth), 0);
|
|
15075
|
+
return need + gap * (kids.length - 1) <= row.clientWidth + 0.5;
|
|
15076
|
+
};
|
|
15077
|
+
for (const density of DENSITIES) {
|
|
15078
|
+
apply(density);
|
|
15079
|
+
if (fits())
|
|
15080
|
+
return;
|
|
15081
|
+
}
|
|
15082
|
+
}
|
|
14020
15083
|
isStacked() {
|
|
14021
15084
|
return !this.isCollapsed() && !this.hasMobileLinks();
|
|
14022
15085
|
}
|
|
@@ -14039,16 +15102,27 @@ class OlNavbar {
|
|
|
14039
15102
|
* di động. Bản trước chỉ ẩn chúng, nên 5/7 thanh trên /navbar và /blocks bấm
|
|
14040
15103
|
* ☰ ra bảng trống (đo iPhone 13 giả lập, 2026-09-28).
|
|
14041
15104
|
*/
|
|
15105
|
+
bp = computed(() => BREAKPOINT_CLASSES[this.collapseAt()], /* @ts-ignore */
|
|
15106
|
+
...(ngDevMode ? [{ debugName: "bp" }] : /* istanbul ignore next */ []));
|
|
15107
|
+
/** Hàng trên: lưới ở khung hẹp (logo | nút), flex từ mốc gập trở lên. */
|
|
15108
|
+
rowClasses = computed(() => `grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 @[40rem]:gap-x-4 min-h-16 min-w-0 w-full ${this.bp().row}`, /* @ts-ignore */
|
|
15109
|
+
...(ngDevMode ? [{ debugName: "rowClasses" }] : /* istanbul ignore next */ []));
|
|
15110
|
+
toggleClasses = computed(() => `${this.bp().hide} p-2 rounded-md text-body-subtle hover:text-heading hover:bg-neutral-secondary-medium focus:outline-none focus:ring-2 focus:ring-brand-subtle cursor-pointer`, /* @ts-ignore */
|
|
15111
|
+
...(ngDevMode ? [{ debugName: "toggleClasses" }] : /* istanbul ignore next */ []));
|
|
14042
15112
|
navWrapClasses = computed(() => {
|
|
14043
|
-
const wide
|
|
14044
|
-
const
|
|
14045
|
-
|
|
15113
|
+
const { wide, stackReset } = this.bp();
|
|
15114
|
+
const base = `${wide} whitespace-nowrap min-w-0`;
|
|
15115
|
+
// Xếp dọc trong ☰ thì luôn hiện chữ — không áp COMPACT_LABELS.
|
|
15116
|
+
if (this.isStacked())
|
|
15117
|
+
return `${base} ${STACKED} ${stackReset}`;
|
|
15118
|
+
const compact = this.compactItems() ? ` ${COMPACT_LABELS}` : '';
|
|
15119
|
+
return `${base} hidden gap-1${compact}`;
|
|
14046
15120
|
}, /* @ts-ignore */
|
|
14047
15121
|
...(ngDevMode ? [{ debugName: "navWrapClasses" }] : /* istanbul ignore next */ []));
|
|
14048
15122
|
mobilePanelClasses = computed(() => {
|
|
14049
15123
|
const show = !this.isCollapsed() && this.hasPanelContent();
|
|
14050
15124
|
return show
|
|
14051
|
-
?
|
|
15125
|
+
? `${this.bp().hide} ${OVERLAY} border-t border-default bg-neutral-primary px-4 pt-4 pb-6 space-y-4 animate-in fade-in slide-in-from-top-2 duration-150 shadow-xl`
|
|
14052
15126
|
: 'hidden';
|
|
14053
15127
|
}, /* @ts-ignore */
|
|
14054
15128
|
...(ngDevMode ? [{ debugName: "mobilePanelClasses" }] : /* istanbul ignore next */ []));
|
|
@@ -14066,7 +15140,9 @@ class OlNavbar {
|
|
|
14066
15140
|
* còn ~1265px — đặt 80rem là màn 1280px mất bố cục desktop vốn đang có.
|
|
14067
15141
|
*/
|
|
14068
15142
|
const classes = [
|
|
14069
|
-
|
|
15143
|
+
// `group/navbar`: nội dung slot đọc `data-density` bằng
|
|
15144
|
+
// `group-data-[density=…]/navbar:…` để ẩn phần phụ khi chật.
|
|
15145
|
+
'@container group/navbar w-full bg-neutral-primary transition-colors duration-200 z-sticky',
|
|
14070
15146
|
];
|
|
14071
15147
|
if (this.sticky()) {
|
|
14072
15148
|
classes.push('sticky top-0');
|
|
@@ -14091,6 +15167,8 @@ class OlNavbar {
|
|
|
14091
15167
|
this.detectMobileSlots();
|
|
14092
15168
|
this.isCollapsed.update((v) => !v);
|
|
14093
15169
|
this.onToggleMobileMenu.emit(!this.isCollapsed());
|
|
15170
|
+
// Xếp dọc / thôi xếp dọc đổi cách đo — chờ Angular vẽ xong rồi đo lại.
|
|
15171
|
+
requestAnimationFrame(() => this.updateDensity());
|
|
14094
15172
|
}
|
|
14095
15173
|
handleSearchSubmit() {
|
|
14096
15174
|
if (this.searchQuery.trim()) {
|
|
@@ -14112,12 +15190,12 @@ class OlNavbar {
|
|
|
14112
15190
|
}
|
|
14113
15191
|
}
|
|
14114
15192
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlNavbar, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14115
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlNavbar, isStandalone: true, selector: "ol-navbar", inputs: { brandLogo: { classPropertyName: "brandLogo", publicName: "brandLogo", isSignal: true, isRequired: false, transformFunction: null }, brandName: { classPropertyName: "brandName", publicName: "brandName", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, fluid: { classPropertyName: "fluid", publicName: "fluid", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, isCollapsed: { classPropertyName: "isCollapsed", publicName: "isCollapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isCollapsed: "isCollapsedChange", onSearch: "onSearch", onToggleMobileMenu: "onToggleMobileMenu" }, host: { listeners: { "document:keydown": "handleGlobalKeydown($event)" }, classAttribute: "block w-full min-w-0" }, viewQueries: [{ propertyName: "mobilePanel", first: true, predicate: ["mobilePanel"], descendants: true, isSignal: true }, { propertyName: "navWrap", first: true, predicate: ["navWrap"], descendants: true, isSignal: true }], ngImport: i0, template: "<nav [class]=\"navbarClasses()\" role=\"navigation\" aria-label=\"Thanh \u0111i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n <div [class]=\"containerClasses()\">\n <!--\n Khung h\u1EB9p: l\u01B0\u1EDBi hai c\u1ED9t (logo | n\u00FAt), kh\u1ED1i m\u1EE5c menu khi \u2630 m\u1EDF chi\u1EBFm tr\u1ECDn\n h\u00E0ng th\u1EE9 hai (xem STACKED). Kh\u00F4ng d\u00F9ng `flex-wrap`: n\u00F3 th\u1EA3 nh\u00F3m n\u00FAt\n xu\u1ED1ng d\u00F2ng d\u01B0\u1EDBi logo khi hai th\u1EE9 c\u1ED9ng l\u1EA1i r\u1ED9ng h\u01A1n khung. Khung r\u1ED9ng: flex.\n -->\n <div
|
|
15193
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlNavbar, isStandalone: true, selector: "ol-navbar", inputs: { brandLogo: { classPropertyName: "brandLogo", publicName: "brandLogo", isSignal: true, isRequired: false, transformFunction: null }, brandName: { classPropertyName: "brandName", publicName: "brandName", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, bordered: { classPropertyName: "bordered", publicName: "bordered", isSignal: true, isRequired: false, transformFunction: null }, fluid: { classPropertyName: "fluid", publicName: "fluid", isSignal: true, isRequired: false, transformFunction: null }, showSearch: { classPropertyName: "showSearch", publicName: "showSearch", isSignal: true, isRequired: false, transformFunction: null }, searchPlaceholder: { classPropertyName: "searchPlaceholder", publicName: "searchPlaceholder", isSignal: true, isRequired: false, transformFunction: null }, collapseAt: { classPropertyName: "collapseAt", publicName: "collapseAt", isSignal: true, isRequired: false, transformFunction: null }, compactItems: { classPropertyName: "compactItems", publicName: "compactItems", isSignal: true, isRequired: false, transformFunction: null }, isCollapsed: { classPropertyName: "isCollapsed", publicName: "isCollapsed", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isCollapsed: "isCollapsedChange", onSearch: "onSearch", onToggleMobileMenu: "onToggleMobileMenu" }, host: { listeners: { "document:keydown": "handleGlobalKeydown($event)" }, classAttribute: "block w-full min-w-0" }, viewQueries: [{ propertyName: "mobilePanel", first: true, predicate: ["mobilePanel"], descendants: true, isSignal: true }, { propertyName: "navWrap", first: true, predicate: ["navWrap"], descendants: true, isSignal: true }], ngImport: i0, template: "<nav [class]=\"navbarClasses()\" role=\"navigation\" aria-label=\"Thanh \u0111i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n <div [class]=\"containerClasses()\">\n <!--\n Khung h\u1EB9p: l\u01B0\u1EDBi hai c\u1ED9t (logo | n\u00FAt), kh\u1ED1i m\u1EE5c menu khi \u2630 m\u1EDF chi\u1EBFm tr\u1ECDn\n h\u00E0ng th\u1EE9 hai (xem STACKED). Kh\u00F4ng d\u00F9ng `flex-wrap`: n\u00F3 th\u1EA3 nh\u00F3m n\u00FAt\n xu\u1ED1ng d\u00F2ng d\u01B0\u1EDBi logo khi hai th\u1EE9 c\u1ED9ng l\u1EA1i r\u1ED9ng h\u01A1n khung. Khung r\u1ED9ng: flex.\n -->\n <div [class]=\"rowClasses()\">\n <!-- Left: Brand / Logo -->\n <div class=\"flex h-16 items-center min-w-0\">\n <ng-content select=\"[slot=brand], [brand]\">\n <!--\n Logo v\u00E0 t\u00EAn l\u00E0 HAI th\u1EE9 \u0111\u1ED9c l\u1EADp: c\u00F3 th\u1EC3 ch\u1EC9 c\u00F3 t\u00EAn, ch\u1EC9 c\u00F3 logo, ho\u1EB7c\n c\u1EA3 hai. B\u1EA3n tr\u01B0\u1EDBc l\u1ED3ng t\u00EAn v\u00E0o trong nh\u00E1nh @if (brandLogo()) n\u00EAn\n truy\u1EC1n t\u00EAn m\u00E0 kh\u00F4ng truy\u1EC1n logo th\u00EC t\u00EAn bi\u1EBFn m\u1EA5t im l\u1EB7ng.\n -->\n @if (brandLogo() || brandName()) {\n <a\n href=\"/\"\n class=\"flex items-center gap-2.5 shrink-0 focus:outline-none focus:ring-2 focus:ring-brand-subtle rounded-lg whitespace-nowrap\"\n >\n @if (brandLogo()) {\n <img [src]=\"brandLogo()\" [alt]=\"brandName()\" class=\"h-9 w-auto\" />\n }\n @if (brandName()) {\n <span class=\"font-bold text-heading text-lg whitespace-nowrap\">{{\n brandName()\n }}</span>\n }\n </a>\n }\n </ng-content>\n </div>\n\n <!--\n M\u1EE5c menu \u2014 M\u1ED8T ch\u1ED7 chi\u1EBFu duy nh\u1EA5t, d\u00F9ng cho c\u1EA3 hai b\u1ED1 c\u1EE5c (Angular ch\u1EC9\n chi\u1EBFu m\u1ED9t n\u1ED9i dung v\u00E0o m\u1ED9t ch\u1ED7). Khung r\u1ED9ng: n\u1EB1m ngang c\u1EA1nh logo. Khung\n h\u1EB9p: \u1EA9n, tr\u1EEB khi \u2630 \u0111ang m\u1EDF V\u00C0 n\u01A1i g\u1ECDi kh\u00F4ng khai menu di \u0111\u1ED9ng ri\u00EAng \u2014\n khi \u0111\u00F3 x\u1EBFp d\u1ECDc, N\u1ED4I d\u01B0\u1EDBi thanh (\u0111\u00E8 n\u1ED9i dung). Xem navWrapClasses().\n -->\n <div #navWrap [class]=\"navWrapClasses()\">\n <ng-content select=\"[slot=navigation], [navigation], ol-navbar-item, ol-nav-dropdown\" />\n </div>\n\n <!-- Center: Global Search Input -->\n @if (showSearch()) {\n <div class=\"hidden @[78rem]:flex flex-1 max-w-50 @[96rem]:max-w-xs mx-2 @5xl:mx-4 min-w-0\">\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-body-subtle\"\n >\n <ol-icon name=\"search\" size=\"sm\"></ol-icon>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery\"\n (input)=\"handleSearchInput($event)\"\n (keydown.enter)=\"handleSearchSubmit()\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"w-full pl-9 pr-14 py-2 text-sm bg-neutral-tertiary text-heading rounded-md border border-default focus:outline-none focus:ring-2 focus:ring-brand-subtle/20 focus:border-brand transition\"\n />\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-2.5 pointer-events-none\">\n <kbd\n class=\"px-1.5 py-0.5 text-2xs font-semibold text-heading bg-neutral-tertiary-medium rounded border border-default\"\n >Ctrl K</kbd\n >\n </div>\n </div>\n </div>\n }\n\n <!-- Right: Actions Slot & Mobile Hamburger Button \u2014 ml-auto thay justify-between. -->\n <div class=\"ml-auto flex h-16 items-center gap-2 @[40rem]:gap-3 shrink-0 whitespace-nowrap\">\n <ng-content select=\"[slot=actions], [actions]\" />\n\n <!-- Mobile Menu Toggle Button (Visible below xl) -->\n <button\n type=\"button\"\n (click)=\"toggleMobileMenu()\"\n [class]=\"toggleClasses()\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n aria-label=\"\u0110\u00F3ng/M\u1EDF menu\"\n >\n @if (isCollapsed()) {\n <ol-icon name=\"menu\" size=\"base\"></ol-icon>\n } @else {\n <ol-icon name=\"close\" size=\"base\"></ol-icon>\n }\n </button>\n </div>\n </div>\n </div>\n\n <!-- Mobile Drawer / Dropdown Panel matching Figma 3279:21067 / #1149:75488 -->\n <!--\n Lu\u00F4n D\u1EF0NG, \u1EA9n b\u1EB1ng `hidden` khi \u0111\u00F3ng \u2014 kh\u00F4ng b\u1ECDc trong @if. N\u1ED9i dung chi\u1EBFu\n v\u00E0o nh\u00E1nh @if \u0111ang t\u1EAFt kh\u00F4ng n\u1EB1m trong DOM, n\u00EAn kh\u00F4ng ph\u00E1t hi\u1EC7n \u0111\u01B0\u1EE3c n\u01A1i g\u1ECDi\n c\u00F3 khai `slot=\"mobile-links\"` hay kh\u00F4ng (xem detectMobileSlots()).\n -->\n <div #mobilePanel [class]=\"mobilePanelClasses()\">\n <!-- Mobile User Profile Slot -->\n <ng-content select=\"[slot=mobile-user]\" />\n\n <!-- Mobile Search Input -->\n @if (showSearch()) {\n <div class=\"relative w-full @3xl:hidden\">\n <div\n class=\"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-body-subtle\"\n >\n <ol-icon name=\"search\" size=\"sm\"></ol-icon>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery\"\n (input)=\"handleSearchInput($event)\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"w-full pl-9 pr-3 py-2.5 text-sm bg-neutral-tertiary text-heading rounded-md border border-default focus:outline-none focus:ring-2 focus:ring-brand-subtle\"\n />\n </div>\n }\n\n <!-- Mobile Links Slot -->\n <ng-content select=\"[slot=mobile], [slot=mobile-links]\" />\n\n <!-- Mobile Actions Slot -->\n <ng-content select=\"[slot=mobile-actions]\" />\n\n <!-- Mobile Footer Contact Info Slot -->\n <ng-content select=\"[slot=mobile-footer]\" />\n </div>\n</nav>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14116
15194
|
}
|
|
14117
15195
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlNavbar, decorators: [{
|
|
14118
15196
|
type: Component,
|
|
14119
|
-
args: [{ selector: 'ol-navbar', standalone: true, imports: [CommonModule, FormsModule, OlIcon], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block w-full min-w-0' }, template: "<nav [class]=\"navbarClasses()\" role=\"navigation\" aria-label=\"Thanh \u0111i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n <div [class]=\"containerClasses()\">\n <!--\n Khung h\u1EB9p: l\u01B0\u1EDBi hai c\u1ED9t (logo | n\u00FAt), kh\u1ED1i m\u1EE5c menu khi \u2630 m\u1EDF chi\u1EBFm tr\u1ECDn\n h\u00E0ng th\u1EE9 hai (xem STACKED). Kh\u00F4ng d\u00F9ng `flex-wrap`: n\u00F3 th\u1EA3 nh\u00F3m n\u00FAt\n xu\u1ED1ng d\u00F2ng d\u01B0\u1EDBi logo khi hai th\u1EE9 c\u1ED9ng l\u1EA1i r\u1ED9ng h\u01A1n khung. Khung r\u1ED9ng: flex.\n -->\n <div
|
|
14120
|
-
}], ctorParameters: () => [], propDecorators: { brandLogo: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLogo", required: false }] }], brandName: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandName", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], fluid: [{ type: i0.Input, args: [{ isSignal: true, alias: "fluid", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], isCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCollapsed", required: false }] }, { type: i0.Output, args: ["isCollapsedChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onToggleMobileMenu: [{ type: i0.Output, args: ["onToggleMobileMenu"] }], mobilePanel: [{ type: i0.ViewChild, args: ['mobilePanel', { isSignal: true }] }], navWrap: [{ type: i0.ViewChild, args: ['navWrap', { isSignal: true }] }], handleGlobalKeydown: [{
|
|
15197
|
+
args: [{ selector: 'ol-navbar', standalone: true, imports: [CommonModule, FormsModule, OlIcon], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block w-full min-w-0' }, template: "<nav [class]=\"navbarClasses()\" role=\"navigation\" aria-label=\"Thanh \u0111i\u1EC1u h\u01B0\u1EDBng ch\u00EDnh\">\n <div [class]=\"containerClasses()\">\n <!--\n Khung h\u1EB9p: l\u01B0\u1EDBi hai c\u1ED9t (logo | n\u00FAt), kh\u1ED1i m\u1EE5c menu khi \u2630 m\u1EDF chi\u1EBFm tr\u1ECDn\n h\u00E0ng th\u1EE9 hai (xem STACKED). Kh\u00F4ng d\u00F9ng `flex-wrap`: n\u00F3 th\u1EA3 nh\u00F3m n\u00FAt\n xu\u1ED1ng d\u00F2ng d\u01B0\u1EDBi logo khi hai th\u1EE9 c\u1ED9ng l\u1EA1i r\u1ED9ng h\u01A1n khung. Khung r\u1ED9ng: flex.\n -->\n <div [class]=\"rowClasses()\">\n <!-- Left: Brand / Logo -->\n <div class=\"flex h-16 items-center min-w-0\">\n <ng-content select=\"[slot=brand], [brand]\">\n <!--\n Logo v\u00E0 t\u00EAn l\u00E0 HAI th\u1EE9 \u0111\u1ED9c l\u1EADp: c\u00F3 th\u1EC3 ch\u1EC9 c\u00F3 t\u00EAn, ch\u1EC9 c\u00F3 logo, ho\u1EB7c\n c\u1EA3 hai. B\u1EA3n tr\u01B0\u1EDBc l\u1ED3ng t\u00EAn v\u00E0o trong nh\u00E1nh @if (brandLogo()) n\u00EAn\n truy\u1EC1n t\u00EAn m\u00E0 kh\u00F4ng truy\u1EC1n logo th\u00EC t\u00EAn bi\u1EBFn m\u1EA5t im l\u1EB7ng.\n -->\n @if (brandLogo() || brandName()) {\n <a\n href=\"/\"\n class=\"flex items-center gap-2.5 shrink-0 focus:outline-none focus:ring-2 focus:ring-brand-subtle rounded-lg whitespace-nowrap\"\n >\n @if (brandLogo()) {\n <img [src]=\"brandLogo()\" [alt]=\"brandName()\" class=\"h-9 w-auto\" />\n }\n @if (brandName()) {\n <span class=\"font-bold text-heading text-lg whitespace-nowrap\">{{\n brandName()\n }}</span>\n }\n </a>\n }\n </ng-content>\n </div>\n\n <!--\n M\u1EE5c menu \u2014 M\u1ED8T ch\u1ED7 chi\u1EBFu duy nh\u1EA5t, d\u00F9ng cho c\u1EA3 hai b\u1ED1 c\u1EE5c (Angular ch\u1EC9\n chi\u1EBFu m\u1ED9t n\u1ED9i dung v\u00E0o m\u1ED9t ch\u1ED7). Khung r\u1ED9ng: n\u1EB1m ngang c\u1EA1nh logo. Khung\n h\u1EB9p: \u1EA9n, tr\u1EEB khi \u2630 \u0111ang m\u1EDF V\u00C0 n\u01A1i g\u1ECDi kh\u00F4ng khai menu di \u0111\u1ED9ng ri\u00EAng \u2014\n khi \u0111\u00F3 x\u1EBFp d\u1ECDc, N\u1ED4I d\u01B0\u1EDBi thanh (\u0111\u00E8 n\u1ED9i dung). Xem navWrapClasses().\n -->\n <div #navWrap [class]=\"navWrapClasses()\">\n <ng-content select=\"[slot=navigation], [navigation], ol-navbar-item, ol-nav-dropdown\" />\n </div>\n\n <!-- Center: Global Search Input -->\n @if (showSearch()) {\n <div class=\"hidden @[78rem]:flex flex-1 max-w-50 @[96rem]:max-w-xs mx-2 @5xl:mx-4 min-w-0\">\n <div class=\"relative w-full\">\n <div\n class=\"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-body-subtle\"\n >\n <ol-icon name=\"search\" size=\"sm\"></ol-icon>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery\"\n (input)=\"handleSearchInput($event)\"\n (keydown.enter)=\"handleSearchSubmit()\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"w-full pl-9 pr-14 py-2 text-sm bg-neutral-tertiary text-heading rounded-md border border-default focus:outline-none focus:ring-2 focus:ring-brand-subtle/20 focus:border-brand transition\"\n />\n <div class=\"absolute inset-y-0 right-0 flex items-center pr-2.5 pointer-events-none\">\n <kbd\n class=\"px-1.5 py-0.5 text-2xs font-semibold text-heading bg-neutral-tertiary-medium rounded border border-default\"\n >Ctrl K</kbd\n >\n </div>\n </div>\n </div>\n }\n\n <!-- Right: Actions Slot & Mobile Hamburger Button \u2014 ml-auto thay justify-between. -->\n <div class=\"ml-auto flex h-16 items-center gap-2 @[40rem]:gap-3 shrink-0 whitespace-nowrap\">\n <ng-content select=\"[slot=actions], [actions]\" />\n\n <!-- Mobile Menu Toggle Button (Visible below xl) -->\n <button\n type=\"button\"\n (click)=\"toggleMobileMenu()\"\n [class]=\"toggleClasses()\"\n [attr.aria-expanded]=\"!isCollapsed()\"\n aria-label=\"\u0110\u00F3ng/M\u1EDF menu\"\n >\n @if (isCollapsed()) {\n <ol-icon name=\"menu\" size=\"base\"></ol-icon>\n } @else {\n <ol-icon name=\"close\" size=\"base\"></ol-icon>\n }\n </button>\n </div>\n </div>\n </div>\n\n <!-- Mobile Drawer / Dropdown Panel matching Figma 3279:21067 / #1149:75488 -->\n <!--\n Lu\u00F4n D\u1EF0NG, \u1EA9n b\u1EB1ng `hidden` khi \u0111\u00F3ng \u2014 kh\u00F4ng b\u1ECDc trong @if. N\u1ED9i dung chi\u1EBFu\n v\u00E0o nh\u00E1nh @if \u0111ang t\u1EAFt kh\u00F4ng n\u1EB1m trong DOM, n\u00EAn kh\u00F4ng ph\u00E1t hi\u1EC7n \u0111\u01B0\u1EE3c n\u01A1i g\u1ECDi\n c\u00F3 khai `slot=\"mobile-links\"` hay kh\u00F4ng (xem detectMobileSlots()).\n -->\n <div #mobilePanel [class]=\"mobilePanelClasses()\">\n <!-- Mobile User Profile Slot -->\n <ng-content select=\"[slot=mobile-user]\" />\n\n <!-- Mobile Search Input -->\n @if (showSearch()) {\n <div class=\"relative w-full @3xl:hidden\">\n <div\n class=\"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-body-subtle\"\n >\n <ol-icon name=\"search\" size=\"sm\"></ol-icon>\n </div>\n <input\n type=\"text\"\n [value]=\"searchQuery\"\n (input)=\"handleSearchInput($event)\"\n [placeholder]=\"searchPlaceholder()\"\n class=\"w-full pl-9 pr-3 py-2.5 text-sm bg-neutral-tertiary text-heading rounded-md border border-default focus:outline-none focus:ring-2 focus:ring-brand-subtle\"\n />\n </div>\n }\n\n <!-- Mobile Links Slot -->\n <ng-content select=\"[slot=mobile], [slot=mobile-links]\" />\n\n <!-- Mobile Actions Slot -->\n <ng-content select=\"[slot=mobile-actions]\" />\n\n <!-- Mobile Footer Contact Info Slot -->\n <ng-content select=\"[slot=mobile-footer]\" />\n </div>\n</nav>\n" }]
|
|
15198
|
+
}], ctorParameters: () => [], propDecorators: { brandLogo: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandLogo", required: false }] }], brandName: [{ type: i0.Input, args: [{ isSignal: true, alias: "brandName", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], bordered: [{ type: i0.Input, args: [{ isSignal: true, alias: "bordered", required: false }] }], fluid: [{ type: i0.Input, args: [{ isSignal: true, alias: "fluid", required: false }] }], showSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "showSearch", required: false }] }], searchPlaceholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchPlaceholder", required: false }] }], collapseAt: [{ type: i0.Input, args: [{ isSignal: true, alias: "collapseAt", required: false }] }], compactItems: [{ type: i0.Input, args: [{ isSignal: true, alias: "compactItems", required: false }] }], isCollapsed: [{ type: i0.Input, args: [{ isSignal: true, alias: "isCollapsed", required: false }] }, { type: i0.Output, args: ["isCollapsedChange"] }], onSearch: [{ type: i0.Output, args: ["onSearch"] }], onToggleMobileMenu: [{ type: i0.Output, args: ["onToggleMobileMenu"] }], mobilePanel: [{ type: i0.ViewChild, args: ['mobilePanel', { isSignal: true }] }], navWrap: [{ type: i0.ViewChild, args: ['navWrap', { isSignal: true }] }], handleGlobalKeydown: [{
|
|
14121
15199
|
type: HostListener,
|
|
14122
15200
|
args: ['document:keydown', ['$event']]
|
|
14123
15201
|
}] } });
|
|
@@ -14181,11 +15259,11 @@ class OlNavbarItem {
|
|
|
14181
15259
|
}, /* @ts-ignore */
|
|
14182
15260
|
...(ngDevMode ? [{ debugName: "itemClasses" }] : /* istanbul ignore next */ []));
|
|
14183
15261
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlNavbarItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14184
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlNavbarItem, isStandalone: true, selector: "ol-navbar-item", inputs: { routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActive: { classPropertyName: "routerLinkActive", publicName: "routerLinkActive", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActiveOptions: { classPropertyName: "routerLinkActiveOptions", publicName: "routerLinkActiveOptions", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n BA NH\u00C1NH, `routerLinkActive` CH\u1EC8 \u1EDF nh\u00E1nh c\u00F3 `routerLink`.\n\n B\u1EA3n c\u0169 d\u00F9ng M\u1ED8T `<a>` mang c\u1EA3 `[routerLink]`, `[routerLinkActive]` v\u00E0\n `[class]=\"itemClasses()\"`. Hai th\u1EE9 sau c\u00F9ng ghi v\u00E0o thu\u1ED9c t\u00EDnh `class`, v\u00E0\n `RouterLinkActive` ghi SAU n\u00EAn n\u00F3 th\u1EAFng: chu\u1ED7i `'active \u2026'` m\u00E0 `itemClasses()`\n th\u00EAm khi `[active]=\"true\"` b\u1ECB nu\u1ED1t s\u1EA1ch.\n\n T\u00E1ch nh\u00E1nh th\u00F4i KH\u00D4NG \u0111\u1EE7 \u2014 nh\u00E1nh `routerLink` v\u1EABn c\u00F3 c\u1EA3 hai directive tr\u00EAn\n c\u00F9ng th\u1EBB. N\u00EAn `routerLinkActive` ch\u1EC9 g\u1EAFn khi `active()` T\u1EAET: \u0111\u00E3 \u00E9p active\n b\u1EB1ng input th\u00EC kh\u00F4ng c\u00F2n g\u00EC \u0111\u1EC3 directive t\u00EDnh, hai b\u00EAn h\u1EBFt tranh nhau.\n\n H\u1ECFng im l\u1EB7ng \u2014 n\u1EC1n v\u00E0 ch\u1EEF v\u1EABn \u0111\u00FAng (ch\u00FAng n\u1EB1m trong c\u00F9ng chu\u1ED7i), ch\u1EC9 ri\u00EAng\n ch\u1EEF `active` m\u1EA5t. H\u1EADu qu\u1EA3 ch\u1EC9 l\u1ED9 ra khi r\u00EA chu\u1ED9t: \u0111o /navbar 2026-09-24, m\u1EE5c\n \u0111ang m\u1EDF NH\u1EA0T \u0110I (#e3eaff -> #f5f8ff) v\u00EC `not-[.active]:hover:bg-brand-softer`\n v\u1EABn \u00E1p d\u1EE5ng, c\u00F2n `[&.active]:hover:bg-brand-medium` th\u00EC kh\u00F4ng. Sidebar c\u00F9ng\n b\u1ED9 class nh\u01B0ng \u0110\u1EACM L\u00CAN \u0111\u00FAng (#d2dcfa) v\u00EC n\u00F3 \u0111\u00E3 t\u00E1ch nh\u00E1nh t\u1EEB tr\u01B0\u1EDBc.\n\n Unit test KH\u00D4NG b\u1EAFt \u0111\u01B0\u1EE3c: `provideRouter([])` kh\u00F4ng c\u00F3 route n\u00E0o kh\u1EDBp n\u00EAn\n directive kh\u00F4ng ch\u1EA1m v\u00E0o `class`, test xanh trong khi tr\u00ECnh duy\u1EC7t h\u1ECFng.\n\n N\u1ED9i dung g\u00F3i trong `ng-template` v\u00E0 chi\u1EBFu l\u1EA1i b\u1EB1ng `ngTemplateOutlet`: khai\n nhi\u1EC1u `<ng-content>` kh\u00F4ng `select` \u2014 k\u1EC3 c\u1EA3 \u1EDF c\u00E1c nh\u00E1nh lo\u1EA1i tr\u1EEB nhau \u2014\n th\u00EC Angular ch\u1EC9 chi\u1EBFu v\u00E0o M\u1ED8T ch\u1ED7 v\u00E0 th\u1EF1c t\u1EBF m\u1EA5t \u1EDF m\u1ECDi nh\u00E1nh.\n-->\n<ng-template #itemContent>\n @if (icon()) {\n <ol-icon [name]=\"icon()!\" size=\"sm\" class=\"shrink-0\"></ol-icon>\n }\n @if (label()) {\n <span class=\"whitespace-nowrap\">{{ label() }}</span>\n }\n <ng-content
|
|
15262
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlNavbarItem, isStandalone: true, selector: "ol-navbar-item", inputs: { routerLink: { classPropertyName: "routerLink", publicName: "routerLink", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActive: { classPropertyName: "routerLinkActive", publicName: "routerLinkActive", isSignal: true, isRequired: false, transformFunction: null }, routerLinkActiveOptions: { classPropertyName: "routerLinkActiveOptions", publicName: "routerLinkActiveOptions", isSignal: true, isRequired: false, transformFunction: null }, href: { classPropertyName: "href", publicName: "href", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<!--\n BA NH\u00C1NH, `routerLinkActive` CH\u1EC8 \u1EDF nh\u00E1nh c\u00F3 `routerLink`.\n\n B\u1EA3n c\u0169 d\u00F9ng M\u1ED8T `<a>` mang c\u1EA3 `[routerLink]`, `[routerLinkActive]` v\u00E0\n `[class]=\"itemClasses()\"`. Hai th\u1EE9 sau c\u00F9ng ghi v\u00E0o thu\u1ED9c t\u00EDnh `class`, v\u00E0\n `RouterLinkActive` ghi SAU n\u00EAn n\u00F3 th\u1EAFng: chu\u1ED7i `'active \u2026'` m\u00E0 `itemClasses()`\n th\u00EAm khi `[active]=\"true\"` b\u1ECB nu\u1ED1t s\u1EA1ch.\n\n T\u00E1ch nh\u00E1nh th\u00F4i KH\u00D4NG \u0111\u1EE7 \u2014 nh\u00E1nh `routerLink` v\u1EABn c\u00F3 c\u1EA3 hai directive tr\u00EAn\n c\u00F9ng th\u1EBB. N\u00EAn `routerLinkActive` ch\u1EC9 g\u1EAFn khi `active()` T\u1EAET: \u0111\u00E3 \u00E9p active\n b\u1EB1ng input th\u00EC kh\u00F4ng c\u00F2n g\u00EC \u0111\u1EC3 directive t\u00EDnh, hai b\u00EAn h\u1EBFt tranh nhau.\n\n H\u1ECFng im l\u1EB7ng \u2014 n\u1EC1n v\u00E0 ch\u1EEF v\u1EABn \u0111\u00FAng (ch\u00FAng n\u1EB1m trong c\u00F9ng chu\u1ED7i), ch\u1EC9 ri\u00EAng\n ch\u1EEF `active` m\u1EA5t. H\u1EADu qu\u1EA3 ch\u1EC9 l\u1ED9 ra khi r\u00EA chu\u1ED9t: \u0111o /navbar 2026-09-24, m\u1EE5c\n \u0111ang m\u1EDF NH\u1EA0T \u0110I (#e3eaff -> #f5f8ff) v\u00EC `not-[.active]:hover:bg-brand-softer`\n v\u1EABn \u00E1p d\u1EE5ng, c\u00F2n `[&.active]:hover:bg-brand-medium` th\u00EC kh\u00F4ng. Sidebar c\u00F9ng\n b\u1ED9 class nh\u01B0ng \u0110\u1EACM L\u00CAN \u0111\u00FAng (#d2dcfa) v\u00EC n\u00F3 \u0111\u00E3 t\u00E1ch nh\u00E1nh t\u1EEB tr\u01B0\u1EDBc.\n\n Unit test KH\u00D4NG b\u1EAFt \u0111\u01B0\u1EE3c: `provideRouter([])` kh\u00F4ng c\u00F3 route n\u00E0o kh\u1EDBp n\u00EAn\n directive kh\u00F4ng ch\u1EA1m v\u00E0o `class`, test xanh trong khi tr\u00ECnh duy\u1EC7t h\u1ECFng.\n\n N\u1ED9i dung g\u00F3i trong `ng-template` v\u00E0 chi\u1EBFu l\u1EA1i b\u1EB1ng `ngTemplateOutlet`: khai\n nhi\u1EC1u `<ng-content>` kh\u00F4ng `select` \u2014 k\u1EC3 c\u1EA3 \u1EDF c\u00E1c nh\u00E1nh lo\u1EA1i tr\u1EEB nhau \u2014\n th\u00EC Angular ch\u1EC9 chi\u1EBFu v\u00E0o M\u1ED8T ch\u1ED7 v\u00E0 th\u1EF1c t\u1EBF m\u1EA5t \u1EDF m\u1ECDi nh\u00E1nh.\n-->\n<ng-template #itemContent>\n @if (icon()) {\n <ol-icon [name]=\"icon()!\" size=\"sm\" class=\"shrink-0 ol-nav-icon\"></ol-icon>\n }\n @if (label()) {\n <!--\n `ol-nav-label` (ch\u1EC9 khi c\u00F3 icon): `ol-navbar [compactItems]` \u1EA9n n\u00F3 \u1EDF khung\n v\u1EEBa (sr-only \u2014 b\u1ED9 \u0111\u1ECDc m\u00E0n h\u00ECnh v\u1EABn \u0111\u1ECDc t\u00EAn). Kh\u00F4ng icon th\u00EC kh\u00F4ng g\u1EAFn:\n \u1EA9n ch\u1EEF c\u1EE7a m\u1EE5c ch\u1EC9 c\u00F3 ch\u1EEF l\u00E0 ra m\u1ED9t n\u00FAt r\u1ED7ng.\n -->\n <span class=\"whitespace-nowrap\" [class.ol-nav-label]=\"!!icon()\">{{ label() }}</span>\n }\n <!--\n Ch\u1EEF vi\u1EBFt b\u1EB1ng n\u1ED9i dung chi\u1EBFu v\u00E0o (<ol-navbar-item>Trang ch\u1EE7</ol-navbar-item>)\n \u2014 c\u00E1ch vi\u1EBFt ph\u1ED5 bi\u1EBFn nh\u1EA5t \u2014 c\u0169ng ph\u1EA3i thu g\u1ECDn \u0111\u01B0\u1EE3c, n\u00EAn b\u1ECDc c\u00F9ng class.\n B\u1EA3n \u0111\u1EA7u ch\u1EC9 g\u1EAFn cho input label: compactItems im l\u1EB7ng kh\u00F4ng c\u00F3 t\u00E1c d\u1EE5ng.\n -->\n <span class=\"whitespace-nowrap empty:hidden\" [class.ol-nav-label]=\"!!icon()\"><ng-content /></span>\n</ng-template>\n\n@if (routerLink()) {\n <a\n [routerLink]=\"routerLink()\"\n [routerLinkActive]=\"active() ? '' : routerLinkActive()\"\n [routerLinkActiveOptions]=\"routerLinkActiveOptions()\"\n [class]=\"itemClasses()\"\n [attr.title]=\"icon() ? label() : null\"\n [attr.aria-disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"itemContent\" />\n </a>\n} @else if (href()) {\n <a\n [href]=\"href()\"\n [class]=\"itemClasses()\"\n [attr.title]=\"icon() ? label() : null\"\n [attr.aria-disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"itemContent\" />\n </a>\n} @else {\n <!--\n Kh\u00F4ng li\u00EAn k\u1EBFt th\u00EC l\u00E0 N\u00DAT, kh\u00F4ng ph\u1EA3i `<a>` tr\u1EA7n. `<a>` kh\u00F4ng `href` kh\u00F4ng\n nh\u1EADn Tab v\u00E0 Enter kh\u00F4ng k\u00EDch ho\u1EA1t \u0111\u01B0\u1EE3c: m\u1EE5c d\u00F9ng \u0111\u1EC3 \u0111\u1ED5i tab t\u1EA1i ch\u1ED7 (kh\u00F4ng\n \u0111i\u1EC1u h\u01B0\u1EDBng) th\u00EC b\u00E0n ph\u00EDm kh\u00F4ng b\u1EA5m \u0111\u01B0\u1EE3c (\u0111o /navbar 2026-09-28).\n -->\n <button\n type=\"button\"\n [class]=\"itemClasses()\"\n [attr.title]=\"icon() ? label() : null\"\n [disabled]=\"disabled()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n >\n <ng-container [ngTemplateOutlet]=\"itemContent\" />\n </button>\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "browserUrl", "routerLink"] }, { kind: "directive", type: i2.RouterLinkActive, selector: "[routerLinkActive]", inputs: ["routerLinkActiveOptions", "ariaCurrentWhenActive", "routerLinkActive"], outputs: ["isActiveChange"], exportAs: ["routerLinkActive"] }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14185
15263
|
}
|
|
14186
15264
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlNavbarItem, decorators: [{
|
|
14187
15265
|
type: Component,
|
|
14188
|
-
args: [{ selector: 'ol-navbar-item', standalone: true, imports: [CommonModule, RouterModule, OlIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n BA NH\u00C1NH, `routerLinkActive` CH\u1EC8 \u1EDF nh\u00E1nh c\u00F3 `routerLink`.\n\n B\u1EA3n c\u0169 d\u00F9ng M\u1ED8T `<a>` mang c\u1EA3 `[routerLink]`, `[routerLinkActive]` v\u00E0\n `[class]=\"itemClasses()\"`. Hai th\u1EE9 sau c\u00F9ng ghi v\u00E0o thu\u1ED9c t\u00EDnh `class`, v\u00E0\n `RouterLinkActive` ghi SAU n\u00EAn n\u00F3 th\u1EAFng: chu\u1ED7i `'active \u2026'` m\u00E0 `itemClasses()`\n th\u00EAm khi `[active]=\"true\"` b\u1ECB nu\u1ED1t s\u1EA1ch.\n\n T\u00E1ch nh\u00E1nh th\u00F4i KH\u00D4NG \u0111\u1EE7 \u2014 nh\u00E1nh `routerLink` v\u1EABn c\u00F3 c\u1EA3 hai directive tr\u00EAn\n c\u00F9ng th\u1EBB. N\u00EAn `routerLinkActive` ch\u1EC9 g\u1EAFn khi `active()` T\u1EAET: \u0111\u00E3 \u00E9p active\n b\u1EB1ng input th\u00EC kh\u00F4ng c\u00F2n g\u00EC \u0111\u1EC3 directive t\u00EDnh, hai b\u00EAn h\u1EBFt tranh nhau.\n\n H\u1ECFng im l\u1EB7ng \u2014 n\u1EC1n v\u00E0 ch\u1EEF v\u1EABn \u0111\u00FAng (ch\u00FAng n\u1EB1m trong c\u00F9ng chu\u1ED7i), ch\u1EC9 ri\u00EAng\n ch\u1EEF `active` m\u1EA5t. H\u1EADu qu\u1EA3 ch\u1EC9 l\u1ED9 ra khi r\u00EA chu\u1ED9t: \u0111o /navbar 2026-09-24, m\u1EE5c\n \u0111ang m\u1EDF NH\u1EA0T \u0110I (#e3eaff -> #f5f8ff) v\u00EC `not-[.active]:hover:bg-brand-softer`\n v\u1EABn \u00E1p d\u1EE5ng, c\u00F2n `[&.active]:hover:bg-brand-medium` th\u00EC kh\u00F4ng. Sidebar c\u00F9ng\n b\u1ED9 class nh\u01B0ng \u0110\u1EACM L\u00CAN \u0111\u00FAng (#d2dcfa) v\u00EC n\u00F3 \u0111\u00E3 t\u00E1ch nh\u00E1nh t\u1EEB tr\u01B0\u1EDBc.\n\n Unit test KH\u00D4NG b\u1EAFt \u0111\u01B0\u1EE3c: `provideRouter([])` kh\u00F4ng c\u00F3 route n\u00E0o kh\u1EDBp n\u00EAn\n directive kh\u00F4ng ch\u1EA1m v\u00E0o `class`, test xanh trong khi tr\u00ECnh duy\u1EC7t h\u1ECFng.\n\n N\u1ED9i dung g\u00F3i trong `ng-template` v\u00E0 chi\u1EBFu l\u1EA1i b\u1EB1ng `ngTemplateOutlet`: khai\n nhi\u1EC1u `<ng-content>` kh\u00F4ng `select` \u2014 k\u1EC3 c\u1EA3 \u1EDF c\u00E1c nh\u00E1nh lo\u1EA1i tr\u1EEB nhau \u2014\n th\u00EC Angular ch\u1EC9 chi\u1EBFu v\u00E0o M\u1ED8T ch\u1ED7 v\u00E0 th\u1EF1c t\u1EBF m\u1EA5t \u1EDF m\u1ECDi nh\u00E1nh.\n-->\n<ng-template #itemContent>\n @if (icon()) {\n <ol-icon [name]=\"icon()!\" size=\"sm\" class=\"shrink-0\"></ol-icon>\n }\n @if (label()) {\n <span class=\"whitespace-nowrap\">{{ label() }}</span>\n }\n <ng-content
|
|
15266
|
+
args: [{ selector: 'ol-navbar-item', standalone: true, imports: [CommonModule, RouterModule, OlIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\n BA NH\u00C1NH, `routerLinkActive` CH\u1EC8 \u1EDF nh\u00E1nh c\u00F3 `routerLink`.\n\n B\u1EA3n c\u0169 d\u00F9ng M\u1ED8T `<a>` mang c\u1EA3 `[routerLink]`, `[routerLinkActive]` v\u00E0\n `[class]=\"itemClasses()\"`. Hai th\u1EE9 sau c\u00F9ng ghi v\u00E0o thu\u1ED9c t\u00EDnh `class`, v\u00E0\n `RouterLinkActive` ghi SAU n\u00EAn n\u00F3 th\u1EAFng: chu\u1ED7i `'active \u2026'` m\u00E0 `itemClasses()`\n th\u00EAm khi `[active]=\"true\"` b\u1ECB nu\u1ED1t s\u1EA1ch.\n\n T\u00E1ch nh\u00E1nh th\u00F4i KH\u00D4NG \u0111\u1EE7 \u2014 nh\u00E1nh `routerLink` v\u1EABn c\u00F3 c\u1EA3 hai directive tr\u00EAn\n c\u00F9ng th\u1EBB. N\u00EAn `routerLinkActive` ch\u1EC9 g\u1EAFn khi `active()` T\u1EAET: \u0111\u00E3 \u00E9p active\n b\u1EB1ng input th\u00EC kh\u00F4ng c\u00F2n g\u00EC \u0111\u1EC3 directive t\u00EDnh, hai b\u00EAn h\u1EBFt tranh nhau.\n\n H\u1ECFng im l\u1EB7ng \u2014 n\u1EC1n v\u00E0 ch\u1EEF v\u1EABn \u0111\u00FAng (ch\u00FAng n\u1EB1m trong c\u00F9ng chu\u1ED7i), ch\u1EC9 ri\u00EAng\n ch\u1EEF `active` m\u1EA5t. H\u1EADu qu\u1EA3 ch\u1EC9 l\u1ED9 ra khi r\u00EA chu\u1ED9t: \u0111o /navbar 2026-09-24, m\u1EE5c\n \u0111ang m\u1EDF NH\u1EA0T \u0110I (#e3eaff -> #f5f8ff) v\u00EC `not-[.active]:hover:bg-brand-softer`\n v\u1EABn \u00E1p d\u1EE5ng, c\u00F2n `[&.active]:hover:bg-brand-medium` th\u00EC kh\u00F4ng. Sidebar c\u00F9ng\n b\u1ED9 class nh\u01B0ng \u0110\u1EACM L\u00CAN \u0111\u00FAng (#d2dcfa) v\u00EC n\u00F3 \u0111\u00E3 t\u00E1ch nh\u00E1nh t\u1EEB tr\u01B0\u1EDBc.\n\n Unit test KH\u00D4NG b\u1EAFt \u0111\u01B0\u1EE3c: `provideRouter([])` kh\u00F4ng c\u00F3 route n\u00E0o kh\u1EDBp n\u00EAn\n directive kh\u00F4ng ch\u1EA1m v\u00E0o `class`, test xanh trong khi tr\u00ECnh duy\u1EC7t h\u1ECFng.\n\n N\u1ED9i dung g\u00F3i trong `ng-template` v\u00E0 chi\u1EBFu l\u1EA1i b\u1EB1ng `ngTemplateOutlet`: khai\n nhi\u1EC1u `<ng-content>` kh\u00F4ng `select` \u2014 k\u1EC3 c\u1EA3 \u1EDF c\u00E1c nh\u00E1nh lo\u1EA1i tr\u1EEB nhau \u2014\n th\u00EC Angular ch\u1EC9 chi\u1EBFu v\u00E0o M\u1ED8T ch\u1ED7 v\u00E0 th\u1EF1c t\u1EBF m\u1EA5t \u1EDF m\u1ECDi nh\u00E1nh.\n-->\n<ng-template #itemContent>\n @if (icon()) {\n <ol-icon [name]=\"icon()!\" size=\"sm\" class=\"shrink-0 ol-nav-icon\"></ol-icon>\n }\n @if (label()) {\n <!--\n `ol-nav-label` (ch\u1EC9 khi c\u00F3 icon): `ol-navbar [compactItems]` \u1EA9n n\u00F3 \u1EDF khung\n v\u1EEBa (sr-only \u2014 b\u1ED9 \u0111\u1ECDc m\u00E0n h\u00ECnh v\u1EABn \u0111\u1ECDc t\u00EAn). Kh\u00F4ng icon th\u00EC kh\u00F4ng g\u1EAFn:\n \u1EA9n ch\u1EEF c\u1EE7a m\u1EE5c ch\u1EC9 c\u00F3 ch\u1EEF l\u00E0 ra m\u1ED9t n\u00FAt r\u1ED7ng.\n -->\n <span class=\"whitespace-nowrap\" [class.ol-nav-label]=\"!!icon()\">{{ label() }}</span>\n }\n <!--\n Ch\u1EEF vi\u1EBFt b\u1EB1ng n\u1ED9i dung chi\u1EBFu v\u00E0o (<ol-navbar-item>Trang ch\u1EE7</ol-navbar-item>)\n \u2014 c\u00E1ch vi\u1EBFt ph\u1ED5 bi\u1EBFn nh\u1EA5t \u2014 c\u0169ng ph\u1EA3i thu g\u1ECDn \u0111\u01B0\u1EE3c, n\u00EAn b\u1ECDc c\u00F9ng class.\n B\u1EA3n \u0111\u1EA7u ch\u1EC9 g\u1EAFn cho input label: compactItems im l\u1EB7ng kh\u00F4ng c\u00F3 t\u00E1c d\u1EE5ng.\n -->\n <span class=\"whitespace-nowrap empty:hidden\" [class.ol-nav-label]=\"!!icon()\"><ng-content /></span>\n</ng-template>\n\n@if (routerLink()) {\n <a\n [routerLink]=\"routerLink()\"\n [routerLinkActive]=\"active() ? '' : routerLinkActive()\"\n [routerLinkActiveOptions]=\"routerLinkActiveOptions()\"\n [class]=\"itemClasses()\"\n [attr.title]=\"icon() ? label() : null\"\n [attr.aria-disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"itemContent\" />\n </a>\n} @else if (href()) {\n <a\n [href]=\"href()\"\n [class]=\"itemClasses()\"\n [attr.title]=\"icon() ? label() : null\"\n [attr.aria-disabled]=\"disabled()\"\n >\n <ng-container [ngTemplateOutlet]=\"itemContent\" />\n </a>\n} @else {\n <!--\n Kh\u00F4ng li\u00EAn k\u1EBFt th\u00EC l\u00E0 N\u00DAT, kh\u00F4ng ph\u1EA3i `<a>` tr\u1EA7n. `<a>` kh\u00F4ng `href` kh\u00F4ng\n nh\u1EADn Tab v\u00E0 Enter kh\u00F4ng k\u00EDch ho\u1EA1t \u0111\u01B0\u1EE3c: m\u1EE5c d\u00F9ng \u0111\u1EC3 \u0111\u1ED5i tab t\u1EA1i ch\u1ED7 (kh\u00F4ng\n \u0111i\u1EC1u h\u01B0\u1EDBng) th\u00EC b\u00E0n ph\u00EDm kh\u00F4ng b\u1EA5m \u0111\u01B0\u1EE3c (\u0111o /navbar 2026-09-28).\n -->\n <button\n type=\"button\"\n [class]=\"itemClasses()\"\n [attr.title]=\"icon() ? label() : null\"\n [disabled]=\"disabled()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n >\n <ng-container [ngTemplateOutlet]=\"itemContent\" />\n </button>\n}\n" }]
|
|
14189
15267
|
}], propDecorators: { routerLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLink", required: false }] }], routerLinkActive: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLinkActive", required: false }] }], routerLinkActiveOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "routerLinkActiveOptions", required: false }] }], href: [{ type: i0.Input, args: [{ isSignal: true, alias: "href", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
14190
15268
|
|
|
14191
15269
|
class OlNavDropdown {
|
|
@@ -14294,11 +15372,11 @@ class OlNavDropdown {
|
|
|
14294
15372
|
}
|
|
14295
15373
|
}
|
|
14296
15374
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlNavDropdown, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14297
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlNavDropdown, isStandalone: true, selector: "ol-nav-dropdown", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, alignRight: { classPropertyName: "alignRight", publicName: "alignRight", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "handleClickOutside($event)" } }, ngImport: i0, template: "<div class=\"relative inline-block text-left\">\n <!-- Trigger -->\n <div (click)=\"toggle()\">\n <ng-content select=\"[slot=trigger], [trigger]\" />\n @if (label() || icon()) {\n <button\n type=\"button\"\n [class]=\"triggerClasses()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n aria-haspopup=\"true\"\n >\n @if (icon()) {\n <ol-icon
|
|
15375
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.1.6", type: OlNavDropdown, isStandalone: true, selector: "ol-nav-dropdown", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, alignRight: { classPropertyName: "alignRight", publicName: "alignRight", isSignal: true, isRequired: false, transformFunction: null }, active: { classPropertyName: "active", publicName: "active", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, fullWidth: { classPropertyName: "fullWidth", publicName: "fullWidth", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "handleClickOutside($event)" } }, ngImport: i0, template: "<div class=\"relative inline-block text-left\">\n <!-- Trigger -->\n <div (click)=\"toggle()\">\n <ng-content select=\"[slot=trigger], [trigger]\" />\n @if (label() || icon()) {\n <button\n type=\"button\"\n [class]=\"triggerClasses()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.title]=\"icon() ? label() : null\"\n aria-haspopup=\"true\"\n >\n @if (icon()) {\n <ol-icon\n [name]=\"icon()!\"\n size=\"sm\"\n class=\"shrink-0\"\n [class.ol-nav-icon]=\"!!label()\"\n ></ol-icon>\n }\n <span class=\"whitespace-nowrap\" [class.ol-nav-label]=\"!!icon()\">{{ label() }}</span>\n <ol-icon\n name=\"chevron-down\"\n size=\"xs\"\n class=\"transition-transform duration-200 shrink-0\"\n [class.rotate-180]=\"isOpen()\"\n ></ol-icon>\n </button>\n }\n </div>\n\n <!--\n Panel. Class gom trong `panelClasses()` v\u00EC `columns` \u0111\u1ED5i c\u1EA3 c\u00E1ch x\u1EBFp, b\u1EC1\n ngang l\u1EABn c\u00E1ch neo \u2014 ba th\u1EE9 \u0111\u00F3 ph\u1EA3i quy\u1EBFt c\u00F9ng nhau, t\u00E1ch th\u00E0nh nhi\u1EC1u\n `[class.x]` r\u1EDDi th\u00EC m\u1ED7i t\u1ED5 h\u1EE3p l\u00E0 m\u1ED9t lu\u1EADt CSS ch\u1ECDi nhau.\n -->\n <div [class]=\"panelClasses()\" [class.hidden]=\"!isOpen()\" role=\"menu\">\n <ng-content />\n </div>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: OlIcon, selector: "ol-icon", inputs: ["name", "size", "color", "variant", "strokeWidth"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14298
15376
|
}
|
|
14299
15377
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImport: i0, type: OlNavDropdown, decorators: [{
|
|
14300
15378
|
type: Component,
|
|
14301
|
-
args: [{ selector: 'ol-nav-dropdown', standalone: true, imports: [CommonModule, OlIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"relative inline-block text-left\">\n <!-- Trigger -->\n <div (click)=\"toggle()\">\n <ng-content select=\"[slot=trigger], [trigger]\" />\n @if (label() || icon()) {\n <button\n type=\"button\"\n [class]=\"triggerClasses()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n aria-haspopup=\"true\"\n >\n @if (icon()) {\n <ol-icon
|
|
15379
|
+
args: [{ selector: 'ol-nav-dropdown', standalone: true, imports: [CommonModule, OlIcon], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"relative inline-block text-left\">\n <!-- Trigger -->\n <div (click)=\"toggle()\">\n <ng-content select=\"[slot=trigger], [trigger]\" />\n @if (label() || icon()) {\n <button\n type=\"button\"\n [class]=\"triggerClasses()\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-current]=\"active() ? 'page' : null\"\n [attr.title]=\"icon() ? label() : null\"\n aria-haspopup=\"true\"\n >\n @if (icon()) {\n <ol-icon\n [name]=\"icon()!\"\n size=\"sm\"\n class=\"shrink-0\"\n [class.ol-nav-icon]=\"!!label()\"\n ></ol-icon>\n }\n <span class=\"whitespace-nowrap\" [class.ol-nav-label]=\"!!icon()\">{{ label() }}</span>\n <ol-icon\n name=\"chevron-down\"\n size=\"xs\"\n class=\"transition-transform duration-200 shrink-0\"\n [class.rotate-180]=\"isOpen()\"\n ></ol-icon>\n </button>\n }\n </div>\n\n <!--\n Panel. Class gom trong `panelClasses()` v\u00EC `columns` \u0111\u1ED5i c\u1EA3 c\u00E1ch x\u1EBFp, b\u1EC1\n ngang l\u1EABn c\u00E1ch neo \u2014 ba th\u1EE9 \u0111\u00F3 ph\u1EA3i quy\u1EBFt c\u00F9ng nhau, t\u00E1ch th\u00E0nh nhi\u1EC1u\n `[class.x]` r\u1EDDi th\u00EC m\u1ED7i t\u1ED5 h\u1EE3p l\u00E0 m\u1ED9t lu\u1EADt CSS ch\u1ECDi nhau.\n -->\n <div [class]=\"panelClasses()\" [class.hidden]=\"!isOpen()\" role=\"menu\">\n <ng-content />\n </div>\n</div>\n" }]
|
|
14302
15380
|
}], ctorParameters: () => [], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], alignRight: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignRight", required: false }] }], active: [{ type: i0.Input, args: [{ isSignal: true, alias: "active", required: false }] }], columns: [{ type: i0.Input, args: [{ isSignal: true, alias: "columns", required: false }] }], fullWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "fullWidth", required: false }] }], handleClickOutside: [{
|
|
14303
15381
|
type: HostListener,
|
|
14304
15382
|
args: ['document:click', ['$event']]
|
|
@@ -19292,5 +20370,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.1.6", ngImpor
|
|
|
19292
20370
|
* Generated bundle index. Do not edit.
|
|
19293
20371
|
*/
|
|
19294
20372
|
|
|
19295
|
-
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, FILE_KIND_META, 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, OlFileDropzone, OlFileInput, OlFileItem, OlFileUpload,
|
|
20373
|
+
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 };
|
|
19296
20374
|
//# sourceMappingURL=edmicro-olkit.mjs.map
|