@goplusvn/core 0.1.96 → 0.1.97
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/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## 0.1.97 — Sửa triệt để tương tác chạm và nhập liệu cho ô tìm kiếm Combobox/MultiSelect
|
|
2
|
+
|
|
3
|
+
Khi `modal={true}` được bật mặc định, Radix Popover kích hoạt FocusScope + ScrollLock
|
|
4
|
+
khiến thiết bị cảm ứng (Mobile/Tablet) và các trình duyệt bị xung đột:
|
|
5
|
+
chạm vào ô tìm kiếm không mở được bàn phím ảo hoặc bị đóng dropdown / mất focus.
|
|
6
|
+
|
|
7
|
+
**Đổi**
|
|
8
|
+
|
|
9
|
+
- `ui/primitives/combobox.tsx` & `ui/forms/multi-select.tsx` — chuyển `modal` về mặc định `false`,
|
|
10
|
+
loại bỏ `e.stopPropagation()` trên `onFocus`/`onClick` của `Input`, thêm `onPointerDown={(e) => e.stopPropagation()}`
|
|
11
|
+
để input nhận focus tự nhiên và kích hoạt bàn phím ảo mượt mà trên mọi thiết bị.
|
|
12
|
+
- `ui/forms/multi-select.tsx` — sửa hàm `findMatchRange` xử lý chính xác ký tự khoảng trắng khi highlight.
|
|
13
|
+
|
|
1
14
|
## 0.1.96 — Sửa lỗi Focus Trap khiến ô tìm kiếm Combobox/MultiSelect mất focus khi lồng trong Sheet/Dialog
|
|
2
15
|
|
|
3
16
|
Khi `Combobox` hoặc `MultiSelect` nằm trong `<Sheet>` (bộ lọc mobile) hoặc `<Dialog>`,
|
package/package.json
CHANGED
|
@@ -148,6 +148,14 @@ const normalizeVietnamese = (str: string) =>
|
|
|
148
148
|
.toLowerCase()
|
|
149
149
|
: "";
|
|
150
150
|
|
|
151
|
+
const normalizeVietnameseChar = (char: string) => {
|
|
152
|
+
return char
|
|
153
|
+
.normalize("NFD")
|
|
154
|
+
.replace(/[\u0300-\u036f\u031b\u0323]/g, "")
|
|
155
|
+
.replace(/[đĐ]/g, (m) => (m === "đ" ? "d" : "D"))
|
|
156
|
+
.toLowerCase();
|
|
157
|
+
};
|
|
158
|
+
|
|
151
159
|
/**
|
|
152
160
|
* Tìm vị trí khớp (không dấu, không phân biệt hoa thường, hỗ trợ đ/Đ) của query trong
|
|
153
161
|
* label GỐC — trả về [start, end) trên chuỗi gốc để highlight.
|
|
@@ -158,7 +166,7 @@ function findMatchRange(label: string, query: string): [number, number] | null {
|
|
|
158
166
|
let stripped = "";
|
|
159
167
|
const indexMap: number[] = [];
|
|
160
168
|
for (let i = 0; i < label.length; i++) {
|
|
161
|
-
const s =
|
|
169
|
+
const s = normalizeVietnameseChar(label[i]);
|
|
162
170
|
for (let j = 0; j < s.length; j++) {
|
|
163
171
|
stripped += s[j];
|
|
164
172
|
indexMap.push(i);
|
|
@@ -204,7 +212,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
204
212
|
loadingText = "Đang tải...",
|
|
205
213
|
className,
|
|
206
214
|
id,
|
|
207
|
-
modal =
|
|
215
|
+
modal = false,
|
|
208
216
|
},
|
|
209
217
|
ref,
|
|
210
218
|
) => {
|
|
@@ -724,11 +732,6 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
724
732
|
collisionPadding={8}
|
|
725
733
|
onOpenAutoFocus={(e) => {
|
|
726
734
|
e.preventDefault();
|
|
727
|
-
searchInputRef.current?.focus();
|
|
728
|
-
}}
|
|
729
|
-
onFocusOutside={(e) => {
|
|
730
|
-
// Ngăn Dialog / Sheet cha giật focus khỏi ô tìm kiếm của Popover portal
|
|
731
|
-
e.preventDefault();
|
|
732
735
|
}}
|
|
733
736
|
className="z-[100] flex w-[var(--radix-popover-trigger-width)] min-w-[15rem] max-w-[calc(100vw-1rem)] flex-col overflow-hidden rounded-md border bg-popover/95 p-0 text-popover-foreground shadow-lg shadow-black/5 backdrop-blur-md"
|
|
734
737
|
style={{
|
|
@@ -749,9 +752,11 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
749
752
|
placeholder={searchPlaceholder}
|
|
750
753
|
value={searchValue}
|
|
751
754
|
onChange={(e) => {
|
|
752
|
-
e.stopPropagation();
|
|
753
755
|
setSearchValue(e.target.value);
|
|
754
756
|
}}
|
|
757
|
+
onPointerDown={(e) => {
|
|
758
|
+
e.stopPropagation();
|
|
759
|
+
}}
|
|
755
760
|
onKeyDown={(e) => {
|
|
756
761
|
// Prevent Dialog from intercepting keyboard events
|
|
757
762
|
e.stopPropagation();
|
|
@@ -778,12 +783,6 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
|
|
|
778
783
|
updateValue(value.slice(0, -1));
|
|
779
784
|
}
|
|
780
785
|
}}
|
|
781
|
-
onClick={(e) => {
|
|
782
|
-
e.stopPropagation();
|
|
783
|
-
}}
|
|
784
|
-
onFocus={(e) => {
|
|
785
|
-
e.stopPropagation();
|
|
786
|
-
}}
|
|
787
786
|
className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-7 bg-transparent px-0 text-sm shadow-none"
|
|
788
787
|
/>
|
|
789
788
|
{searchValue && (
|
|
@@ -25,8 +25,8 @@ interface ComboboxProps {
|
|
|
25
25
|
className?: string;
|
|
26
26
|
id?: string;
|
|
27
27
|
/**
|
|
28
|
-
* Chế độ modal cho Popover (mặc định
|
|
29
|
-
*
|
|
28
|
+
* Chế độ modal cho Popover (mặc định false). Mặc định false giúp ô tìm kiếm
|
|
29
|
+
* nhận focus và bật bàn phím ảo mượt mà trên mobile/sheet mà không bị kẹt focus trap.
|
|
30
30
|
*/
|
|
31
31
|
modal?: boolean;
|
|
32
32
|
/**
|
|
@@ -56,7 +56,7 @@ export function Combobox({
|
|
|
56
56
|
disabled = false,
|
|
57
57
|
className,
|
|
58
58
|
id,
|
|
59
|
-
modal =
|
|
59
|
+
modal = false,
|
|
60
60
|
maxHeight = "20rem",
|
|
61
61
|
}: ComboboxProps) {
|
|
62
62
|
const [open, setOpen] = useState(false);
|
|
@@ -178,11 +178,6 @@ export function Combobox({
|
|
|
178
178
|
collisionPadding={8}
|
|
179
179
|
onOpenAutoFocus={(e) => {
|
|
180
180
|
e.preventDefault();
|
|
181
|
-
searchInputRef.current?.focus();
|
|
182
|
-
}}
|
|
183
|
-
onFocusOutside={(e) => {
|
|
184
|
-
// Ngăn Dialog / Sheet cha giật focus khỏi ô tìm kiếm của Popover portal
|
|
185
|
-
e.preventDefault();
|
|
186
181
|
}}
|
|
187
182
|
className="z-[100] w-[var(--radix-popover-trigger-width)] min-w-[12rem] max-w-[calc(100vw-1rem)] p-0 flex flex-col overflow-hidden"
|
|
188
183
|
style={{
|
|
@@ -197,9 +192,11 @@ export function Combobox({
|
|
|
197
192
|
placeholder={searchPlaceholder}
|
|
198
193
|
value={searchValue}
|
|
199
194
|
onChange={(e) => {
|
|
200
|
-
e.stopPropagation();
|
|
201
195
|
setSearchValue(e.target.value);
|
|
202
196
|
}}
|
|
197
|
+
onPointerDown={(e) => {
|
|
198
|
+
e.stopPropagation();
|
|
199
|
+
}}
|
|
203
200
|
onKeyDown={(e) => {
|
|
204
201
|
// Prevent Dialog from intercepting keyboard events
|
|
205
202
|
e.stopPropagation();
|
|
@@ -216,10 +213,7 @@ export function Combobox({
|
|
|
216
213
|
}
|
|
217
214
|
}
|
|
218
215
|
}}
|
|
219
|
-
|
|
220
|
-
e.stopPropagation();
|
|
221
|
-
}}
|
|
222
|
-
className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-9 bg-transparent px-0"
|
|
216
|
+
className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-9 bg-transparent px-0 text-sm"
|
|
223
217
|
/>
|
|
224
218
|
{searchValue && (
|
|
225
219
|
<Button
|