@goplusvn/core 0.1.99 → 0.1.100

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.100 — Cho phép cấu hình Inline Render (portal=false) cho Combobox & MultiSelect
2
+
3
+ Khi Combobox hoặc MultiSelect nằm trong Modal/Dialog/Sheet, Radix Portal ra `document.body` khiến
4
+ panel nằm ngoài DOM tree của Dialog cha, dẫn đến việc Radix Dialog gán `aria-hidden` và cướp focus
5
+ của ô tìm kiếm.
6
+ Bản này bổ sung prop `portal?: boolean` (mặc định `false`) trên `PopoverContent`, `Combobox` và `MultiSelect`,
7
+ giúp panel render inline cùng DOM tree của Dialog/Sheet, giải quyết triệt để vấn đề mất focus trên cả Desktop và Mobile.
8
+
9
+ **Đổi**
10
+
11
+ - `ui/primitives/popover.tsx` — hỗ trợ prop `portal?: boolean` (mặc định `true`).
12
+ - `ui/primitives/combobox.tsx` & `ui/forms/multi-select.tsx` — mặc định `portal = false` và `modal = false`.
13
+
1
14
  ## 0.1.99 — Native Event Isolation ngăn chặn hoàn toàn việc Dialog/Sheet cha cướp focus của ô tìm kiếm
2
15
 
3
16
  Khi người dùng click hoặc chạm vào ô tìm kiếm hoặc danh sách của Popover nằm trong Dialog/Sheet/Modal,
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.99",
4
+ "version": "0.1.100",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "registry": "https://registry.npmjs.org",
@@ -79,10 +79,15 @@ interface MultiSelectProps {
79
79
  /** Text hiển thị khi đang tải (async mode) */
80
80
  loadingText?: string;
81
81
  /**
82
- * Chế độ modal cho Popover (mặc định true). Bắt buộc bật true khi nằm trong
83
- * Dialog / Sheet để tránh FocusTrap cướp focus khỏi ô tìm kiếm và hỗ trợ chạm trên mobile.
82
+ * Chế độ modal cho Popover (mặc định false).
84
83
  */
85
84
  modal?: boolean;
85
+ /**
86
+ * Render qua Portal của Radix ra document.body (mặc định false).
87
+ * Mặc định false giúp panel nằm cùng DOM tree của Dialog/Sheet,
88
+ * tránh bị Radix Dialog gán aria-hidden và chặn focus/bàn phím.
89
+ */
90
+ portal?: boolean;
86
91
  className?: string;
87
92
  id?: string;
88
93
  }
@@ -212,7 +217,8 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
212
217
  loadingText = "Đang tải...",
213
218
  className,
214
219
  id,
215
- modal = true,
220
+ modal = false,
221
+ portal = false,
216
222
  },
217
223
  ref,
218
224
  ) => {
@@ -744,6 +750,7 @@ export const MultiSelect = React.forwardRef<MultiSelectRef, MultiSelectProps>(
744
750
  align="start"
745
751
  sideOffset={4}
746
752
  collisionPadding={8}
753
+ portal={portal}
747
754
  onOpenAutoFocus={(e) => {
748
755
  e.preventDefault();
749
756
  searchInputRef.current?.focus();
@@ -54,14 +54,12 @@ function renderInScrollBox(onValueChange = vi.fn()) {
54
54
  }
55
55
 
56
56
  describe("Combobox", () => {
57
- it("mở panel ra ngoài hộp cuộn (portal), không bị hộp cha cắt", async () => {
57
+ it("mở panel hiển thị ô tìm kiếm", async () => {
58
58
  renderInScrollBox();
59
59
  fireEvent.click(screen.getByRole("combobox"));
60
60
 
61
61
  const search = await screen.findByPlaceholderText("Search trạng thái...");
62
- const scrollBox = screen.getByTestId("scroll-box");
63
-
64
- expect(scrollBox.contains(search)).toBe(false);
62
+ expect(search).toBeTruthy();
65
63
  expect(document.body.contains(search)).toBe(true);
66
64
  });
67
65
 
@@ -25,10 +25,15 @@ 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 true để kích hoạt
29
- * FocusScope Stack của Radix khi nằm trong Dialog/Sheet/Modal.
28
+ * Chế độ modal cho Popover (mặc định false).
30
29
  */
31
30
  modal?: boolean;
31
+ /**
32
+ * Render qua Portal của Radix ra document.body (mặc định false).
33
+ * Mặc định false giúp panel nằm cùng DOM tree của Dialog/Sheet,
34
+ * tránh bị Radix Dialog gán aria-hidden và chặn focus/bàn phím.
35
+ */
36
+ portal?: boolean;
32
37
  /**
33
38
  * Chiều cao tối đa của panel (mặc định 20rem). Radix vẫn cắt tiếp theo chỗ
34
39
  * trống thực tế của màn hình, nên đây chỉ là trần trên.
@@ -56,7 +61,8 @@ export function Combobox({
56
61
  disabled = false,
57
62
  className,
58
63
  id,
59
- modal = true,
64
+ modal = false,
65
+ portal = false,
60
66
  maxHeight = "20rem",
61
67
  }: ComboboxProps) {
62
68
  const [open, setOpen] = useState(false);
@@ -190,6 +196,7 @@ export function Combobox({
190
196
  align="start"
191
197
  sideOffset={4}
192
198
  collisionPadding={8}
199
+ portal={portal}
193
200
  onOpenAutoFocus={(e) => {
194
201
  e.preventDefault();
195
202
  searchInputRef.current?.focus();
@@ -35,22 +35,32 @@ export function PopoverContent({
35
35
  align = "center",
36
36
  sideOffset = 4,
37
37
  container,
38
+ portal = true,
38
39
  ...props
39
40
  }: ComponentProps<typeof PopoverPrimitive.Content> & {
40
41
  container?: HTMLElement;
42
+ portal?: boolean;
41
43
  }) {
44
+ const content = (
45
+ <PopoverPrimitive.Content
46
+ data-slot="popover-content"
47
+ align={align}
48
+ sideOffset={sideOffset}
49
+ className={cn(
50
+ "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
51
+ className,
52
+ )}
53
+ {...props}
54
+ />
55
+ );
56
+
57
+ if (!portal) {
58
+ return content;
59
+ }
60
+
42
61
  return (
43
62
  <PopoverPrimitive.Portal container={container}>
44
- <PopoverPrimitive.Content
45
- data-slot="popover-content"
46
- align={align}
47
- sideOffset={sideOffset}
48
- className={cn(
49
- "z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
50
- className,
51
- )}
52
- {...props}
53
- />
63
+ {content}
54
64
  </PopoverPrimitive.Portal>
55
65
  );
56
66
  }
@@ -22,7 +22,7 @@
22
22
  "prepare": "husky || true"
23
23
  },
24
24
  "dependencies": {
25
- "@goerp/core": "npm:@goplusvn/core@^0.1.99",
25
+ "@goerp/core": "npm:@goplusvn/core@^0.1.100",
26
26
  "@hookform/resolvers": "3.9.0",
27
27
  "@prisma/adapter-pg": "^7.0.0",
28
28
  "@prisma/client": "^7.0.0",