@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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@goplusvn/core",
3
3
  "description": "GoPlusVN Platform Kit - ERP kernel: layout, RBAC, CRUD, multi-tenant, system pages",
4
- "version": "0.1.96",
4
+ "version": "0.1.97",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org",
@@ -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 = normalizeVietnamese(label[i]);
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 = true,
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 true). Bắt buộc bật true khi nằm trong
29
- * Dialog / Sheet để tránh FocusTrap cướp focus khỏi ô tìm kiếm hỗ trợ chạm trên mobile.
28
+ * Chế độ modal cho Popover (mặc định false). Mặc định false giúp ô tìm kiếm
29
+ * nhận focus bật bàn phím ảo mượt trên mobile/sheet 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 = true,
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
- onClick={(e) => {
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
@@ -22,7 +22,7 @@
22
22
  "prepare": "husky || true"
23
23
  },
24
24
  "dependencies": {
25
- "@goerp/core": "npm:@goplusvn/core@^0.1.96",
25
+ "@goerp/core": "npm:@goplusvn/core@^0.1.97",
26
26
  "@hookform/resolvers": "3.9.0",
27
27
  "@prisma/adapter-pg": "^7.0.0",
28
28
  "@prisma/client": "^7.0.0",