@comitor/ui 3.0.1 → 3.1.0

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.
Files changed (76) hide show
  1. package/dist/components/composite/confirm-dialog.js +56 -35
  2. package/dist/components/composite/confirm-dialog.js.map +1 -1
  3. package/dist/components/composite/search-filter-bar-constants.d.ts +3 -0
  4. package/dist/components/composite/search-filter-bar-constants.js.map +1 -1
  5. package/dist/components/composite/table-pagination-constants.d.ts +3 -1
  6. package/dist/components/composite/table-pagination-constants.js.map +1 -1
  7. package/dist/components/composite/table-pagination.js +10 -2
  8. package/dist/components/composite/table-pagination.js.map +1 -1
  9. package/dist/components/composite/text-utils.d.ts +4 -23
  10. package/dist/components/composite/text-utils.js +1 -11
  11. package/dist/components/composite/text-utils.js.map +1 -1
  12. package/dist/components/ui/banner.d.ts +8 -0
  13. package/dist/components/ui/banner.js +149 -35
  14. package/dist/components/ui/banner.js.map +1 -1
  15. package/dist/components/ui/mask-input-constants.d.ts +34 -2
  16. package/dist/components/ui/mask-input-constants.js +56 -5
  17. package/dist/components/ui/mask-input-constants.js.map +1 -1
  18. package/dist/components/ui/mask-input.d.ts +6 -5
  19. package/dist/components/ui/mask-input.js +5 -1
  20. package/dist/components/ui/mask-input.js.map +1 -1
  21. package/dist/components/ui/mention.d.ts +14 -2
  22. package/dist/components/ui/mention.js +33 -6
  23. package/dist/components/ui/mention.js.map +1 -1
  24. package/dist/components/ui/phone-input.d.ts +18 -8
  25. package/dist/components/ui/phone-input.js +120 -33
  26. package/dist/components/ui/phone-input.js.map +1 -1
  27. package/dist/components/ui/scroll-spy.js +46 -31
  28. package/dist/components/ui/scroll-spy.js.map +1 -1
  29. package/dist/components/ui/stepper.js +26 -3
  30. package/dist/components/ui/stepper.js.map +1 -1
  31. package/dist/components/ui/time-picker.d.ts +24 -4
  32. package/dist/components/ui/time-picker.js +425 -162
  33. package/dist/components/ui/time-picker.js.map +1 -1
  34. package/dist/components/ui/timeline.js +45 -9
  35. package/dist/components/ui/timeline.js.map +1 -1
  36. package/dist/data-grid/data-grid-cell-variants.js +4 -4
  37. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  38. package/dist/data-grid/data-grid-cell-wrapper.d.ts +1 -1
  39. package/dist/data-grid/data-grid-cell-wrapper.js +6 -0
  40. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  41. package/dist/data-grid/data-grid-row.js +1 -1
  42. package/dist/data-grid/data-grid-row.js.map +1 -1
  43. package/dist/data-grid/data-grid-select-column.js +6 -2
  44. package/dist/data-grid/data-grid-select-column.js.map +1 -1
  45. package/dist/data-grid/data-grid-skeleton.d.ts +5 -0
  46. package/dist/data-grid/data-grid-skeleton.js +1 -4
  47. package/dist/data-grid/data-grid-skeleton.js.map +1 -1
  48. package/dist/data-grid/data-grid.d.ts +1 -1
  49. package/dist/data-grid/data-grid.js +3 -2
  50. package/dist/data-grid/data-grid.js.map +1 -1
  51. package/dist/data-grid/filters.js +3 -2
  52. package/dist/data-grid/filters.js.map +1 -1
  53. package/dist/data-grid/types.d.ts +7 -0
  54. package/dist/data-grid/use-data-grid.d.ts +3 -2
  55. package/dist/data-grid/use-data-grid.js +70 -24
  56. package/dist/data-grid/use-data-grid.js.map +1 -1
  57. package/dist/data-grid/utils.d.ts +7 -0
  58. package/dist/data-grid/utils.js +3 -1
  59. package/dist/data-grid/utils.js.map +1 -1
  60. package/dist/i18n/namespaces/search-filter-bar.js +8 -6
  61. package/dist/i18n/namespaces/search-filter-bar.js.map +1 -1
  62. package/dist/i18n/namespaces/table-pagination.js +6 -4
  63. package/dist/i18n/namespaces/table-pagination.js.map +1 -1
  64. package/dist/index.d.ts +2 -1
  65. package/dist/lib/collect-item-elements.d.ts +47 -0
  66. package/dist/lib/collect-item-elements.js +22 -0
  67. package/dist/lib/collect-item-elements.js.map +1 -0
  68. package/dist/lib/text-search.d.ts +32 -0
  69. package/dist/lib/text-search.js +17 -0
  70. package/dist/lib/text-search.js.map +1 -0
  71. package/dist/shell/app-shell.d.ts +1 -0
  72. package/dist/shell/display-axis-provider.d.ts +1 -1
  73. package/dist/shell/index.d.ts +2 -1
  74. package/dist/shell/shell-context.d.ts +1 -0
  75. package/dist/shell/utils.d.ts +2 -1
  76. package/package.json +1 -1
@@ -51,6 +51,28 @@ function useBanner() {
51
51
  };
52
52
  }, [id, variant, dismissible, onClose, storeContext]);
53
53
  }
54
+ function insertByPriority(banners, banner) {
55
+ const priority = banner.priority ?? DEFAULT_BANNER_PRIORITY;
56
+ const insertIndex = banners.findIndex((b) => (b.priority ?? DEFAULT_BANNER_PRIORITY) < priority);
57
+ if (insertIndex === -1) {
58
+ banners.push(banner);
59
+ } else {
60
+ banners.splice(insertIndex, 0, banner);
61
+ }
62
+ }
63
+ function scheduleAutoClose(timeouts, id, duration, onElapsed) {
64
+ const pendingTimeoutId = timeouts.get(id);
65
+ if (pendingTimeoutId !== void 0) {
66
+ clearTimeout(pendingTimeoutId);
67
+ timeouts.delete(id);
68
+ }
69
+ if (!duration || duration <= 0) return;
70
+ const timeoutId = setTimeout(() => {
71
+ onElapsed();
72
+ timeouts.delete(id);
73
+ }, duration);
74
+ timeouts.set(id, timeoutId);
75
+ }
54
76
  function Banners(props) {
55
77
  const { children, maxVisible = 1, side = "top", strategy = "fixed", container: containerProp } = props;
56
78
  const instanceId = React.useId();
@@ -78,25 +100,34 @@ function Banners(props) {
78
100
  idCounterRef.current += 1;
79
101
  const id = `${instanceId}-${idCounterRef.current}`;
80
102
  const newBanner = { ...banner, id };
81
- const priority = banner.priority ?? DEFAULT_BANNER_PRIORITY;
82
103
  const banners2 = [...stateRef.current.banners];
83
- const insertIndex = banners2.findIndex((b) => (b.priority ?? DEFAULT_BANNER_PRIORITY) < priority);
84
- if (insertIndex === -1) {
85
- banners2.push(newBanner);
86
- } else {
87
- banners2.splice(insertIndex, 0, newBanner);
88
- }
104
+ insertByPriority(banners2, newBanner);
89
105
  stateRef.current.banners = banners2;
90
106
  store.notify();
91
- if (banner.duration && banner.duration > 0) {
92
- const timeoutId = setTimeout(() => {
93
- store.onRemovingChange(id, true);
94
- timeoutsRef.current.delete(id);
95
- }, banner.duration);
96
- timeoutsRef.current.set(id, timeoutId);
97
- }
107
+ scheduleAutoClose(timeoutsRef.current, id, banner.duration, () => store.onRemovingChange(id, true));
98
108
  return id;
99
109
  },
110
+ onBannerUpdate: (id, patch) => {
111
+ const banners2 = stateRef.current.banners;
112
+ const index = banners2.findIndex((b) => b.id === id);
113
+ const current = banners2[index];
114
+ if (!current) return;
115
+ const keys = Object.keys(patch);
116
+ if (keys.every((key) => Object.is(current[key], patch[key]))) return;
117
+ const next = { ...current, ...patch };
118
+ const nextBanners = [...banners2];
119
+ if (Object.is(next.priority ?? DEFAULT_BANNER_PRIORITY, current.priority ?? DEFAULT_BANNER_PRIORITY)) {
120
+ nextBanners[index] = next;
121
+ } else {
122
+ nextBanners.splice(index, 1);
123
+ insertByPriority(nextBanners, next);
124
+ }
125
+ stateRef.current.banners = nextBanners;
126
+ if (!Object.is(next.duration, current.duration) && !stateRef.current.removing.has(id)) {
127
+ scheduleAutoClose(timeoutsRef.current, id, next.duration, () => store.onRemovingChange(id, true));
128
+ }
129
+ store.notify();
130
+ },
100
131
  onBannerRemove: (id) => {
101
132
  const banner = stateRef.current.banners.find((b) => b.id === id);
102
133
  if (!banner) return;
@@ -232,6 +263,49 @@ const bannerVariants = cva(
232
263
  }
233
264
  }
234
265
  );
266
+ function useFocusReturn(active, onFocusProp, onBlurProp) {
267
+ const returnFocusRef = React.useRef(null);
268
+ const focusInsideRef = React.useRef(false);
269
+ const onFocus = React.useCallback(
270
+ (event) => {
271
+ onFocusProp?.(event);
272
+ const from = event.relatedTarget;
273
+ if (!focusInsideRef.current && from instanceof HTMLElement && !event.currentTarget.contains(from)) {
274
+ returnFocusRef.current = from;
275
+ }
276
+ focusInsideRef.current = true;
277
+ },
278
+ [onFocusProp]
279
+ );
280
+ const onBlur = React.useCallback(
281
+ (event) => {
282
+ onBlurProp?.(event);
283
+ const to = event.relatedTarget;
284
+ if (!(to instanceof Node) || !event.currentTarget.contains(to)) {
285
+ focusInsideRef.current = false;
286
+ }
287
+ },
288
+ [onBlurProp]
289
+ );
290
+ useIsomorphicLayoutEffect(() => {
291
+ if (!active) return;
292
+ return () => {
293
+ const returnTarget = returnFocusRef.current;
294
+ const hadFocus = focusInsideRef.current;
295
+ returnFocusRef.current = null;
296
+ focusInsideRef.current = false;
297
+ if (!hadFocus || !returnTarget) return;
298
+ queueMicrotask(() => {
299
+ const ownerDocument = returnTarget.ownerDocument;
300
+ const activeElement = ownerDocument.activeElement;
301
+ if (activeElement && activeElement !== ownerDocument.body && activeElement.isConnected) return;
302
+ if (!returnTarget.isConnected) return;
303
+ returnTarget.focus({ preventScroll: true });
304
+ });
305
+ };
306
+ }, [active]);
307
+ return { onFocus, onBlur };
308
+ }
235
309
  function BannerImpl(props) {
236
310
  const { banner, side, index } = props;
237
311
  const store = useStoreContext("BannerImpl");
@@ -257,9 +331,15 @@ function BannerImpl(props) {
257
331
  return () => cancelAnimationFrame(frame);
258
332
  }, []);
259
333
  useIsomorphicLayoutEffect(() => {
260
- if (!bannerRef.current || removing) return;
261
- const height = bannerRef.current.getBoundingClientRect().height;
262
- store.onHeightChange(banner.id, height);
334
+ const node = bannerRef.current;
335
+ if (!node || removing) return;
336
+ store.onHeightChange(banner.id, node.getBoundingClientRect().height);
337
+ if (typeof ResizeObserver === "undefined") return;
338
+ const observer = new ResizeObserver(() => {
339
+ store.onHeightChange(banner.id, node.getBoundingClientRect().height);
340
+ });
341
+ observer.observe(node);
342
+ return () => observer.disconnect();
263
343
  }, [store, banner.id, removing]);
264
344
  React.useEffect(() => {
265
345
  if (!removing) return;
@@ -269,6 +349,7 @@ function BannerImpl(props) {
269
349
  }, [removing, store, banner.id]);
270
350
  const onClose = React.useCallback(() => store.onRemovingChange(banner.id, true), [store, banner.id]);
271
351
  const onRemove = React.useCallback(() => store.onBannerRemove(banner.id), [store, banner.id]);
352
+ const { onFocus, onBlur } = useFocusReturn(true);
272
353
  const dismissible = banner.dismissible ?? DEFAULT_BANNER_DISMISSIBLE;
273
354
  const contextValue = React.useMemo(
274
355
  () => ({ id: banner.id, variant: banner.variant, dismissible, onClose }),
@@ -319,10 +400,36 @@ function BannerImpl(props) {
319
400
  // Thời lượng qua `--banner-duration` (0 khi giảm chuyển động) — xem chú thích ở `Banners`.
320
401
  transition: `transform var(--banner-duration, ${BANNER_ANIMATION_DURATION}ms) cubic-bezier(0.32, 0.72, 0, 1), opacity var(--banner-duration, ${opacityDuration}ms) ease`
321
402
  },
403
+ onFocus,
404
+ onBlur,
322
405
  children: typeof banner.content === "function" ? banner.content(renderProps) : banner.content
323
406
  }
324
407
  ) });
325
408
  }
409
+ function createChildrenSource(initialChildren) {
410
+ let children = initialChildren;
411
+ const listeners = /* @__PURE__ */ new Set();
412
+ return {
413
+ get: () => children,
414
+ set: (nextChildren) => {
415
+ if (Object.is(children, nextChildren)) return;
416
+ children = nextChildren;
417
+ for (const listener of listeners) {
418
+ listener();
419
+ }
420
+ },
421
+ subscribe: (listener) => {
422
+ listeners.add(listener);
423
+ return () => {
424
+ listeners.delete(listener);
425
+ };
426
+ }
427
+ };
428
+ }
429
+ function DeclaredBannerChildren(props) {
430
+ const { source } = props;
431
+ return React.useSyncExternalStore(source.subscribe, source.get, source.get);
432
+ }
326
433
  function Banner(props) {
327
434
  const {
328
435
  className,
@@ -336,6 +443,8 @@ function Banner(props) {
336
443
  dismissible = DEFAULT_BANNER_DISMISSIBLE,
337
444
  children,
338
445
  asChild,
446
+ onFocus: onFocusProp,
447
+ onBlur: onBlurProp,
339
448
  ...rootProps
340
449
  } = props;
341
450
  const store = React.useContext(StoreContext);
@@ -346,6 +455,9 @@ function Banner(props) {
346
455
  const bannerIdRef = React.useRef(null);
347
456
  const onDismissRef = useAsRef(onDismiss);
348
457
  const onOpenChangeRef = useAsRef(onOpenChange);
458
+ const isControlledRef = useAsRef(isControlled);
459
+ const queuedDataRef = useAsRef({ variant: variant ?? void 0, priority, dismissible, duration });
460
+ const childrenSourceRef = useLazyRef(() => createChildrenSource(children));
349
461
  if (isControlled) {
350
462
  openRef.current = openProp;
351
463
  }
@@ -358,18 +470,23 @@ function Banner(props) {
358
470
  );
359
471
  const getSnapshot = React.useCallback(() => openRef.current, [openRef]);
360
472
  const open = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
473
+ useIsomorphicLayoutEffect(() => {
474
+ childrenSourceRef.current.set(children);
475
+ });
476
+ useIsomorphicLayoutEffect(() => {
477
+ const id = bannerIdRef.current;
478
+ if (!store || id === null) return;
479
+ store.onBannerUpdate(id, { variant: variant ?? void 0, priority, dismissible, duration });
480
+ }, [store, variant, priority, dismissible, duration]);
361
481
  React.useEffect(() => {
362
- if (!isInsideStore || !store || !open) return;
482
+ if (!store || !open) return;
363
483
  let isCleaningUp = false;
364
484
  const id = store.onBannerAdd({
365
- content: children,
366
- variant: variant ?? void 0,
367
- priority,
368
- dismissible,
369
- duration,
485
+ ...queuedDataRef.current,
486
+ content: /* @__PURE__ */ jsx(DeclaredBannerChildren, { source: childrenSourceRef.current }),
370
487
  onDismiss: () => {
371
488
  if (isCleaningUp) return;
372
- if (!isControlled) {
489
+ if (!isControlledRef.current) {
373
490
  openRef.current = false;
374
491
  for (const listener of listenersRef.current) {
375
492
  listener();
@@ -382,21 +499,15 @@ function Banner(props) {
382
499
  bannerIdRef.current = id;
383
500
  return () => {
384
501
  isCleaningUp = true;
385
- if (bannerIdRef.current) {
386
- store.onBannerRemove(bannerIdRef.current);
387
- bannerIdRef.current = null;
388
- }
502
+ bannerIdRef.current = null;
503
+ store.onBannerRemove(id);
389
504
  };
390
505
  }, [
391
- isInsideStore,
392
506
  store,
393
507
  open,
394
- isControlled,
395
- children,
396
- variant,
397
- priority,
398
- dismissible,
399
- duration,
508
+ queuedDataRef,
509
+ childrenSourceRef,
510
+ isControlledRef,
400
511
  openRef,
401
512
  listenersRef,
402
513
  onDismissRef,
@@ -411,6 +522,7 @@ function Banner(props) {
411
522
  }
412
523
  onOpenChangeRef.current?.(false);
413
524
  }, [isControlled, openRef, listenersRef, onOpenChangeRef]);
525
+ const { onFocus, onBlur } = useFocusReturn(open && !isInsideStore, onFocusProp, onBlurProp);
414
526
  const contextValue = React.useMemo(
415
527
  () => ({
416
528
  variant,
@@ -430,6 +542,8 @@ function Banner(props) {
430
542
  "data-state": "open",
431
543
  className: cn(bannerVariants({ variant, className })),
432
544
  ...rootProps,
545
+ onFocus,
546
+ onBlur,
433
547
  children
434
548
  }
435
549
  ) });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/banner.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/banner) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n *\n * Dùng ở Comitor: thông báo TOÀN WORKSPACE ở đỉnh khung app (sắp hết hạn dùng thử, bảo trì, lỗi\n * thanh toán) — đóng được, khác `Alert` là khối tĩnh nằm trong nội dung trang.\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { Button } from \"./button.js\";\n\nconst BANNER_ANIMATION_DURATION = 400;\nconst DEFAULT_BANNER_PRIORITY = 0;\nconst DEFAULT_BANNER_DISMISSIBLE = true;\n\ntype BannerVariant = \"default\" | \"info\" | \"success\" | \"warning\" | \"destructive\";\ntype BannerSide = \"top\" | \"bottom\";\ntype BannerStrategy = \"fixed\" | \"static\" | \"sticky\" | \"absolute\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype CloseElement = React.ComponentRef<typeof BannerClose>;\n\ninterface BannerRenderProps {\n id: string;\n variant?: BannerVariant;\n dismissible: boolean;\n onClose: () => void;\n onRemove: () => void;\n}\n\n// Thượng nguồn đặt tên kiểu này là `BannerContent` — trùng tên component `BannerContent`. Đổi tên\n// để `export { BannerContent }` không mang theo một KIỂU cùng tên mà nghĩa khác hẳn.\ntype BannerContentValue = React.ReactNode | ((props: BannerRenderProps) => React.ReactNode);\n\ninterface BannerData {\n id: string;\n content: BannerContentValue;\n variant?: BannerVariant;\n priority?: number;\n duration?: number;\n dismissible?: boolean;\n onDismiss?: () => void;\n}\n\ninterface StoreState {\n banners: BannerData[];\n removing: Set<string>;\n heights: Map<string, number>;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n notify: () => void;\n onBannerAdd: (banner: Omit<BannerData, \"id\">) => string;\n onBannerRemove: (id: string) => void;\n onBannersClear: () => void;\n onRemovingChange: (id: string, value: boolean) => void;\n onHeightChange: (id: string, height: number) => void;\n onHeightRemove: (id: string) => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`Banners\\``);\n }\n return context;\n}\n\nfunction useStore<T>(store: Store, selector: (state: StoreState) => T): T {\n return React.useSyncExternalStore(\n store.subscribe,\n () => selector(store.getState()),\n () => selector(store.getState())\n );\n}\n\ninterface BannerContextValue {\n id?: string;\n variant?: BannerVariant | null;\n dismissible?: boolean;\n onClose?: () => void;\n}\n\nconst BannerContext = React.createContext<BannerContextValue | null>(null);\n\nfunction useBannerContext(consumerName: string) {\n const context = React.useContext(BannerContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`Banner\\``);\n }\n return context;\n}\n\nfunction useBanner() {\n const { id, variant, dismissible, onClose } = useBannerContext(\"useBanner\");\n const storeContext = React.useContext(StoreContext);\n\n return React.useMemo(() => {\n const onRemove = id && storeContext ? () => storeContext.onBannerRemove(id) : undefined;\n\n return {\n id,\n variant,\n dismissible,\n onClose,\n onRemove\n };\n }, [id, variant, dismissible, onClose, storeContext]);\n}\n\ninterface BannersProps {\n children?: React.ReactNode;\n maxVisible?: number;\n side?: BannerSide;\n strategy?: BannerStrategy;\n container?: Element | DocumentFragment | null;\n}\n\nfunction Banners(props: BannersProps) {\n const { children, maxVisible = 1, side = \"top\", strategy = \"fixed\", container: containerProp } = props;\n\n const instanceId = React.useId();\n const stateRef = useLazyRef<StoreState>(() => ({\n banners: [],\n removing: new Set(),\n heights: new Map()\n }));\n const listenersRef = useLazyRef<Set<() => void>>(() => new Set());\n const timeoutsRef = useLazyRef<Map<string, ReturnType<typeof setTimeout>>>(() => new Map());\n const idCounterRef = React.useRef(0);\n\n const store: Store = React.useMemo(\n () => ({\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n notify: () => {\n for (const listener of listenersRef.current) {\n listener();\n }\n },\n onBannerAdd: (banner) => {\n /*\n * ⚠ Khác thượng nguồn: bản gốc `crypto.randomUUID()` — hàm này CHỈ có trong secure\n * context (https, localhost). Mở app qua http bằng IP mạng nội bộ (máy dev, môi trường\n * nội bộ) thì nó là `undefined` và mọi lần thêm banner đều throw. Id chỉ cần duy nhất\n * trong một `Banners` → bộ đếm, tiền tố `useId()` cho chắc khi có nhiều `Banners`.\n */\n idCounterRef.current += 1;\n const id = `${instanceId}-${idCounterRef.current}`;\n const newBanner: BannerData = { ...banner, id };\n const priority = banner.priority ?? DEFAULT_BANNER_PRIORITY;\n\n const banners = [...stateRef.current.banners];\n const insertIndex = banners.findIndex((b) => (b.priority ?? DEFAULT_BANNER_PRIORITY) < priority);\n\n if (insertIndex === -1) {\n banners.push(newBanner);\n } else {\n banners.splice(insertIndex, 0, newBanner);\n }\n\n stateRef.current.banners = banners;\n store.notify();\n\n if (banner.duration && banner.duration > 0) {\n const timeoutId = setTimeout(() => {\n store.onRemovingChange(id, true);\n timeoutsRef.current.delete(id);\n }, banner.duration);\n timeoutsRef.current.set(id, timeoutId);\n }\n\n return id;\n },\n onBannerRemove: (id) => {\n const banner = stateRef.current.banners.find((b) => b.id === id);\n if (!banner) return;\n\n const timeoutId = timeoutsRef.current.get(id);\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutsRef.current.delete(id);\n }\n\n const newRemoving = new Set(stateRef.current.removing);\n newRemoving.delete(id);\n stateRef.current.removing = newRemoving;\n\n banner.onDismiss?.();\n stateRef.current.banners = stateRef.current.banners.filter((b) => b.id !== id);\n store.notify();\n },\n onBannersClear: () => {\n for (const timeoutId of timeoutsRef.current.values()) {\n clearTimeout(timeoutId);\n }\n timeoutsRef.current.clear();\n stateRef.current.removing = new Set();\n stateRef.current.heights = new Map();\n stateRef.current.banners = [];\n store.notify();\n },\n onRemovingChange: (id, value) => {\n const newSet = new Set(stateRef.current.removing);\n if (value) {\n newSet.add(id);\n } else {\n newSet.delete(id);\n }\n stateRef.current.removing = newSet;\n store.notify();\n },\n onHeightChange: (id, height) => {\n if (stateRef.current.heights.get(id) === height) return;\n const newHeights = new Map(stateRef.current.heights);\n newHeights.set(id, height);\n stateRef.current.heights = newHeights;\n store.notify();\n },\n onHeightRemove: (id) => {\n if (!stateRef.current.heights.has(id)) return;\n const newHeights = new Map(stateRef.current.heights);\n newHeights.delete(id);\n stateRef.current.heights = newHeights;\n store.notify();\n }\n }),\n [instanceId, stateRef, listenersRef, timeoutsRef]\n );\n\n const banners = useStore(store, (state) => state.banners);\n const heights = useStore(store, (state) => state.heights);\n const visibleBanners = banners.slice(0, maxVisible);\n\n /*\n * ⚠ Khác thượng nguồn: bản gốc đọc `globalThis.document?.body` NGAY TRONG render. Ở đây chỉ đọc\n * sau khi mount (cùng khuôn với `ActionBar`) — lượt render phía server và lượt hydrate không\n * chạm `document`.\n */\n const [mounted, setMounted] = React.useState(false);\n useIsomorphicLayoutEffect(() => {\n setMounted(true);\n }, []);\n\n const withPortal = strategy === \"fixed\" || strategy === \"absolute\";\n const container = withPortal ? (containerProp ?? (mounted ? globalThis.document?.body : null) ?? null) : null;\n\n const totalHeight = React.useMemo(() => {\n let total = 0;\n for (const banner of visibleBanners) {\n total += heights.get(banner.id) ?? 0;\n }\n return total;\n }, [visibleBanners, heights]);\n\n const bannerContainer = visibleBanners.length > 0 && (\n <div\n data-slot=\"banner-container\"\n data-side={side}\n data-strategy={strategy}\n className={cn(\n \"pointer-events-none right-0 left-0 isolate z-50\",\n /*\n * ⚠ Khác thượng nguồn: bản gốc ghi `transition` thẳng vào `style`, nên lớp\n * `motion-reduce:transition-none` của từng banner KHÔNG BAO GIỜ thắng được (style nội tuyến\n * luôn thắng lớp) — người bật \"giảm chuyển động\" vẫn thấy banner trượt. Nay thời lượng đi\n * qua `--banner-duration`, bằng 0 khi `prefers-reduced-motion`; banner con thừa kế biến này.\n */\n \"motion-reduce:[--banner-duration:0ms]\",\n strategy === \"fixed\" && \"fixed\",\n strategy === \"static\" && \"relative\",\n strategy === \"sticky\" && \"sticky\",\n strategy === \"absolute\" && \"absolute\",\n side === \"top\" ? \"top-0\" : \"bottom-0\"\n )}\n style={{\n height: totalHeight > 0 ? totalHeight : \"auto\",\n transition: `height var(--banner-duration, ${BANNER_ANIMATION_DURATION}ms) cubic-bezier(0.32, 0.72, 0, 1)`\n }}\n >\n {visibleBanners.map((banner, index) => (\n <BannerImpl key={banner.id} banner={banner} side={side} index={index} />\n ))}\n </div>\n );\n\n return (\n <StoreContext.Provider value={store}>\n {strategy === \"static\" || strategy === \"sticky\" ? (\n <>\n {side === \"top\" && bannerContainer}\n {children}\n {side === \"bottom\" && bannerContainer}\n </>\n ) : (\n <>\n {children}\n {container && bannerContainer && ReactDOM.createPortal(bannerContainer, container)}\n </>\n )}\n </StoreContext.Provider>\n );\n}\n\nfunction useBanners() {\n const store = useStoreContext(\"useBanners\");\n const banners = useStore(store, (state) => state.banners);\n\n return React.useMemo(\n () => ({\n onBannerAdd: store.onBannerAdd,\n onBannerRemove: store.onBannerRemove,\n onBannersClear: store.onBannersClear,\n banners\n }),\n [store, banners]\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn: bản gốc tô bằng bảng màu mặc định của Tailwind (`bg-blue-50\n * text-blue-900`, `bg-red-50`… kèm `dark:`) — đi vòng qua hợp đồng token nên không đổi theo bảng\n * tương phản cao. Ở đây dùng ĐÚNG bộ lớp của `Alert` (alert.tsx) để Banner và Alert cùng tông\n * trông như một họ: nền `bg-card`, chữ + icon ở bậc `-ink` của tone. `Alert` chưa có `info` /\n * `success`; hai tông đó theo cùng khuôn với `warning` / `destructive` của nó.\n *\n * Nền ĐẶC `bg-card` chứ không tint `bg-<tone>/15`: ở chế độ `fixed`/`absolute`, banner nằm ĐÈ lên\n * nội dung trang — một nền trong suốt một phần sẽ cho chữ trang lộ xuyên qua chữ banner.\n *\n * Mô tả (`BannerDescription`) giữ `opacity-90` của bản gốc — trên nền đặc nó tương đương đúng\n * `text-<tone>-ink/90` của `AlertDescription`. Cổng không đo được `opacity`, nên số đo tay (trên\n * `--card`, tiêu đề / mô tả 90%):\n *\n * bảng tương phản cao light dark\n * info 6,68 / 5,31 9,54 / 7,92 ✓\n * success 6,52 / 5,20 10,19 / 8,44 ✓\n * warning 6,51 / 5,17 11,60 / 9,56 ✓\n * destructive 7,15 / 5,77 6,42 / 5,41 ✓\n * bảng mặc định (nợ token đã chấp nhận, cùng mức với `Alert`):\n * destructive 4,38 / 3,81 (light) · 4,06 / 3,50 (dark) · info dark 4,28 / 3,71\n */\nconst bannerVariants = cva(\n \"pointer-events-auto relative flex w-full items-center gap-3 border-b px-4 py-3 text-sm motion-reduce:transition-none\",\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n info: \"bg-card text-info-ink\",\n success: \"bg-card text-success-ink\",\n warning: \"bg-card text-warning-ink\",\n destructive: \"bg-card text-destructive-ink\"\n }\n },\n defaultVariants: {\n variant: \"default\"\n }\n }\n);\n\ninterface BannerImplProps {\n banner: BannerData;\n side: BannerSide;\n index: number;\n}\n\nfunction BannerImpl(props: BannerImplProps) {\n const { banner, side, index } = props;\n\n const store = useStoreContext(\"BannerImpl\");\n const removing = useStore(store, (state) => state.removing.has(banner.id));\n const banners = useStore(store, (state) => state.banners);\n const heights = useStore(store, (state) => state.heights);\n\n const [mounted, setMounted] = React.useState(false);\n const bannerRef = React.useRef<HTMLDivElement>(null);\n const offsetBeforeRemoveRef = React.useRef(0);\n\n const offset = React.useMemo(() => {\n let total = 0;\n for (const b of banners) {\n if (b.id === banner.id) break;\n total += heights.get(b.id) ?? 0;\n }\n return total;\n }, [banners, heights, banner.id]);\n\n if (!removing) {\n offsetBeforeRemoveRef.current = offset;\n }\n\n React.useEffect(() => {\n const frame = requestAnimationFrame(() => setMounted(true));\n return () => cancelAnimationFrame(frame);\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n if (!bannerRef.current || removing) return;\n const height = bannerRef.current.getBoundingClientRect().height;\n store.onHeightChange(banner.id, height);\n }, [store, banner.id, removing]);\n\n React.useEffect(() => {\n if (!removing) return;\n store.onHeightRemove(banner.id);\n const timeoutId = setTimeout(() => store.onBannerRemove(banner.id), BANNER_ANIMATION_DURATION);\n return () => clearTimeout(timeoutId);\n }, [removing, store, banner.id]);\n\n const onClose = React.useCallback(() => store.onRemovingChange(banner.id, true), [store, banner.id]);\n\n const onRemove = React.useCallback(() => store.onBannerRemove(banner.id), [store, banner.id]);\n\n const dismissible = banner.dismissible ?? DEFAULT_BANNER_DISMISSIBLE;\n\n const contextValue = React.useMemo<BannerContextValue>(\n () => ({ id: banner.id, variant: banner.variant, dismissible, onClose }),\n [banner.id, banner.variant, dismissible, onClose]\n );\n\n const renderProps = React.useMemo<BannerRenderProps>(\n () => ({\n id: banner.id,\n variant: banner.variant,\n dismissible,\n onClose,\n onRemove\n }),\n [banner.id, banner.variant, dismissible, onClose, onRemove]\n );\n\n const currentOffset = removing ? offsetBeforeRemoveRef.current : offset;\n const isTop = side === \"top\";\n\n function getTransform() {\n if (!mounted) return isTop ? \"translateY(-100%)\" : \"translateY(100%)\";\n if (removing) {\n return isTop ? `translateY(calc(${currentOffset}px - 100%))` : `translateY(calc(-${currentOffset}px + 100%))`;\n }\n return isTop ? `translateY(${currentOffset}px)` : `translateY(-${currentOffset}px)`;\n }\n\n const opacityDuration = removing ? BANNER_ANIMATION_DURATION / 2 : BANNER_ANIMATION_DURATION;\n\n return (\n <BannerContext.Provider value={contextValue}>\n <div\n role=\"status\"\n aria-live=\"polite\"\n data-slot=\"queued-banner\"\n data-state={removing ? \"closed\" : \"open\"}\n data-mounted={mounted}\n data-removed={removing}\n data-side={side}\n data-front={index === 0}\n data-index={index}\n ref={bannerRef}\n className={bannerVariants({ variant: banner.variant })}\n style={{\n position: \"absolute\",\n [isTop ? \"top\" : \"bottom\"]: 0,\n left: 0,\n right: 0,\n zIndex: removing ? 0 : 50 - index,\n transform: getTransform(),\n opacity: mounted && !removing ? 1 : 0,\n // Thời lượng qua `--banner-duration` (0 khi giảm chuyển động) — xem chú thích ở `Banners`.\n transition: `transform var(--banner-duration, ${BANNER_ANIMATION_DURATION}ms) cubic-bezier(0.32, 0.72, 0, 1), opacity var(--banner-duration, ${opacityDuration}ms) ease`\n }}\n >\n {typeof banner.content === \"function\" ? banner.content(renderProps) : banner.content}\n </div>\n </BannerContext.Provider>\n );\n}\n\ninterface BannerProps extends DivProps, VariantProps<typeof bannerVariants> {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n onDismiss?: () => void;\n priority?: number;\n duration?: number;\n dismissible?: boolean;\n}\n\nfunction Banner(props: BannerProps) {\n const {\n className,\n variant = \"default\",\n open: openProp,\n defaultOpen = true,\n onOpenChange,\n onDismiss,\n priority,\n duration,\n dismissible = DEFAULT_BANNER_DISMISSIBLE,\n children,\n asChild,\n ...rootProps\n } = props;\n\n const store = React.useContext(StoreContext);\n\n const isInsideStore = store !== null;\n const isControlled = openProp !== undefined;\n\n const openRef = useLazyRef(() => openProp ?? defaultOpen);\n const listenersRef = useLazyRef<Set<() => void>>(() => new Set());\n const bannerIdRef = React.useRef<string | null>(null);\n const onDismissRef = useAsRef(onDismiss);\n const onOpenChangeRef = useAsRef(onOpenChange);\n\n if (isControlled) {\n openRef.current = openProp;\n }\n\n const subscribe = React.useCallback(\n (cb: () => void) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n [listenersRef]\n );\n\n const getSnapshot = React.useCallback(() => openRef.current, [openRef]);\n\n const open = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n React.useEffect(() => {\n if (!isInsideStore || !store || !open) return;\n\n /*\n * ⚠ Khác thượng nguồn — hai lỗi của `<Banner>` đặt trong `<Banners>`:\n *\n * 1. Hàm dọn của effect này gỡ banner bằng `onBannerRemove`, mà hàm đó GỌI `onDismiss`. Bản\n * gốc vì thế báo \"người dùng đã đóng\" (`onDismiss` + `onOpenChange(false)`) mỗi khi effect\n * chạy lại — tức MỖI LẦN cha re-render, vì `children` là JSX mới — và cả khi unmount (đổi\n * route). Banner tự đóng, và app lưu \"đã tắt thông báo\" cho một lần người dùng chưa hề bấm.\n * Cờ `isCleaningUp` chặn đúng đường đó; đóng thật (nút X, hết `duration`) vẫn báo như cũ.\n * 2. Ở chế độ không kiểm soát, đóng qua hàng đợi không hạ `open` của chính `Banner`, nên lần\n * re-render kế tiếp thêm LẠI banner vừa đóng. Nay hạ `open` như `onClose` bên dưới.\n */\n let isCleaningUp = false;\n\n const id = store.onBannerAdd({\n content: children,\n variant: variant ?? undefined,\n priority,\n dismissible,\n duration,\n onDismiss: () => {\n if (isCleaningUp) return;\n if (!isControlled) {\n openRef.current = false;\n for (const listener of listenersRef.current) {\n listener();\n }\n }\n onDismissRef.current?.();\n onOpenChangeRef.current?.(false);\n }\n });\n bannerIdRef.current = id;\n\n return () => {\n isCleaningUp = true;\n if (bannerIdRef.current) {\n store.onBannerRemove(bannerIdRef.current);\n bannerIdRef.current = null;\n }\n };\n }, [\n isInsideStore,\n store,\n open,\n isControlled,\n children,\n variant,\n priority,\n dismissible,\n duration,\n openRef,\n listenersRef,\n onDismissRef,\n onOpenChangeRef\n ]);\n\n const onClose = React.useCallback(() => {\n if (!isControlled) {\n openRef.current = false;\n for (const listener of listenersRef.current) {\n listener();\n }\n }\n onOpenChangeRef.current?.(false);\n }, [isControlled, openRef, listenersRef, onOpenChangeRef]);\n\n const contextValue = React.useMemo<BannerContextValue>(\n () => ({\n variant,\n dismissible,\n onClose\n }),\n [variant, dismissible, onClose]\n );\n\n if (!open || isInsideStore) return null;\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <BannerContext.Provider value={contextValue}>\n <RootPrimitive\n role=\"status\"\n aria-live=\"polite\"\n data-slot=\"banner\"\n data-state=\"open\"\n className={cn(bannerVariants({ variant, className }))}\n {...rootProps}\n >\n {children}\n </RootPrimitive>\n </BannerContext.Provider>\n );\n}\n\nfunction BannerIcon(props: DivProps) {\n const { className, asChild, ...iconProps } = props;\n\n const IconPrimitive = asChild ? Slot : \"div\";\n\n return (\n <IconPrimitive\n data-slot=\"banner-icon\"\n className={cn(\"flex shrink-0 items-center [&>svg]:size-4\", className)}\n {...iconProps}\n />\n );\n}\n\nfunction BannerContent(props: DivProps) {\n const { className, asChild, ...contentProps } = props;\n\n const ContentPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ContentPrimitive\n data-slot=\"banner-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col gap-1\", className)}\n {...contentProps}\n />\n );\n}\n\nfunction BannerTitle(props: React.ComponentProps<\"div\">) {\n const { className, ...titleProps } = props;\n\n return <div data-slot=\"banner-title\" className={cn(\"text-sm leading-none font-medium\", className)} {...titleProps} />;\n}\n\nfunction BannerDescription(props: React.ComponentProps<\"div\">) {\n const { className, ...descriptionProps } = props;\n\n return <div data-slot=\"banner-description\" className={cn(\"text-xs opacity-90\", className)} {...descriptionProps} />;\n}\n\nfunction BannerActions(props: DivProps) {\n const { className, asChild, ...actionsProps } = props;\n\n const ActionsPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ActionsPrimitive\n data-slot=\"banner-actions\"\n className={cn(\"flex items-center gap-2\", className)}\n {...actionsProps}\n />\n );\n}\n\ntype BannerCloseProps = React.ComponentProps<typeof Button>;\n\n/**\n * Nút đóng banner. Không truyền `children` thì vẽ icon X.\n *\n * ⚠ Khác thượng nguồn: có tên cho trình đọc màn hình — `aria-label` mặc định lấy khoá\n * `closeLabel` của namespace `banner` (`ComitorLocaleProvider`), ghi đè bằng chính prop\n * `aria-label`. Bản gốc là `Button` chỉ chứa icon X, tức một nút KHÔNG TÊN. Nếu `children` là\n * CHỮ nhìn thấy được thì truyền `aria-label` trùng với chữ đó (WCAG 2.5.3), hoặc\n * `aria-label={undefined}`.\n */\nfunction BannerClose(props: BannerCloseProps) {\n const { onClick: onClickProp, disabled, children, ...closeProps } = props;\n\n const { dismissible = DEFAULT_BANNER_DISMISSIBLE, onClose } = useBannerContext(\"BannerClose\");\n const t = useComitorMessages(\"banner\");\n\n const isDisabled = disabled ?? !dismissible;\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<CloseElement>) => {\n onClickProp?.(event);\n if (event.defaultPrevented || isDisabled) return;\n onClose?.();\n },\n [onClickProp, isDisabled, onClose]\n );\n\n return (\n <Button\n aria-label={t.closeLabel}\n data-slot=\"banner-close\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClick}\n disabled={isDisabled}\n {...closeProps}\n >\n {children ?? <X aria-hidden=\"true\" className=\"size-3.5\" />}\n </Button>\n );\n}\n\nexport {\n Banner,\n BannerActions,\n BannerClose,\n type BannerCloseProps,\n BannerContent,\n type BannerData,\n BannerDescription,\n BannerIcon,\n type BannerProps,\n type BannerRenderProps,\n Banners,\n type BannersProps,\n BannerTitle,\n type BannerVariant,\n useBanner,\n useBanners\n};\n"],"mappings":"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","names":["banners"]}
1
+ {"version":3,"sources":["../../../src/components/ui/banner.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/banner) — MIT, © 2024 Sadman Sakib.\n * Giữ nguyên cấu trúc thượng nguồn để còn đối chiếu ngược được; mọi chỗ đổi đều có chú thích.\n *\n * Dùng ở Comitor: thông báo TOÀN WORKSPACE ở đỉnh khung app (sắp hết hạn dùng thử, bảo trì, lỗi\n * thanh toán) — đóng được, khác `Alert` là khối tĩnh nằm trong nội dung trang.\n */\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { X } from \"lucide-react\";\nimport * as React from \"react\";\nimport * as ReactDOM from \"react-dom\";\nimport { useComitorMessages } from \"../../i18n/locale-provider.js\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useAsRef } from \"../../lib/use-as-ref.js\";\nimport { useIsomorphicLayoutEffect } from \"../../lib/use-isomorphic-layout-effect.js\";\nimport { useLazyRef } from \"../../lib/use-lazy-ref.js\";\nimport { Button } from \"./button.js\";\n\nconst BANNER_ANIMATION_DURATION = 400;\nconst DEFAULT_BANNER_PRIORITY = 0;\nconst DEFAULT_BANNER_DISMISSIBLE = true;\n\ntype BannerVariant = \"default\" | \"info\" | \"success\" | \"warning\" | \"destructive\";\ntype BannerSide = \"top\" | \"bottom\";\ntype BannerStrategy = \"fixed\" | \"static\" | \"sticky\" | \"absolute\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\ntype CloseElement = React.ComponentRef<typeof BannerClose>;\n\ninterface BannerRenderProps {\n id: string;\n variant?: BannerVariant;\n dismissible: boolean;\n onClose: () => void;\n onRemove: () => void;\n}\n\n// Thượng nguồn đặt tên kiểu này là `BannerContent` — trùng tên component `BannerContent`. Đổi tên\n// để `export { BannerContent }` không mang theo một KIỂU cùng tên mà nghĩa khác hẳn.\ntype BannerContentValue = React.ReactNode | ((props: BannerRenderProps) => React.ReactNode);\n\ninterface BannerData {\n id: string;\n content: BannerContentValue;\n variant?: BannerVariant;\n priority?: number;\n duration?: number;\n dismissible?: boolean;\n onDismiss?: () => void;\n}\n\n// Phần của một banner ĐANG trong hàng đợi mà `onBannerUpdate` đổi được tại chỗ. Nội dung không nằm\n// ở đây — nó đi đường riêng, xem `ChildrenSource`.\ntype BannerUpdate = Pick<BannerData, \"variant\" | \"priority\" | \"duration\" | \"dismissible\">;\n\ninterface StoreState {\n banners: BannerData[];\n removing: Set<string>;\n heights: Map<string, number>;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n notify: () => void;\n onBannerAdd: (banner: Omit<BannerData, \"id\">) => string;\n // Nội bộ — KHÔNG xuất qua `useBanners()`. Chỉ `Banner` khai báo dùng (xem chú thích trong thân hàm).\n onBannerUpdate: (id: string, patch: BannerUpdate) => void;\n onBannerRemove: (id: string) => void;\n onBannersClear: () => void;\n onRemovingChange: (id: string, value: boolean) => void;\n onHeightChange: (id: string, height: number) => void;\n onHeightRemove: (id: string) => void;\n}\n\nconst StoreContext = React.createContext<Store | null>(null);\n\nfunction useStoreContext(consumerName: string) {\n const context = React.useContext(StoreContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`Banners\\``);\n }\n return context;\n}\n\nfunction useStore<T>(store: Store, selector: (state: StoreState) => T): T {\n return React.useSyncExternalStore(\n store.subscribe,\n () => selector(store.getState()),\n () => selector(store.getState())\n );\n}\n\ninterface BannerContextValue {\n id?: string;\n variant?: BannerVariant | null;\n dismissible?: boolean;\n onClose?: () => void;\n}\n\nconst BannerContext = React.createContext<BannerContextValue | null>(null);\n\nfunction useBannerContext(consumerName: string) {\n const context = React.useContext(BannerContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`Banner\\``);\n }\n return context;\n}\n\nfunction useBanner() {\n const { id, variant, dismissible, onClose } = useBannerContext(\"useBanner\");\n const storeContext = React.useContext(StoreContext);\n\n return React.useMemo(() => {\n const onRemove = id && storeContext ? () => storeContext.onBannerRemove(id) : undefined;\n\n return {\n id,\n variant,\n dismissible,\n onClose,\n onRemove\n };\n }, [id, variant, dismissible, onClose, storeContext]);\n}\n\n/**\n * Chèn `banner` vào `banners` (sửa tại chỗ) theo `priority`: cao đứng trước, cùng mức thì xếp CUỐI\n * nhóm. Một luật cho cả thêm mới (`onBannerAdd`) lẫn đổi `priority` tại chỗ (`onBannerUpdate`) —\n * thượng nguồn viết thẳng trong `onBannerAdd`; tách ra để hai đường không lệch nhau.\n */\nfunction insertByPriority(banners: BannerData[], banner: BannerData) {\n const priority = banner.priority ?? DEFAULT_BANNER_PRIORITY;\n const insertIndex = banners.findIndex((b) => (b.priority ?? DEFAULT_BANNER_PRIORITY) < priority);\n\n if (insertIndex === -1) {\n banners.push(banner);\n } else {\n banners.splice(insertIndex, 0, banner);\n }\n}\n\n/**\n * Hẹn giờ tự đóng theo `duration`, huỷ hẹn cũ của cùng `id` trước; `duration` không dương thì chỉ\n * huỷ. Thượng nguồn viết thẳng trong `onBannerAdd`; tách ra vì `onBannerUpdate` cũng hẹn lại.\n */\nfunction scheduleAutoClose(\n timeouts: Map<string, ReturnType<typeof setTimeout>>,\n id: string,\n duration: number | undefined,\n onElapsed: () => void\n) {\n const pendingTimeoutId = timeouts.get(id);\n if (pendingTimeoutId !== undefined) {\n clearTimeout(pendingTimeoutId);\n timeouts.delete(id);\n }\n\n if (!duration || duration <= 0) return;\n\n const timeoutId = setTimeout(() => {\n onElapsed();\n timeouts.delete(id);\n }, duration);\n timeouts.set(id, timeoutId);\n}\n\ninterface BannersProps {\n children?: React.ReactNode;\n maxVisible?: number;\n side?: BannerSide;\n strategy?: BannerStrategy;\n container?: Element | DocumentFragment | null;\n}\n\nfunction Banners(props: BannersProps) {\n const { children, maxVisible = 1, side = \"top\", strategy = \"fixed\", container: containerProp } = props;\n\n const instanceId = React.useId();\n const stateRef = useLazyRef<StoreState>(() => ({\n banners: [],\n removing: new Set(),\n heights: new Map()\n }));\n const listenersRef = useLazyRef<Set<() => void>>(() => new Set());\n const timeoutsRef = useLazyRef<Map<string, ReturnType<typeof setTimeout>>>(() => new Map());\n const idCounterRef = React.useRef(0);\n\n const store: Store = React.useMemo(\n () => ({\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n notify: () => {\n for (const listener of listenersRef.current) {\n listener();\n }\n },\n onBannerAdd: (banner) => {\n /*\n * ⚠ Khác thượng nguồn: bản gốc `crypto.randomUUID()` — hàm này CHỈ có trong secure\n * context (https, localhost). Mở app qua http bằng IP mạng nội bộ (máy dev, môi trường\n * nội bộ) thì nó là `undefined` và mọi lần thêm banner đều throw. Id chỉ cần duy nhất\n * trong một `Banners` → bộ đếm, tiền tố `useId()` cho chắc khi có nhiều `Banners`.\n */\n idCounterRef.current += 1;\n const id = `${instanceId}-${idCounterRef.current}`;\n const newBanner: BannerData = { ...banner, id };\n\n const banners = [...stateRef.current.banners];\n insertByPriority(banners, newBanner);\n\n stateRef.current.banners = banners;\n store.notify();\n\n scheduleAutoClose(timeoutsRef.current, id, banner.duration, () => store.onRemovingChange(id, true));\n\n return id;\n },\n onBannerUpdate: (id, patch) => {\n /*\n * ⚠ Khác thượng nguồn (3.1.0): thượng nguồn không có đường ĐỔI một banner đang trong hàng\n * đợi — `Banner` khai báo chỉ biết gỡ ra rồi thêm lại (xem chú thích trong `Banner`). Ở đây\n * đổi TẠI CHỖ: giữ id, giữ vị trí, không gọi `onDismiss`, không phát lại hiệu ứng vào.\n *\n * - `priority` đổi → dời tới đúng chỗ theo luật của `onBannerAdd` (cuối nhóm của mức mới),\n * như thể vừa được thêm với mức đó.\n * - `duration` đổi GIÁ TRỊ → huỷ hẹn cũ và đếm LẠI trọn `duration` mới tính từ lúc đổi (không\n * trừ phần đã trôi — đổi từ \"không tự đóng\" sang 5 giây mà trừ thời gian đã hiện thì banner\n * sẽ biến mất ngay); `0` / `undefined` → thôi tự đóng. Render lại với CÙNG giá trị thì đồng\n * hồ đang chạy giữ nguyên. Banner đang chạy hiệu ứng ra thì không hẹn lại.\n * - Không có gì đổi thật thì không `notify()` — `Banner` gọi hàm này sau mỗi lần các prop ấy\n * đổi, và `useBanners()` của app không nên render lại vì một lần gọi không đổi gì.\n */\n const banners = stateRef.current.banners;\n const index = banners.findIndex((b) => b.id === id);\n const current = banners[index];\n if (!current) return;\n\n const keys = Object.keys(patch) as (keyof BannerUpdate)[];\n if (keys.every((key) => Object.is(current[key], patch[key]))) return;\n\n const next: BannerData = { ...current, ...patch };\n const nextBanners = [...banners];\n if (Object.is(next.priority ?? DEFAULT_BANNER_PRIORITY, current.priority ?? DEFAULT_BANNER_PRIORITY)) {\n nextBanners[index] = next;\n } else {\n nextBanners.splice(index, 1);\n insertByPriority(nextBanners, next);\n }\n stateRef.current.banners = nextBanners;\n\n if (!Object.is(next.duration, current.duration) && !stateRef.current.removing.has(id)) {\n scheduleAutoClose(timeoutsRef.current, id, next.duration, () => store.onRemovingChange(id, true));\n }\n\n store.notify();\n },\n onBannerRemove: (id) => {\n const banner = stateRef.current.banners.find((b) => b.id === id);\n if (!banner) return;\n\n const timeoutId = timeoutsRef.current.get(id);\n if (timeoutId) {\n clearTimeout(timeoutId);\n timeoutsRef.current.delete(id);\n }\n\n const newRemoving = new Set(stateRef.current.removing);\n newRemoving.delete(id);\n stateRef.current.removing = newRemoving;\n\n banner.onDismiss?.();\n stateRef.current.banners = stateRef.current.banners.filter((b) => b.id !== id);\n store.notify();\n },\n onBannersClear: () => {\n for (const timeoutId of timeoutsRef.current.values()) {\n clearTimeout(timeoutId);\n }\n timeoutsRef.current.clear();\n stateRef.current.removing = new Set();\n stateRef.current.heights = new Map();\n stateRef.current.banners = [];\n store.notify();\n },\n onRemovingChange: (id, value) => {\n const newSet = new Set(stateRef.current.removing);\n if (value) {\n newSet.add(id);\n } else {\n newSet.delete(id);\n }\n stateRef.current.removing = newSet;\n store.notify();\n },\n onHeightChange: (id, height) => {\n if (stateRef.current.heights.get(id) === height) return;\n const newHeights = new Map(stateRef.current.heights);\n newHeights.set(id, height);\n stateRef.current.heights = newHeights;\n store.notify();\n },\n onHeightRemove: (id) => {\n if (!stateRef.current.heights.has(id)) return;\n const newHeights = new Map(stateRef.current.heights);\n newHeights.delete(id);\n stateRef.current.heights = newHeights;\n store.notify();\n }\n }),\n [instanceId, stateRef, listenersRef, timeoutsRef]\n );\n\n const banners = useStore(store, (state) => state.banners);\n const heights = useStore(store, (state) => state.heights);\n const visibleBanners = banners.slice(0, maxVisible);\n\n /*\n * ⚠ Khác thượng nguồn: bản gốc đọc `globalThis.document?.body` NGAY TRONG render. Ở đây chỉ đọc\n * sau khi mount (cùng khuôn với `ActionBar`) — lượt render phía server và lượt hydrate không\n * chạm `document`.\n */\n const [mounted, setMounted] = React.useState(false);\n useIsomorphicLayoutEffect(() => {\n setMounted(true);\n }, []);\n\n const withPortal = strategy === \"fixed\" || strategy === \"absolute\";\n const container = withPortal ? (containerProp ?? (mounted ? globalThis.document?.body : null) ?? null) : null;\n\n const totalHeight = React.useMemo(() => {\n let total = 0;\n for (const banner of visibleBanners) {\n total += heights.get(banner.id) ?? 0;\n }\n return total;\n }, [visibleBanners, heights]);\n\n const bannerContainer = visibleBanners.length > 0 && (\n <div\n data-slot=\"banner-container\"\n data-side={side}\n data-strategy={strategy}\n className={cn(\n \"pointer-events-none right-0 left-0 isolate z-50\",\n /*\n * ⚠ Khác thượng nguồn: bản gốc ghi `transition` thẳng vào `style`, nên lớp\n * `motion-reduce:transition-none` của từng banner KHÔNG BAO GIỜ thắng được (style nội tuyến\n * luôn thắng lớp) — người bật \"giảm chuyển động\" vẫn thấy banner trượt. Nay thời lượng đi\n * qua `--banner-duration`, bằng 0 khi `prefers-reduced-motion`; banner con thừa kế biến này.\n */\n \"motion-reduce:[--banner-duration:0ms]\",\n strategy === \"fixed\" && \"fixed\",\n strategy === \"static\" && \"relative\",\n strategy === \"sticky\" && \"sticky\",\n strategy === \"absolute\" && \"absolute\",\n side === \"top\" ? \"top-0\" : \"bottom-0\"\n )}\n style={{\n height: totalHeight > 0 ? totalHeight : \"auto\",\n transition: `height var(--banner-duration, ${BANNER_ANIMATION_DURATION}ms) cubic-bezier(0.32, 0.72, 0, 1)`\n }}\n >\n {visibleBanners.map((banner, index) => (\n <BannerImpl key={banner.id} banner={banner} side={side} index={index} />\n ))}\n </div>\n );\n\n return (\n <StoreContext.Provider value={store}>\n {strategy === \"static\" || strategy === \"sticky\" ? (\n <>\n {side === \"top\" && bannerContainer}\n {children}\n {side === \"bottom\" && bannerContainer}\n </>\n ) : (\n <>\n {children}\n {container && bannerContainer && ReactDOM.createPortal(bannerContainer, container)}\n </>\n )}\n </StoreContext.Provider>\n );\n}\n\nfunction useBanners() {\n const store = useStoreContext(\"useBanners\");\n const banners = useStore(store, (state) => state.banners);\n\n return React.useMemo(\n () => ({\n onBannerAdd: store.onBannerAdd,\n onBannerRemove: store.onBannerRemove,\n onBannersClear: store.onBannersClear,\n banners\n }),\n [store, banners]\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn: bản gốc tô bằng bảng màu mặc định của Tailwind (`bg-blue-50\n * text-blue-900`, `bg-red-50`… kèm `dark:`) — đi vòng qua hợp đồng token nên không đổi theo bảng\n * tương phản cao. Ở đây dùng ĐÚNG bộ lớp của `Alert` (alert.tsx) để Banner và Alert cùng tông\n * trông như một họ: nền `bg-card`, chữ + icon ở bậc `-ink` của tone. `Alert` chưa có `info` /\n * `success`; hai tông đó theo cùng khuôn với `warning` / `destructive` của nó.\n *\n * Nền ĐẶC `bg-card` chứ không tint `bg-<tone>/15`: ở chế độ `fixed`/`absolute`, banner nằm ĐÈ lên\n * nội dung trang — một nền trong suốt một phần sẽ cho chữ trang lộ xuyên qua chữ banner.\n *\n * Mô tả (`BannerDescription`) giữ `opacity-90` của bản gốc — trên nền đặc nó tương đương đúng\n * `text-<tone>-ink/90` của `AlertDescription`. Cổng không đo được `opacity`, nên số đo tay (trên\n * `--card`, tiêu đề / mô tả 90%):\n *\n * bảng tương phản cao light dark\n * info 6,68 / 5,31 9,54 / 7,92 ✓\n * success 6,52 / 5,20 10,19 / 8,44 ✓\n * warning 6,51 / 5,17 11,60 / 9,56 ✓\n * destructive 7,15 / 5,77 6,42 / 5,41 ✓\n * bảng mặc định (nợ token đã chấp nhận, cùng mức với `Alert`):\n * destructive 4,38 / 3,81 (light) · 4,06 / 3,50 (dark) · info dark 4,28 / 3,71\n */\nconst bannerVariants = cva(\n \"pointer-events-auto relative flex w-full items-center gap-3 border-b px-4 py-3 text-sm motion-reduce:transition-none\",\n {\n variants: {\n variant: {\n default: \"bg-card text-card-foreground\",\n info: \"bg-card text-info-ink\",\n success: \"bg-card text-success-ink\",\n warning: \"bg-card text-warning-ink\",\n destructive: \"bg-card text-destructive-ink\"\n }\n },\n defaultVariants: {\n variant: \"default\"\n }\n }\n);\n\n/*\n * ⚠ Khác thượng nguồn (3.1.0): TRẢ FOCUS khi banner rời DOM — cùng khuôn, cùng lý do với\n * `ActionBar` (3.0.1). Banner đóng là cây của nó bị gỡ: `Banner` đứng riêng gỡ ngay, banner trong\n * hàng đợi gỡ sau hiệu ứng ra (`BANNER_ANIMATION_DURATION`). Người dùng bàn phím vừa bấm Enter/Space\n * trên `BannerClose` vì thế thấy focus rơi xuống `<body>`, và Tab kế tiếp chạy lại từ đầu trang\n * (WCAG 2.4.3). Bản gốc để rơi như vậy.\n *\n * Ghi phần tử giữ focus NGAY TRƯỚC khi focus vào banner (`relatedTarget` lúc focus đi vào từ ngoài),\n * rồi trả về đó nếu lúc banner rời DOM focus còn ở trong nó. Trong hàng đợi, focus nằm trong\n * `BannerImpl` suốt hiệu ứng ra — nên hook gắn ở `BannerImpl` và trả lúc NÓ unmount, không phải lúc\n * bấm đóng. Cùng đường ấy phủ luôn mọi cách rời DOM khác khi focus đang ở trong: hết `duration`,\n * `open={false}`, `onBannerRemove`, `onBannersClear`, bị banner ưu tiên cao hơn đẩy ra ngoài\n * `maxVisible`, `Banner` khai báo unmount (đổi route).\n *\n * App tự đưa focus đi thì app thắng: trong `onOpenChange` / `onDismiss` (chạy khi banner còn trong\n * DOM — `onBlur` thấy focus ra ngoài nên không còn gì để trả), trong effect, hay sau đó. Không có\n * phần tử để trả (focus vào banner không từ phần tử nào, hoặc phần tử ấy đã bị gỡ) thì gói KHÔNG\n * đoán: app đưa focus đi trong `onOpenChange` (banner thêm bằng `onBannerAdd`: trong `onDismiss`).\n */\nfunction useFocusReturn<T extends HTMLElement>(\n active: boolean,\n onFocusProp?: React.FocusEventHandler<T>,\n onBlurProp?: React.FocusEventHandler<T>\n) {\n const returnFocusRef = React.useRef<HTMLElement | null>(null);\n const focusInsideRef = React.useRef(false);\n\n const onFocus = React.useCallback(\n (event: React.FocusEvent<T>) => {\n onFocusProp?.(event);\n\n /*\n * Chỉ ghi khi focus ĐI VÀO từ ngoài. Menu mở từ một nút trong banner là con React nhưng nằm ở\n * portal khác: focus từ menu quay về nút có `relatedTarget` ngoài banner (theo DOM), và không\n * được ghi đè phần tử thật sự đứng trước banner.\n */\n const from = event.relatedTarget;\n if (!focusInsideRef.current && from instanceof HTMLElement && !event.currentTarget.contains(from)) {\n returnFocusRef.current = from;\n }\n focusInsideRef.current = true;\n },\n [onFocusProp]\n );\n\n const onBlur = React.useCallback(\n (event: React.FocusEvent<T>) => {\n onBlurProp?.(event);\n\n const to = event.relatedTarget;\n if (!(to instanceof Node) || !event.currentTarget.contains(to)) {\n focusInsideRef.current = false;\n }\n },\n [onBlurProp]\n );\n\n useIsomorphicLayoutEffect(() => {\n if (!active) return;\n\n return () => {\n const returnTarget = returnFocusRef.current;\n const hadFocus = focusInsideRef.current;\n returnFocusRef.current = null;\n focusInsideRef.current = false;\n if (!hadFocus || !returnTarget) return;\n\n /*\n * Cleanup này chạy GIỮA lượt commit, lúc React tạm tắt hệ sự kiện (nên `blur` của phần tử bị\n * gỡ không tới được `onBlur` ở trên — cờ `focusInsideRef` còn đúng), và focus ngay tại đây thì\n * `onFocus` của phần tử nhận focus không chạy. Đợi commit xong rồi mới trả, và chỉ khi focus\n * vẫn đang rơi. Effect của app nằm trong CHÍNH lượt commit này khi nó đồng bộ (bấm chuột, gõ\n * phím) thì chạy ngay cuối commit, TRƯỚC microtask này, và thắng luôn; effect ở một lượt commit\n * SAU (vd state app đổi trong `onOpenChange` của banner trong hàng đợi — lời gọi ấy tới từ hẹn\n * giờ của hiệu ứng ra, không từ sự kiện) thì chạy sau và đè lên. Đằng nào app cũng thắng.\n */\n queueMicrotask(() => {\n const ownerDocument = returnTarget.ownerDocument;\n const activeElement = ownerDocument.activeElement;\n if (activeElement && activeElement !== ownerDocument.body && activeElement.isConnected) return;\n if (!returnTarget.isConnected) return;\n returnTarget.focus({ preventScroll: true });\n });\n };\n }, [active]);\n\n return { onFocus, onBlur };\n}\n\ninterface BannerImplProps {\n banner: BannerData;\n side: BannerSide;\n index: number;\n}\n\nfunction BannerImpl(props: BannerImplProps) {\n const { banner, side, index } = props;\n\n const store = useStoreContext(\"BannerImpl\");\n const removing = useStore(store, (state) => state.removing.has(banner.id));\n const banners = useStore(store, (state) => state.banners);\n const heights = useStore(store, (state) => state.heights);\n\n const [mounted, setMounted] = React.useState(false);\n const bannerRef = React.useRef<HTMLDivElement>(null);\n const offsetBeforeRemoveRef = React.useRef(0);\n\n const offset = React.useMemo(() => {\n let total = 0;\n for (const b of banners) {\n if (b.id === banner.id) break;\n total += heights.get(b.id) ?? 0;\n }\n return total;\n }, [banners, heights, banner.id]);\n\n if (!removing) {\n offsetBeforeRemoveRef.current = offset;\n }\n\n React.useEffect(() => {\n const frame = requestAnimationFrame(() => setMounted(true));\n return () => cancelAnimationFrame(frame);\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n const node = bannerRef.current;\n if (!node || removing) return;\n store.onHeightChange(banner.id, node.getBoundingClientRect().height);\n\n /*\n * ⚠ Khác thượng nguồn (3.1.0): đo LẠI mỗi khi cỡ banner đổi. Bản gốc đo một lần lúc mount — đủ\n * cho nó vì `Banner` khai báo gỡ-rồi-thêm (tức mount lại, đo lại) ở mỗi lần render. Nay nội dung\n * đổi TẠI CHỖ (xem `Banner`), nên chữ dài ra một dòng mà không đo lại thì `Banners` giữ chiều cao\n * cũ: banner kế tiếp đè lên nó, và ở `strategy=\"static\"` khung đẩy nội dung trang sai một đoạn.\n * Kèm theo: khung nhìn hẹp lại làm chữ xuống dòng cũng được đo (bản gốc thì không). Đang chạy\n * hiệu ứng ra thì thôi theo dõi — chiều cao của nó đã được gỡ khỏi tổng (`onHeightRemove`).\n */\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(() => {\n store.onHeightChange(banner.id, node.getBoundingClientRect().height);\n });\n observer.observe(node);\n return () => observer.disconnect();\n }, [store, banner.id, removing]);\n\n React.useEffect(() => {\n if (!removing) return;\n store.onHeightRemove(banner.id);\n const timeoutId = setTimeout(() => store.onBannerRemove(banner.id), BANNER_ANIMATION_DURATION);\n return () => clearTimeout(timeoutId);\n }, [removing, store, banner.id]);\n\n const onClose = React.useCallback(() => store.onRemovingChange(banner.id, true), [store, banner.id]);\n\n const onRemove = React.useCallback(() => store.onBannerRemove(banner.id), [store, banner.id]);\n\n // Trả focus lúc banner rời DOM — tức lúc `BannerImpl` unmount, SAU hiệu ứng ra. Xem `useFocusReturn`.\n const { onFocus, onBlur } = useFocusReturn<HTMLDivElement>(true);\n\n const dismissible = banner.dismissible ?? DEFAULT_BANNER_DISMISSIBLE;\n\n const contextValue = React.useMemo<BannerContextValue>(\n () => ({ id: banner.id, variant: banner.variant, dismissible, onClose }),\n [banner.id, banner.variant, dismissible, onClose]\n );\n\n const renderProps = React.useMemo<BannerRenderProps>(\n () => ({\n id: banner.id,\n variant: banner.variant,\n dismissible,\n onClose,\n onRemove\n }),\n [banner.id, banner.variant, dismissible, onClose, onRemove]\n );\n\n const currentOffset = removing ? offsetBeforeRemoveRef.current : offset;\n const isTop = side === \"top\";\n\n function getTransform() {\n if (!mounted) return isTop ? \"translateY(-100%)\" : \"translateY(100%)\";\n if (removing) {\n return isTop ? `translateY(calc(${currentOffset}px - 100%))` : `translateY(calc(-${currentOffset}px + 100%))`;\n }\n return isTop ? `translateY(${currentOffset}px)` : `translateY(-${currentOffset}px)`;\n }\n\n const opacityDuration = removing ? BANNER_ANIMATION_DURATION / 2 : BANNER_ANIMATION_DURATION;\n\n return (\n <BannerContext.Provider value={contextValue}>\n <div\n role=\"status\"\n aria-live=\"polite\"\n data-slot=\"queued-banner\"\n data-state={removing ? \"closed\" : \"open\"}\n data-mounted={mounted}\n data-removed={removing}\n data-side={side}\n data-front={index === 0}\n data-index={index}\n ref={bannerRef}\n className={bannerVariants({ variant: banner.variant })}\n style={{\n position: \"absolute\",\n [isTop ? \"top\" : \"bottom\"]: 0,\n left: 0,\n right: 0,\n zIndex: removing ? 0 : 50 - index,\n transform: getTransform(),\n opacity: mounted && !removing ? 1 : 0,\n // Thời lượng qua `--banner-duration` (0 khi giảm chuyển động) — xem chú thích ở `Banners`.\n transition: `transform var(--banner-duration, ${BANNER_ANIMATION_DURATION}ms) cubic-bezier(0.32, 0.72, 0, 1), opacity var(--banner-duration, ${opacityDuration}ms) ease`\n }}\n onFocus={onFocus}\n onBlur={onBlur}\n >\n {typeof banner.content === \"function\" ? banner.content(renderProps) : banner.content}\n </div>\n </BannerContext.Provider>\n );\n}\n\n/*\n * Nguồn `children` MỚI NHẤT của một `Banner` khai báo, cho banner của nó trong hàng đợi đọc.\n *\n * ⚠ Khác thượng nguồn (3.1.0): nội dung KHÔNG đi qua store của `Banners`. `children` là JSX mới ở mỗi\n * lần cha render, nên đẩy nó vào store (`onBannerUpdate`) là `notify()` ở MỖI lần render — mọi\n * `useBanners()` render lại theo. Một component vừa đọc `useBanners()` vừa render `<Banner>` thì thành\n * vòng lặp vô tận: store đổi → nó render → `children` mới → store đổi… Thay vào đó, mục trong hàng đợi\n * giữ MỘT phần tử cố định (`DeclaredBannerChildren`) tự đăng ký vào nguồn này, và chỉ đúng phần nội\n * dung ấy render lại khi `children` đổi. `banners[i].content` của một banner khai báo vì thế là phần tử\n * đó — render ở đâu cũng ra nội dung mới nhất.\n */\ninterface ChildrenSource {\n get: () => React.ReactNode;\n set: (children: React.ReactNode) => void;\n subscribe: (listener: () => void) => () => void;\n}\n\nfunction createChildrenSource(initialChildren: React.ReactNode): ChildrenSource {\n let children = initialChildren;\n const listeners = new Set<() => void>();\n\n return {\n get: () => children,\n set: (nextChildren) => {\n if (Object.is(children, nextChildren)) return;\n children = nextChildren;\n for (const listener of listeners) {\n listener();\n }\n },\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n }\n };\n}\n\nfunction DeclaredBannerChildren(props: { source: ChildrenSource }) {\n const { source } = props;\n return React.useSyncExternalStore(source.subscribe, source.get, source.get);\n}\n\ninterface BannerProps extends DivProps, VariantProps<typeof bannerVariants> {\n open?: boolean;\n defaultOpen?: boolean;\n /**\n * Báo `false` khi banner đóng THẬT: `BannerClose`; trong `Banners` thêm hết `duration` hay hàng đợi\n * gỡ nó. Không báo khi render lại, khi unmount, hay khi chính app đặt `open={false}`.\n *\n * Focus: nếu lúc banner rời DOM focus còn ở trong nó, gói trả focus về phần tử giữ focus ngay trước\n * khi vào banner. Muốn đưa focus nơi khác (vd tiêu đề trang) thì làm ngay ở đây — app luôn thắng.\n * Không có phần tử ấy (hoặc nó đã bị gỡ) thì gói không đoán, focus rơi về `<body>`.\n */\n onOpenChange?: (open: boolean) => void;\n onDismiss?: () => void;\n priority?: number;\n duration?: number;\n dismissible?: boolean;\n}\n\nfunction Banner(props: BannerProps) {\n const {\n className,\n variant = \"default\",\n open: openProp,\n defaultOpen = true,\n onOpenChange,\n onDismiss,\n priority,\n duration,\n dismissible = DEFAULT_BANNER_DISMISSIBLE,\n children,\n asChild,\n onFocus: onFocusProp,\n onBlur: onBlurProp,\n ...rootProps\n } = props;\n\n const store = React.useContext(StoreContext);\n\n const isInsideStore = store !== null;\n const isControlled = openProp !== undefined;\n\n const openRef = useLazyRef(() => openProp ?? defaultOpen);\n const listenersRef = useLazyRef<Set<() => void>>(() => new Set());\n const bannerIdRef = React.useRef<string | null>(null);\n const onDismissRef = useAsRef(onDismiss);\n const onOpenChangeRef = useAsRef(onOpenChange);\n const isControlledRef = useAsRef(isControlled);\n const queuedDataRef = useAsRef<BannerUpdate>({ variant: variant ?? undefined, priority, dismissible, duration });\n const childrenSourceRef = useLazyRef(() => createChildrenSource(children));\n\n if (isControlled) {\n openRef.current = openProp;\n }\n\n const subscribe = React.useCallback(\n (cb: () => void) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n [listenersRef]\n );\n\n const getSnapshot = React.useCallback(() => openRef.current, [openRef]);\n\n const open = React.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n\n // Nội dung mới nhất cho banner trong hàng đợi — xem `ChildrenSource`. Chạy sau MỖI commit: `children`\n // đổi thì chỉ phần nội dung trong hàng đợi render lại, trước lượt sơn kế tiếp.\n useIsomorphicLayoutEffect(() => {\n childrenSourceRef.current.set(children);\n });\n\n // Đổi TẠI CHỖ mục đang trong hàng đợi khi các prop dữ liệu đổi — xem `onBannerUpdate`. Chưa đăng ký\n // (lượt mount, hay đang đóng) thì thôi: effect đăng ký bên dưới đọc giá trị mới nhất qua ref.\n useIsomorphicLayoutEffect(() => {\n const id = bannerIdRef.current;\n if (!store || id === null) return;\n store.onBannerUpdate(id, { variant: variant ?? undefined, priority, dismissible, duration });\n }, [store, variant, priority, dismissible, duration]);\n\n React.useEffect(() => {\n if (!store || !open) return;\n\n /*\n * ⚠ Khác thượng nguồn — ba lỗi của `<Banner>` đặt trong `<Banners>`:\n *\n * 1. Hàm dọn của effect này gỡ banner bằng `onBannerRemove`, mà hàm đó GỌI `onDismiss`. Bản\n * gốc vì thế báo \"người dùng đã đóng\" (`onDismiss` + `onOpenChange(false)`) mỗi khi effect\n * chạy lại — ở bản gốc là MỖI LẦN cha re-render (xem 3) — và cả khi unmount (đổi route) hay\n * `open={false}`. Banner tự đóng, và app lưu \"đã tắt thông báo\" cho một lần người dùng chưa\n * hề bấm. Cờ `isCleaningUp` chặn đúng đường đó; đóng thật (nút X, hết `duration`) vẫn báo\n * như cũ, đúng một lần.\n * 2. Ở chế độ không kiểm soát, đóng qua hàng đợi không hạ `open` của chính `Banner`, nên lần\n * re-render kế tiếp thêm LẠI banner vừa đóng. Nay hạ `open` như `onClose` bên dưới.\n * 3. (3.1.0) Deps của bản gốc có `children` (và variant/priority/dismissible/duration):\n * `children` là JSX mới ở mỗi lần cha render, nên MỖI lần render banner rời hàng đợi rồi vào\n * lại với id mới — phát lại hiệu ứng vào, xuống CUỐI nhóm ưu tiên, `duration` đếm lại từ\n * đầu, state bên trong nội dung mất, focus bên trong rơi. Nay đăng ký MỘT lần cho mỗi chu kỳ\n * mở (deps chỉ còn `store`, `open` và các ref ổn định), đọc dữ liệu mới nhất qua ref; thay\n * đổi sau đó đi qua hai effect bên trên. Đóng rồi mở lại (`open` false → true) là một chu kỳ\n * mới: thêm lại, id mới.\n * Hệ quả: mục đã rời hàng đợi mà `open` vẫn `true` — app kiểm soát `open` nhưng bỏ qua\n * `onOpenChange(false)`, hay sau `onBannersClear()` — ở ngoài cho tới khi `open` qua `false`\n * rồi `true`. Bản cũ đưa nó về ở lần render kế tiếp, tức vào một lúc không ai định trước.\n */\n let isCleaningUp = false;\n\n const id = store.onBannerAdd({\n ...queuedDataRef.current,\n content: <DeclaredBannerChildren source={childrenSourceRef.current} />,\n onDismiss: () => {\n if (isCleaningUp) return;\n if (!isControlledRef.current) {\n openRef.current = false;\n for (const listener of listenersRef.current) {\n listener();\n }\n }\n onDismissRef.current?.();\n onOpenChangeRef.current?.(false);\n }\n });\n bannerIdRef.current = id;\n\n return () => {\n isCleaningUp = true;\n bannerIdRef.current = null;\n store.onBannerRemove(id);\n };\n }, [\n store,\n open,\n queuedDataRef,\n childrenSourceRef,\n isControlledRef,\n openRef,\n listenersRef,\n onDismissRef,\n onOpenChangeRef\n ]);\n\n const onClose = React.useCallback(() => {\n if (!isControlled) {\n openRef.current = false;\n for (const listener of listenersRef.current) {\n listener();\n }\n }\n onOpenChangeRef.current?.(false);\n }, [isControlled, openRef, listenersRef, onOpenChangeRef]);\n\n // Chỉ `Banner` đứng riêng tự vẽ phần tử gốc; trong `Banners` thì `BannerImpl` lo — xem `useFocusReturn`.\n const { onFocus, onBlur } = useFocusReturn(open && !isInsideStore, onFocusProp, onBlurProp);\n\n const contextValue = React.useMemo<BannerContextValue>(\n () => ({\n variant,\n dismissible,\n onClose\n }),\n [variant, dismissible, onClose]\n );\n\n if (!open || isInsideStore) return null;\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n return (\n <BannerContext.Provider value={contextValue}>\n <RootPrimitive\n role=\"status\"\n aria-live=\"polite\"\n data-slot=\"banner\"\n data-state=\"open\"\n className={cn(bannerVariants({ variant, className }))}\n {...rootProps}\n onFocus={onFocus}\n onBlur={onBlur}\n >\n {children}\n </RootPrimitive>\n </BannerContext.Provider>\n );\n}\n\nfunction BannerIcon(props: DivProps) {\n const { className, asChild, ...iconProps } = props;\n\n const IconPrimitive = asChild ? Slot : \"div\";\n\n return (\n <IconPrimitive\n data-slot=\"banner-icon\"\n className={cn(\"flex shrink-0 items-center [&>svg]:size-4\", className)}\n {...iconProps}\n />\n );\n}\n\nfunction BannerContent(props: DivProps) {\n const { className, asChild, ...contentProps } = props;\n\n const ContentPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ContentPrimitive\n data-slot=\"banner-content\"\n className={cn(\"flex min-w-0 flex-1 flex-col gap-1\", className)}\n {...contentProps}\n />\n );\n}\n\nfunction BannerTitle(props: React.ComponentProps<\"div\">) {\n const { className, ...titleProps } = props;\n\n return <div data-slot=\"banner-title\" className={cn(\"text-sm leading-none font-medium\", className)} {...titleProps} />;\n}\n\nfunction BannerDescription(props: React.ComponentProps<\"div\">) {\n const { className, ...descriptionProps } = props;\n\n return <div data-slot=\"banner-description\" className={cn(\"text-xs opacity-90\", className)} {...descriptionProps} />;\n}\n\nfunction BannerActions(props: DivProps) {\n const { className, asChild, ...actionsProps } = props;\n\n const ActionsPrimitive = asChild ? Slot : \"div\";\n\n return (\n <ActionsPrimitive\n data-slot=\"banner-actions\"\n className={cn(\"flex items-center gap-2\", className)}\n {...actionsProps}\n />\n );\n}\n\ntype BannerCloseProps = React.ComponentProps<typeof Button>;\n\n/**\n * Nút đóng banner. Không truyền `children` thì vẽ icon X.\n *\n * ⚠ Khác thượng nguồn: có tên cho trình đọc màn hình — `aria-label` mặc định lấy khoá\n * `closeLabel` của namespace `banner` (`ComitorLocaleProvider`), ghi đè bằng chính prop\n * `aria-label`. Bản gốc là `Button` chỉ chứa icon X, tức một nút KHÔNG TÊN. Nếu `children` là\n * CHỮ nhìn thấy được thì truyền `aria-label` trùng với chữ đó (WCAG 2.5.3), hoặc\n * `aria-label={undefined}`.\n */\nfunction BannerClose(props: BannerCloseProps) {\n const { onClick: onClickProp, disabled, children, ...closeProps } = props;\n\n const { dismissible = DEFAULT_BANNER_DISMISSIBLE, onClose } = useBannerContext(\"BannerClose\");\n const t = useComitorMessages(\"banner\");\n\n const isDisabled = disabled ?? !dismissible;\n\n const onClick = React.useCallback(\n (event: React.MouseEvent<CloseElement>) => {\n onClickProp?.(event);\n if (event.defaultPrevented || isDisabled) return;\n onClose?.();\n },\n [onClickProp, isDisabled, onClose]\n );\n\n return (\n <Button\n aria-label={t.closeLabel}\n data-slot=\"banner-close\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={onClick}\n disabled={isDisabled}\n {...closeProps}\n >\n {children ?? <X aria-hidden=\"true\" className=\"size-3.5\" />}\n </Button>\n );\n}\n\nexport {\n Banner,\n BannerActions,\n BannerClose,\n type BannerCloseProps,\n BannerContent,\n type BannerData,\n BannerDescription,\n BannerIcon,\n type BannerProps,\n type BannerRenderProps,\n Banners,\n type BannersProps,\n BannerTitle,\n type BannerVariant,\n useBanner,\n useBanners\n};\n"],"mappings":"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","names":["banners"]}
@@ -10,6 +10,9 @@ declare const REGEX_CACHE: {
10
10
  readonly hashPattern: RegExp;
11
11
  readonly currencyAtEnd: RegExp;
12
12
  readonly percentageChars: RegExp;
13
+ readonly percentageCommaChars: RegExp;
14
+ readonly decimalMark: RegExp;
15
+ readonly decimalMarks: RegExp;
13
16
  readonly phone: RegExp;
14
17
  readonly ssn: RegExp;
15
18
  readonly zipCode: RegExp;
@@ -61,7 +64,21 @@ declare function applyCurrencyMask(opts: {
61
64
  currency?: string;
62
65
  locale?: string;
63
66
  }): string;
64
- declare function applyPercentageMask(value: string): string;
67
+ /**
68
+ * Giá trị thô dạng chuẩn (`"12.5"`) → chuỗi hiển thị (`"12.5%"`).
69
+ *
70
+ * ⚠ Khác thượng nguồn (3.1.0): nhận thêm `locale` (tuỳ chọn) và vẽ dấu thập phân của nó — `vi` →
71
+ * `12,5%`. Không truyền thì `.` như thượng nguồn. Chỉ đổi DẤU: không nhóm nghìn, và `%` luôn dính
72
+ * ngay sau số (`Intl` của de-DE viết `12,5 %`) — để con trỏ và nút xoá chỉ phải biết một dáng chuỗi.
73
+ */
74
+ declare function applyPercentageMask(value: string, opts?: {
75
+ locale?: string;
76
+ }): string;
77
+ /** Ô phần trăm đang đọc và vẽ dấu `,` — `mask-input.tsx` dùng để chọn cách đặt con trỏ. */
78
+ declare function isDecimalCommaPercentage(opts: {
79
+ mask: MaskPatternKey | MaskPattern | undefined;
80
+ locale?: string;
81
+ }): boolean;
65
82
  declare function getUnmaskedValue(opts: {
66
83
  value: string;
67
84
  currency?: string;
@@ -86,6 +103,21 @@ declare function getCurrencyCaretPosition(opts: {
86
103
  oldValue?: string;
87
104
  previousUnmasked?: string;
88
105
  }): number;
106
+ /**
107
+ * Con trỏ sau khi ô phần trăm ở chế độ dấu phẩy (`isDecimalCommaPercentage`) vẽ lại chuỗi.
108
+ *
109
+ * ⚠ Khác thượng nguồn (3.1.0) — hàm MỚI. `getCurrencyCaretPosition` chỉ đếm CHỮ SỐ trước con trỏ,
110
+ * mà ở đây mặt nạ còn đổi chính dấu thập phân (phím `.` của bàn phím số hiện thành `,`): đếm chữ số
111
+ * thì con trỏ rơi về TRƯỚC dấu vừa gõ, và chữ số kế tiếp chen vào phần nguyên — `12` `.` `5` ra
112
+ * `125,%`. Đây đúng là cái bẫy làm cách chữa tạm "`transform` đổi `,` thành `.`" không dùng được.
113
+ * Ở đây đếm cả dấu thập phân ĐƯỢC GIỮ (dấu đầu tiên — luật của `transform`), nên con trỏ đứng sau nó.
114
+ * Chế độ dấu chấm (en…) vẫn đi `getCurrencyCaretPosition` như cũ.
115
+ */
116
+ declare function getPercentageCaretPosition(opts: {
117
+ newValue: string;
118
+ oldValue: string;
119
+ oldCursorPosition: number;
120
+ }): number;
89
121
  declare function getPatternCaretPosition(opts: {
90
122
  newValue: string;
91
123
  maskPattern: MaskPattern;
@@ -95,4 +127,4 @@ declare function getPatternCaretPosition(opts: {
95
127
  previousUnmasked?: string;
96
128
  }): number;
97
129
 
98
- export { CURRENCY_PERCENTAGE_SYMBOLS, DEFAULT_CURRENCY, MASK_PATTERNS, type MaskPattern, type MaskPatternKey, NUMERIC_MASK_PATTERNS, REGEX_CACHE, type TransformOptions, applyCurrencyMask, applyMask, applyPercentageMask, fromUnmaskedIndex, getCurrencyCaretPosition, getPatternCaretPosition, getUnmaskedValue, isCurrencyAtEnd, isCurrencyMask, toUnmaskedIndex };
130
+ export { CURRENCY_PERCENTAGE_SYMBOLS, DEFAULT_CURRENCY, MASK_PATTERNS, type MaskPattern, type MaskPatternKey, NUMERIC_MASK_PATTERNS, REGEX_CACHE, type TransformOptions, applyCurrencyMask, applyMask, applyPercentageMask, fromUnmaskedIndex, getCurrencyCaretPosition, getPatternCaretPosition, getPercentageCaretPosition, getUnmaskedValue, isCurrencyAtEnd, isCurrencyMask, isDecimalCommaPercentage, toUnmaskedIndex };