@comitor/ui 1.11.0 → 2.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 (176) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +23 -7
  3. package/dist/components/ui/action-bar-constants.d.ts +31 -0
  4. package/dist/components/ui/action-bar-constants.js +9 -0
  5. package/dist/components/ui/action-bar-constants.js.map +1 -0
  6. package/dist/components/ui/action-bar.d.ts +67 -0
  7. package/dist/components/ui/action-bar.js +522 -0
  8. package/dist/components/ui/action-bar.js.map +1 -0
  9. package/dist/components/ui/badge-overflow.d.ts +30 -0
  10. package/dist/components/ui/badge-overflow.js +179 -0
  11. package/dist/components/ui/badge-overflow.js.map +1 -0
  12. package/dist/components/ui/banner.d.ts +79 -0
  13. package/dist/components/ui/banner.js +518 -0
  14. package/dist/components/ui/banner.js.map +1 -0
  15. package/dist/components/ui/circular-progress.d.ts +50 -0
  16. package/dist/components/ui/circular-progress.js +266 -0
  17. package/dist/components/ui/circular-progress.js.map +1 -0
  18. package/dist/components/ui/cropper-constants.d.ts +34 -0
  19. package/dist/components/ui/cropper-constants.js +10 -0
  20. package/dist/components/ui/cropper-constants.js.map +1 -0
  21. package/dist/components/ui/cropper.d.ts +106 -0
  22. package/dist/components/ui/cropper.js +1269 -0
  23. package/dist/components/ui/cropper.js.map +1 -0
  24. package/dist/components/ui/drawer.d.ts +1 -1
  25. package/dist/components/ui/drawer.js +2 -2
  26. package/dist/components/ui/drawer.js.map +1 -1
  27. package/dist/components/ui/kanban-constants.d.ts +61 -0
  28. package/dist/components/ui/kanban-constants.js +21 -0
  29. package/dist/components/ui/kanban-constants.js.map +1 -0
  30. package/dist/components/ui/kanban.d.ts +72 -0
  31. package/dist/components/ui/kanban.js +787 -0
  32. package/dist/components/ui/kanban.js.map +1 -0
  33. package/dist/components/ui/key-value-constants.d.ts +33 -0
  34. package/dist/components/ui/key-value-constants.js +11 -0
  35. package/dist/components/ui/key-value-constants.js.map +1 -0
  36. package/dist/components/ui/key-value.d.ts +96 -0
  37. package/dist/components/ui/key-value.js +641 -0
  38. package/dist/components/ui/key-value.js.map +1 -0
  39. package/dist/components/ui/mask-input-constants.d.ts +99 -0
  40. package/dist/components/ui/mask-input-constants.js +601 -0
  41. package/dist/components/ui/mask-input-constants.js.map +1 -0
  42. package/dist/components/ui/mask-input.d.ts +36 -0
  43. package/dist/components/ui/mask-input.js +558 -0
  44. package/dist/components/ui/mask-input.js.map +1 -0
  45. package/dist/components/ui/mention.d.ts +31 -0
  46. package/dist/components/ui/mention.js +128 -0
  47. package/dist/components/ui/mention.js.map +1 -0
  48. package/dist/components/ui/phone-input-constants.d.ts +54 -0
  49. package/dist/components/ui/phone-input-constants.js +9 -0
  50. package/dist/components/ui/phone-input-constants.js.map +1 -0
  51. package/dist/components/ui/phone-input.d.ts +85 -0
  52. package/dist/components/ui/phone-input.js +743 -0
  53. package/dist/components/ui/phone-input.js.map +1 -0
  54. package/dist/components/ui/rating.d.ts +56 -0
  55. package/dist/components/ui/rating.js +646 -0
  56. package/dist/components/ui/rating.js.map +1 -0
  57. package/dist/components/ui/responsive-dialog.d.ts +35 -0
  58. package/dist/components/ui/responsive-dialog.js +193 -0
  59. package/dist/components/ui/responsive-dialog.js.map +1 -0
  60. package/dist/components/ui/scroll-spy.d.ts +42 -0
  61. package/dist/components/ui/scroll-spy.js +334 -0
  62. package/dist/components/ui/scroll-spy.js.map +1 -0
  63. package/dist/components/ui/stepper-constants.d.ts +29 -0
  64. package/dist/components/ui/stepper-constants.js +9 -0
  65. package/dist/components/ui/stepper-constants.js.map +1 -0
  66. package/dist/components/ui/stepper.d.ts +80 -0
  67. package/dist/components/ui/stepper.js +902 -0
  68. package/dist/components/ui/stepper.js.map +1 -0
  69. package/dist/components/ui/tags-input.d.ts +41 -0
  70. package/dist/components/ui/tags-input.js +127 -0
  71. package/dist/components/ui/tags-input.js.map +1 -0
  72. package/dist/components/ui/time-picker-constants.d.ts +48 -0
  73. package/dist/components/ui/time-picker-constants.js +14 -0
  74. package/dist/components/ui/time-picker-constants.js.map +1 -0
  75. package/dist/components/ui/time-picker.d.ts +115 -0
  76. package/dist/components/ui/time-picker.js +1620 -0
  77. package/dist/components/ui/time-picker.js.map +1 -0
  78. package/dist/components/ui/timeline.d.ts +32 -0
  79. package/dist/components/ui/timeline.js +559 -0
  80. package/dist/components/ui/timeline.js.map +1 -0
  81. package/dist/components/ui/tour-constants.d.ts +39 -0
  82. package/dist/components/ui/tour-constants.js +12 -0
  83. package/dist/components/ui/tour-constants.js.map +1 -0
  84. package/dist/components/ui/tour.d.ts +124 -0
  85. package/dist/components/ui/tour.js +1207 -0
  86. package/dist/components/ui/tour.js.map +1 -0
  87. package/dist/data-grid/data-grid-cell-variants.js +10 -6
  88. package/dist/data-grid/data-grid-cell-variants.js.map +1 -1
  89. package/dist/data-grid/data-grid-cell-wrapper.js +1 -1
  90. package/dist/data-grid/data-grid-cell-wrapper.js.map +1 -1
  91. package/dist/data-grid/data-grid-column-header.js +4 -2
  92. package/dist/data-grid/data-grid-column-header.js.map +1 -1
  93. package/dist/data-grid/data-grid-context-menu.js +5 -4
  94. package/dist/data-grid/data-grid-context-menu.js.map +1 -1
  95. package/dist/data-grid/data-grid-filter-menu.d.ts +12 -1
  96. package/dist/data-grid/data-grid-filter-menu.js +28 -17
  97. package/dist/data-grid/data-grid-filter-menu.js.map +1 -1
  98. package/dist/data-grid/data-grid-keyboard-shortcuts.d.ts +12 -1
  99. package/dist/data-grid/data-grid-keyboard-shortcuts.js +7 -5
  100. package/dist/data-grid/data-grid-keyboard-shortcuts.js.map +1 -1
  101. package/dist/data-grid/data-grid-locale.d.ts +24 -0
  102. package/dist/data-grid/data-grid-locale.js +25 -0
  103. package/dist/data-grid/data-grid-locale.js.map +1 -0
  104. package/dist/data-grid/data-grid-paste-dialog.js +3 -2
  105. package/dist/data-grid/data-grid-paste-dialog.js.map +1 -1
  106. package/dist/data-grid/data-grid-row-height-menu.d.ts +11 -1
  107. package/dist/data-grid/data-grid-row-height-menu.js +25 -28
  108. package/dist/data-grid/data-grid-row-height-menu.js.map +1 -1
  109. package/dist/data-grid/data-grid-row.js +1 -1
  110. package/dist/data-grid/data-grid-row.js.map +1 -1
  111. package/dist/data-grid/data-grid-search.js +6 -5
  112. package/dist/data-grid/data-grid-search.js.map +1 -1
  113. package/dist/data-grid/data-grid-select-column.js +3 -1
  114. package/dist/data-grid/data-grid-select-column.js.map +1 -1
  115. package/dist/data-grid/data-grid-sort-menu.d.ts +12 -1
  116. package/dist/data-grid/data-grid-sort-menu.js +25 -13
  117. package/dist/data-grid/data-grid-sort-menu.js.map +1 -1
  118. package/dist/data-grid/data-grid-view-menu.d.ts +12 -1
  119. package/dist/data-grid/data-grid-view-menu.js +12 -4
  120. package/dist/data-grid/data-grid-view-menu.js.map +1 -1
  121. package/dist/data-grid/data-grid.d.ts +14 -2
  122. package/dist/data-grid/data-grid.js +7 -4
  123. package/dist/data-grid/data-grid.js.map +1 -1
  124. package/dist/data-grid/filters.d.ts +17 -21
  125. package/dist/data-grid/filters.js +73 -52
  126. package/dist/data-grid/filters.js.map +1 -1
  127. package/dist/data-grid/index.d.ts +4 -1
  128. package/dist/data-grid/index.js +20 -2
  129. package/dist/data-grid/index.js.map +1 -1
  130. package/dist/data-grid/messages-en.d.ts +294 -0
  131. package/dist/data-grid/messages-en.js +279 -0
  132. package/dist/data-grid/messages-en.js.map +1 -0
  133. package/dist/data-grid/messages-vi.d.ts +12 -0
  134. package/dist/data-grid/messages-vi.js +279 -0
  135. package/dist/data-grid/messages-vi.js.map +1 -0
  136. package/dist/data-grid/messages.d.ts +549 -262
  137. package/dist/data-grid/messages.js +16 -275
  138. package/dist/data-grid/messages.js.map +1 -1
  139. package/dist/data-grid/sortable.js +1 -1
  140. package/dist/data-grid/sortable.js.map +1 -1
  141. package/dist/data-grid/use-data-grid-undo-redo.d.ts +13 -1
  142. package/dist/data-grid/use-data-grid-undo-redo.js +13 -10
  143. package/dist/data-grid/use-data-grid-undo-redo.js.map +1 -1
  144. package/dist/data-grid/use-data-grid.d.ts +281 -1
  145. package/dist/data-grid/use-data-grid.js +24 -19
  146. package/dist/data-grid/use-data-grid.js.map +1 -1
  147. package/dist/data-grid/utils.d.ts +9 -1
  148. package/dist/data-grid/utils.js +3 -2
  149. package/dist/data-grid/utils.js.map +1 -1
  150. package/dist/index.d.ts +31 -0
  151. package/dist/index.js +42 -0
  152. package/dist/index.js.map +1 -1
  153. package/dist/lib/compose-refs.js.map +1 -0
  154. package/dist/lib/use-as-ref.js.map +1 -0
  155. package/dist/lib/use-is-mobile.d.ts +10 -0
  156. package/dist/lib/use-is-mobile.js +21 -0
  157. package/dist/lib/use-is-mobile.js.map +1 -0
  158. package/dist/lib/use-isomorphic-layout-effect.js.map +1 -0
  159. package/dist/lib/use-lazy-ref.js.map +1 -0
  160. package/dist/lib/visually-hidden-input.d.ts +12 -0
  161. package/dist/lib/visually-hidden-input.js +105 -0
  162. package/dist/lib/visually-hidden-input.js.map +1 -0
  163. package/package.json +5 -1
  164. package/styles.css +19 -0
  165. package/dist/data-grid/compose-refs.js.map +0 -1
  166. package/dist/data-grid/use-as-ref.js.map +0 -1
  167. package/dist/data-grid/use-isomorphic-layout-effect.js.map +0 -1
  168. package/dist/data-grid/use-lazy-ref.js.map +0 -1
  169. /package/dist/{data-grid → lib}/compose-refs.d.ts +0 -0
  170. /package/dist/{data-grid → lib}/compose-refs.js +0 -0
  171. /package/dist/{data-grid → lib}/use-as-ref.d.ts +0 -0
  172. /package/dist/{data-grid → lib}/use-as-ref.js +0 -0
  173. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.d.ts +0 -0
  174. /package/dist/{data-grid → lib}/use-isomorphic-layout-effect.js +0 -0
  175. /package/dist/{data-grid → lib}/use-lazy-ref.d.ts +0 -0
  176. /package/dist/{data-grid → lib}/use-lazy-ref.js +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/cropper.tsx"],"sourcesContent":["\"use client\";\n\n/*\n * Port từ Dice UI (https://diceui.com/docs/components/cropper) — 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\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"../../lib/cn.js\";\nimport { useComposedRefs } from \"../../lib/compose-refs.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 { type CropperLabels, DEFAULT_CROPPER_LABELS } from \"./cropper-constants.js\";\n\nconst ROOT_NAME = \"Cropper\";\nconst ROOT_IMPL_NAME = \"CropperImpl\";\nconst IMAGE_NAME = \"CropperImage\";\nconst VIDEO_NAME = \"CropperVideo\";\nconst AREA_NAME = \"CropperArea\";\n\ninterface Point {\n x: number;\n y: number;\n}\n\ninterface GestureEvent extends UIEvent {\n rotation: number;\n scale: number;\n clientX: number;\n clientY: number;\n}\n\ninterface Size {\n width: number;\n height: number;\n}\n\ninterface Area {\n width: number;\n height: number;\n x: number;\n y: number;\n}\n\ninterface MediaSize {\n width: number;\n height: number;\n naturalWidth: number;\n naturalHeight: number;\n}\n\ntype Shape = \"rectangle\" | \"circle\";\ntype ObjectFit = \"contain\" | \"cover\" | \"horizontal-cover\" | \"vertical-cover\";\n\ninterface DivProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nconst MAX_CACHE_SIZE = 200;\n\n/*\n * ⚠ Thêm so với thượng nguồn: phím + / - để thu phóng (xem `onKeyDown` của `CropperImpl`).\n * `=` và `_` là cùng phím với `+` / `-` khi KHÔNG giữ Shift trên bàn phím US.\n */\nconst ZOOM_IN_KEYS = new Set([\"+\", \"=\"]);\nconst ZOOM_OUT_KEYS = new Set([\"-\", \"_\"]);\nconst KEYBOARD_ZOOM_STEP = 0.1;\n\n/*\n * ⚠ Khác thượng nguồn: bản gốc chụp `window.devicePixelRatio` vào một HẰNG ở cấp module\n * (`const DPR = typeof window …`). Hệ quả: module chạm `window` ngay lúc nạp; server luôn ra 1 còn\n * client ra 2 nên `style` của ảnh lệch nhau lúc hydrate khi bật `snapPixels`; và zoom trình duyệt\n * thì con số không bao giờ cập nhật. Nay đọc LƯỜI — trong handler/effect gọi hàm này, còn lượt\n * render đi qua `useDevicePixelRatio` (server và lượt hydrate luôn nhận 1).\n */\nfunction getDevicePixelRatio(): number {\n return typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1;\n}\n\nfunction getServerDevicePixelRatio(): number {\n return 1;\n}\n\nfunction subscribeToDevicePixelRatio(onChange: () => void) {\n // Zoom trình duyệt đổi DPR và bắn `resize`.\n window.addEventListener(\"resize\", onChange);\n return () => window.removeEventListener(\"resize\", onChange);\n}\n\nfunction useDevicePixelRatio(): number {\n return React.useSyncExternalStore(subscribeToDevicePixelRatio, getDevicePixelRatio, getServerDevicePixelRatio);\n}\n\nconst rotationSizeCache = new Map<string, Size>();\nconst cropSizeCache = new Map<string, Size>();\nconst croppedAreaCache = new Map<string, { croppedAreaPercentages: Area; croppedAreaPixels: Area }>();\nconst onPositionClampCache = new Map<string, Point>();\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction quantize(n: number, step = 2 / getDevicePixelRatio()): number {\n return Math.round(n / step) * step;\n}\n\nfunction quantizePosition(n: number, step = 4 / getDevicePixelRatio()): number {\n return Math.round(n / step) * step;\n}\n\nfunction quantizeZoom(n: number, step = 0.01): number {\n return Math.round(n / step) * step;\n}\n\nfunction quantizeRotation(n: number, step = 1.0): number {\n return Math.round(n / step) * step;\n}\n\nfunction snapToDevicePixel(n: number, dpr: number): number {\n return Math.round(n * dpr) / dpr;\n}\n\nfunction lruGet<K, V>(map: Map<K, V>, key: K): V | undefined {\n const v = map.get(key);\n if (v !== undefined) {\n map.delete(key);\n map.set(key, v);\n }\n return v;\n}\n\nfunction lruSet<K, V>(map: Map<K, V>, key: K, val: V, max = MAX_CACHE_SIZE): void {\n if (map.has(key)) {\n map.delete(key);\n }\n map.set(key, val);\n if (map.size > max) {\n const firstKey = map.keys().next().value;\n if (firstKey !== undefined) {\n map.delete(firstKey);\n }\n }\n}\n\nfunction getDistanceBetweenPoints(pointA: Point, pointB: Point): number {\n return Math.sqrt((pointA.y - pointB.y) ** 2 + (pointA.x - pointB.x) ** 2);\n}\n\nfunction getCenter(a: Point, b: Point): Point {\n return {\n x: (b.x + a.x) * 0.5,\n y: (b.y + a.y) * 0.5\n };\n}\n\nfunction getRotationBetweenPoints(pointA: Point, pointB: Point): number {\n return (Math.atan2(pointB.y - pointA.y, pointB.x - pointA.x) * 180) / Math.PI;\n}\n\nfunction getRadianAngle(degreeValue: number): number {\n return (degreeValue * Math.PI) / 180;\n}\n\nfunction rotateSize(width: number, height: number, rotation: number): Size {\n const cacheKey = `${quantize(width)}-${quantize(height)}-${quantizeRotation(rotation)}`;\n\n const cached = lruGet(rotationSizeCache, cacheKey);\n if (cached) {\n return cached;\n }\n const rotRad = getRadianAngle(rotation);\n const cosRot = Math.cos(rotRad);\n const sinRot = Math.sin(rotRad);\n\n const result: Size = {\n width: Math.abs(cosRot * width) + Math.abs(sinRot * height),\n height: Math.abs(sinRot * width) + Math.abs(cosRot * height)\n };\n\n lruSet(rotationSizeCache, cacheKey, result, MAX_CACHE_SIZE);\n return result;\n}\n\nfunction getCropSize(\n mediaWidth: number,\n mediaHeight: number,\n contentWidth: number,\n contentHeight: number,\n aspect: number,\n rotation = 0\n): Size {\n const cacheKey = `${quantize(mediaWidth, 8)}-${quantize(mediaHeight, 8)}-${quantize(contentWidth, 8)}-${quantize(contentHeight, 8)}-${quantize(aspect, 0.01)}-${quantizeRotation(rotation)}`;\n\n const cached = lruGet(cropSizeCache, cacheKey);\n if (cached) {\n return cached;\n }\n const { width, height } = rotateSize(mediaWidth, mediaHeight, rotation);\n const fittingWidth = Math.min(width, contentWidth);\n const fittingHeight = Math.min(height, contentHeight);\n\n const result: Size =\n fittingWidth > fittingHeight * aspect\n ? {\n width: fittingHeight * aspect,\n height: fittingHeight\n }\n : {\n width: fittingWidth,\n height: fittingWidth / aspect\n };\n\n lruSet(cropSizeCache, cacheKey, result, MAX_CACHE_SIZE);\n return result;\n}\n\nfunction onPositionClamp(position: Point, mediaSize: Size, cropSize: Size, zoom: number, rotation = 0): Point {\n /*\n * ⚠ Khác thượng nguồn: bản gốc cache CẢ KẾT QUẢ, với khoá chứa vị trí đã lượng tử hoá (bước\n * 4/DPR px) — hai vị trí khác nhau rơi cùng một ô thì nhận lại kết quả của vị trí TRƯỚC. Phím\n * mũi tên dời 1px (Shift: 0,2px) nên vị trí mới luôn rơi vào ô của vị trí cũ và cache trả lại\n * đúng vị trí cũ: ảnh KẸT sau lần bấm đầu tiên — đo trên trình duyệt thật, 20 lần → phải đúng\n * 1px. Tức bàn phím không dời được ảnh. Nay chỉ cache GIỚI HẠN kẹp (thứ phụ thuộc kích thước,\n * zoom, góc — không phụ thuộc vị trí, cùng cách lượng tử hoá như bản gốc) và kẹp vị trí THẬT.\n */\n const cacheKey = `${quantize(mediaSize.width)}-${quantize(mediaSize.height)}-${quantize(cropSize.width)}-${quantize(cropSize.height)}-${quantizeZoom(zoom)}-${quantizeRotation(rotation)}`;\n\n let maxPosition = lruGet(onPositionClampCache, cacheKey);\n if (!maxPosition) {\n const { width, height } = rotateSize(mediaSize.width, mediaSize.height, rotation);\n\n maxPosition = {\n x: width * zoom * 0.5 - cropSize.width * 0.5,\n y: height * zoom * 0.5 - cropSize.height * 0.5\n };\n\n lruSet(onPositionClampCache, cacheKey, maxPosition, MAX_CACHE_SIZE);\n }\n\n return {\n x: clamp(position.x, -maxPosition.x, maxPosition.x),\n y: clamp(position.y, -maxPosition.y, maxPosition.y)\n };\n}\n\nfunction getCroppedArea(\n crop: Point,\n mediaSize: MediaSize,\n cropSize: Size,\n aspect: number,\n zoom: number,\n rotation = 0,\n allowOverflow = false\n): { croppedAreaPercentages: Area; croppedAreaPixels: Area } {\n const cacheKey = `${quantizePosition(crop.x)}-${quantizePosition(crop.y)}-${quantize(mediaSize.width)}-${quantize(mediaSize.height)}-${quantize(mediaSize.naturalWidth)}-${quantize(mediaSize.naturalHeight)}-${quantize(cropSize.width)}-${quantize(cropSize.height)}-${quantize(aspect, 0.01)}-${quantizeZoom(zoom)}-${quantizeRotation(rotation)}-${allowOverflow}`;\n\n const cached = lruGet(croppedAreaCache, cacheKey);\n\n if (cached) return cached;\n\n const onAreaLimit = !allowOverflow\n ? (max: number, value: number) => Math.min(max, Math.max(0, value))\n : (_max: number, value: number) => value;\n\n const mediaBBoxSize = rotateSize(mediaSize.width, mediaSize.height, rotation);\n const mediaNaturalBBoxSize = rotateSize(mediaSize.naturalWidth, mediaSize.naturalHeight, rotation);\n\n const croppedAreaPercentages: Area = {\n x: onAreaLimit(\n 100,\n (((mediaBBoxSize.width - cropSize.width / zoom) / 2 - crop.x / zoom) / mediaBBoxSize.width) * 100\n ),\n y: onAreaLimit(\n 100,\n (((mediaBBoxSize.height - cropSize.height / zoom) / 2 - crop.y / zoom) / mediaBBoxSize.height) * 100\n ),\n width: onAreaLimit(100, ((cropSize.width / mediaBBoxSize.width) * 100) / zoom),\n height: onAreaLimit(100, ((cropSize.height / mediaBBoxSize.height) * 100) / zoom)\n };\n\n const widthInPixels = Math.round(\n onAreaLimit(mediaNaturalBBoxSize.width, (croppedAreaPercentages.width * mediaNaturalBBoxSize.width) / 100)\n );\n const heightInPixels = Math.round(\n onAreaLimit(mediaNaturalBBoxSize.height, (croppedAreaPercentages.height * mediaNaturalBBoxSize.height) / 100)\n );\n const isImageWiderThanHigh = mediaNaturalBBoxSize.width >= mediaNaturalBBoxSize.height * aspect;\n\n const sizePixels: Size = isImageWiderThanHigh\n ? {\n width: Math.round(heightInPixels * aspect),\n height: heightInPixels\n }\n : {\n width: widthInPixels,\n height: Math.round(widthInPixels / aspect)\n };\n\n const croppedAreaPixels: Area = {\n ...sizePixels,\n x: Math.round(\n onAreaLimit(\n mediaNaturalBBoxSize.width - sizePixels.width,\n (croppedAreaPercentages.x * mediaNaturalBBoxSize.width) / 100\n )\n ),\n y: Math.round(\n onAreaLimit(\n mediaNaturalBBoxSize.height - sizePixels.height,\n (croppedAreaPercentages.y * mediaNaturalBBoxSize.height) / 100\n )\n )\n };\n\n const result = { croppedAreaPercentages, croppedAreaPixels };\n\n lruSet(croppedAreaCache, cacheKey, result, MAX_CACHE_SIZE);\n return result;\n}\n\ninterface StoreState {\n crop: Point;\n zoom: number;\n rotation: number;\n mediaSize: MediaSize | null;\n cropSize: Size | null;\n isDragging: boolean;\n isWheelZooming: boolean;\n}\n\ninterface Store {\n subscribe: (callback: () => void) => () => void;\n getState: () => StoreState;\n setState: <K extends keyof StoreState>(key: K, value: StoreState[K]) => void;\n notify: () => void;\n batch: (fn: () => void) => 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 \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\nfunction useStore<T>(selector: (state: StoreState) => T): T {\n // Tên trong thông báo lỗi là tên XUẤT RA NGOÀI (`useCropper`), không phải tên nội bộ.\n const store = useStoreContext(\"useCropper\");\n\n const getSnapshot = React.useCallback(() => selector(store.getState()), [store, selector]);\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ntype RootElement = React.ComponentRef<typeof CropperImpl>;\n\ninterface CropperContextValue {\n aspectRatio: number;\n minZoom: number;\n maxZoom: number;\n zoomSpeed: number;\n keyboardStep: number;\n shape: Shape;\n objectFit: ObjectFit;\n rootRef: React.RefObject<RootElement | null>;\n allowOverflow: boolean;\n preventScrollZoom: boolean;\n withGrid: boolean;\n labels: CropperLabels;\n}\n\nconst CropperContext = React.createContext<CropperContextValue | null>(null);\n\nfunction useCropperContext(consumerName: string) {\n const context = React.useContext(CropperContext);\n if (!context) {\n throw new Error(`\\`${consumerName}\\` phải nằm bên trong \\`${ROOT_NAME}\\``);\n }\n return context;\n}\n\ninterface CropperProps extends DivProps {\n crop?: Point;\n zoom?: number;\n minZoom?: number;\n maxZoom?: number;\n zoomSpeed?: number;\n rotation?: number;\n keyboardStep?: number;\n aspectRatio?: number;\n shape?: Shape;\n objectFit?: ObjectFit;\n allowOverflow?: boolean;\n preventScrollZoom?: boolean;\n withGrid?: boolean;\n /**\n * Chuỗi a11y của vùng cắt — ghi đè từng khoá, mặc định `DEFAULT_CROPPER_LABELS` (tiếng Việt).\n *\n * ⚠ Thêm so với thượng nguồn — xem `CropperLabels`.\n */\n labels?: Partial<CropperLabels>;\n onCropChange?: (crop: Point) => void;\n onCropSizeChange?: (cropSize: Size) => void;\n onCropAreaChange?: (croppedArea: Area, croppedAreaPixels: Area) => void;\n onCropComplete?: (croppedArea: Area, croppedAreaPixels: Area) => void;\n onZoomChange?: (zoom: number) => void;\n onRotationChange?: (rotation: number) => void;\n onMediaLoaded?: (mediaSize: MediaSize) => void;\n onInteractionStart?: () => void;\n onInteractionEnd?: () => void;\n onWheelZoom?: (event: WheelEvent) => void;\n}\n\n/**\n * Cropper — cắt ảnh/video: kéo để dời, lăn chuột/chụm hai ngón để phóng, phím mũi tên và + / -.\n *\n * Ở Comitor dùng để cắt ảnh đại diện hoặc logo công ty TRƯỚC khi tải lên. Component chỉ BÁO vùng\n * cắt (`onCropComplete(croppedArea, croppedAreaPixels)`) — việc vẽ ra canvas và tải lên là của\n * app. Cố ý không phụ thuộc gì ngoài peer bắt buộc để sống được ở entry chính; `ImageUploadField`\n * (entry `/uploader`, kéo theo `@uppy/*`) ghép với nó ở tầng app, không ở đây.\n *\n * `crop` / `zoom` / `rotation` là controlled KHI được truyền, uncontrolled khi bỏ trống.\n */\nfunction Cropper(props: CropperProps) {\n const {\n /*\n * ⚠ Khác thượng nguồn: bản gốc gán mặc định ngay ở đây (`crop = { x: 0, y: 0 }`, `zoom = 1`,\n * `rotation = 0`), nên nhánh `!== undefined` trong effect đồng bộ bên dưới LUÔN đúng, và\n * `{ x: 0, y: 0 }` là object MỚI mỗi lượt render. Hệ quả ở chế độ không kiểm soát: mỗi lần\n * component cha render lại — kể cả vì chính `onCropComplete` gọi `setState` ở cha — vị trí và\n * độ phóng bị kéo về mặc định ngay sau khi người dùng vừa chỉnh xong. Mặc định nay chỉ dùng\n * cho state KHỞI TẠO; effect chỉ đồng bộ prop nào thật sự được truyền, đúng ý của nhánh đó.\n */\n crop: cropProp,\n zoom: zoomProp,\n minZoom = 1,\n maxZoom = 3,\n zoomSpeed = 1,\n rotation: rotationProp,\n keyboardStep = 1,\n aspectRatio = 4 / 3,\n shape = \"rectangle\",\n objectFit = \"contain\",\n allowOverflow = false,\n preventScrollZoom = false,\n withGrid = false,\n labels: labelsProp,\n onCropChange,\n onCropSizeChange,\n onCropAreaChange,\n onCropComplete,\n onZoomChange,\n onRotationChange,\n onMediaLoaded,\n onInteractionStart,\n onInteractionEnd,\n className,\n ...rootProps\n } = props;\n\n const listenersRef = useLazyRef(() => new Set<() => void>());\n const stateRef = useLazyRef<StoreState>(() => ({\n crop: cropProp ?? { x: 0, y: 0 },\n zoom: zoomProp ?? 1,\n rotation: rotationProp ?? 0,\n mediaSize: null,\n cropSize: null,\n isDragging: false,\n isWheelZooming: false\n }));\n\n const propsRef = useAsRef({\n onCropChange,\n onCropSizeChange,\n onCropAreaChange,\n onCropComplete,\n onZoomChange,\n onRotationChange,\n onMediaLoaded,\n onInteractionStart,\n onInteractionEnd\n });\n\n const rootRef = React.useRef<RootElement | null>(null);\n\n const store = React.useMemo<Store>(() => {\n let isBatching = false;\n let raf: number | null = null;\n\n function notifyCropAreaChange() {\n if (raf != null) return;\n raf = requestAnimationFrame(() => {\n raf = null;\n const s = stateRef.current;\n if (s?.mediaSize && s.cropSize && propsRef.current.onCropAreaChange) {\n const { croppedAreaPercentages, croppedAreaPixels } = getCroppedArea(\n s.crop,\n s.mediaSize,\n s.cropSize,\n aspectRatio,\n s.zoom,\n s.rotation\n );\n propsRef.current.onCropAreaChange(croppedAreaPercentages, croppedAreaPixels);\n }\n });\n }\n\n return {\n subscribe: (cb) => {\n listenersRef.current.add(cb);\n return () => listenersRef.current.delete(cb);\n },\n getState: () => stateRef.current,\n setState: (key, value) => {\n if (Object.is(stateRef.current[key], value)) return;\n\n stateRef.current[key] = value;\n\n if (key === \"crop\" && typeof value === \"object\" && value && \"x\" in value) {\n propsRef.current.onCropChange?.(value);\n } else if (key === \"zoom\" && typeof value === \"number\") {\n propsRef.current.onZoomChange?.(value);\n } else if (key === \"rotation\" && typeof value === \"number\") {\n propsRef.current.onRotationChange?.(value);\n } else if (key === \"cropSize\" && typeof value === \"object\" && value && \"width\" in value) {\n propsRef.current.onCropSizeChange?.(value);\n } else if (key === \"mediaSize\" && typeof value === \"object\" && value && \"naturalWidth\" in value) {\n propsRef.current.onMediaLoaded?.(value);\n } else if (key === \"isDragging\") {\n if (value) {\n propsRef.current.onInteractionStart?.();\n } else {\n propsRef.current.onInteractionEnd?.();\n const currentState = stateRef.current;\n if (currentState?.mediaSize && currentState.cropSize && propsRef.current.onCropComplete) {\n const { croppedAreaPercentages, croppedAreaPixels } = getCroppedArea(\n currentState.crop,\n currentState.mediaSize,\n currentState.cropSize,\n aspectRatio,\n currentState.zoom,\n currentState.rotation\n );\n propsRef.current.onCropComplete(croppedAreaPercentages, croppedAreaPixels);\n }\n }\n }\n\n if (\n (key === \"crop\" || key === \"zoom\" || key === \"rotation\" || key === \"mediaSize\" || key === \"cropSize\") &&\n propsRef.current.onCropAreaChange\n ) {\n notifyCropAreaChange();\n }\n\n if (!isBatching) {\n store.notify();\n }\n },\n notify: () => {\n for (const cb of listenersRef.current) {\n cb();\n }\n },\n batch: (fn: () => void) => {\n if (isBatching) {\n fn();\n return;\n }\n isBatching = true;\n try {\n fn();\n } finally {\n isBatching = false;\n store.notify();\n }\n }\n };\n }, [listenersRef, stateRef, propsRef, aspectRatio]);\n\n useIsomorphicLayoutEffect(() => {\n const updates: Partial<StoreState> = {};\n let hasUpdates = false;\n let shouldRecompute = false;\n\n if (cropProp !== undefined) {\n const currentState = store.getState();\n if (!Object.is(currentState.crop, cropProp)) {\n updates.crop = cropProp;\n hasUpdates = true;\n }\n }\n\n if (zoomProp !== undefined) {\n const currentState = store.getState();\n if (currentState.zoom !== zoomProp) {\n updates.zoom = zoomProp;\n hasUpdates = true;\n shouldRecompute = true;\n }\n }\n\n if (rotationProp !== undefined) {\n const currentState = store.getState();\n if (currentState.rotation !== rotationProp) {\n updates.rotation = rotationProp;\n hasUpdates = true;\n shouldRecompute = true;\n }\n }\n\n if (hasUpdates) {\n store.batch(() => {\n Object.entries(updates).forEach(([key, value]) => {\n store.setState(key as keyof StoreState, value);\n });\n });\n\n if (shouldRecompute && rootRef.current) {\n requestAnimationFrame(() => {\n const currentState = store.getState();\n if (currentState.cropSize && currentState.mediaSize) {\n const newPosition = !allowOverflow\n ? onPositionClamp(\n currentState.crop,\n currentState.mediaSize,\n currentState.cropSize,\n currentState.zoom,\n currentState.rotation\n )\n : currentState.crop;\n\n if (\n Math.abs(newPosition.x - currentState.crop.x) > 0.001 ||\n Math.abs(newPosition.y - currentState.crop.y) > 0.001\n ) {\n store.setState(\"crop\", newPosition);\n }\n }\n });\n }\n }\n }, [cropProp, zoomProp, rotationProp, store, allowOverflow]);\n\n const labels = React.useMemo<CropperLabels>(() => ({ ...DEFAULT_CROPPER_LABELS, ...labelsProp }), [labelsProp]);\n\n const contextValue = React.useMemo<CropperContextValue>(\n () => ({\n minZoom,\n maxZoom,\n zoomSpeed,\n keyboardStep,\n aspectRatio,\n shape,\n objectFit,\n preventScrollZoom,\n allowOverflow,\n withGrid,\n rootRef,\n labels\n }),\n [\n minZoom,\n maxZoom,\n zoomSpeed,\n keyboardStep,\n aspectRatio,\n shape,\n objectFit,\n preventScrollZoom,\n allowOverflow,\n withGrid,\n labels\n ]\n );\n\n return (\n <StoreContext.Provider value={store}>\n <CropperContext.Provider value={contextValue}>\n <div data-slot=\"cropper-wrapper\" className={cn(\"relative size-full overflow-hidden\", className)}>\n <CropperImpl {...rootProps} />\n </div>\n </CropperContext.Provider>\n </StoreContext.Provider>\n );\n}\n\ninterface CropperImplProps extends CropperProps {\n onWheelZoom?: (event: WheelEvent) => void;\n}\n\nfunction CropperImpl(props: CropperImplProps) {\n const {\n onWheelZoom: onWheelZoomProp,\n onKeyUp: onKeyUpProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onTouchStart: onTouchStartProp,\n \"aria-describedby\": ariaDescribedByProp,\n asChild,\n className,\n ref,\n ...rootImplProps\n } = props;\n\n const context = useCropperContext(ROOT_IMPL_NAME);\n const store = useStoreContext(ROOT_IMPL_NAME);\n const crop = useStore((state) => state.crop);\n const zoom = useStore((state) => state.zoom);\n const rotation = useStore((state) => state.rotation);\n const mediaSize = useStore((state) => state.mediaSize);\n const cropSize = useStore((state) => state.cropSize);\n\n const descriptionId = React.useId();\n // Câu đọc qua vùng `aria-live` — chỉ đổi khi phóng/thu BẰNG PHÍM (con lăn/chụm là thao tác nhìn).\n const [zoomStatus, setZoomStatus] = React.useState(\"\");\n\n const propsRef = useAsRef({\n onWheelZoom: onWheelZoomProp,\n onKeyUp: onKeyUpProp,\n onKeyDown: onKeyDownProp,\n onMouseDown: onMouseDownProp,\n onTouchStart: onTouchStartProp\n });\n\n const composedRef = useComposedRefs(ref, context.rootRef);\n const dragStartPositionRef = React.useRef<Point>({ x: 0, y: 0 });\n const dragStartCropRef = React.useRef<Point>({ x: 0, y: 0 });\n const contentPositionRef = React.useRef<Point>({ x: 0, y: 0 });\n const lastPinchDistanceRef = React.useRef(0);\n const lastPinchRotationRef = React.useRef(0);\n const rafDragTimeoutRef = React.useRef<number | null>(null);\n const rafPinchTimeoutRef = React.useRef<number | null>(null);\n const wheelTimerRef = React.useRef<number | null>(null);\n const isTouchingRef = React.useRef(false);\n const gestureZoomStartRef = React.useRef(0);\n const gestureRotationStartRef = React.useRef(0);\n\n const onRefsCleanup = React.useCallback(() => {\n if (rafDragTimeoutRef.current) {\n cancelAnimationFrame(rafDragTimeoutRef.current);\n rafDragTimeoutRef.current = null;\n }\n if (rafPinchTimeoutRef.current) {\n cancelAnimationFrame(rafPinchTimeoutRef.current);\n rafPinchTimeoutRef.current = null;\n }\n if (wheelTimerRef.current) {\n clearTimeout(wheelTimerRef.current);\n wheelTimerRef.current = null;\n }\n isTouchingRef.current = false;\n }, []);\n\n const onCacheCleanup = React.useCallback(() => {\n if (onPositionClampCache.size > MAX_CACHE_SIZE * 1.5) {\n onPositionClampCache.clear();\n }\n if (croppedAreaCache.size > MAX_CACHE_SIZE * 1.5) {\n croppedAreaCache.clear();\n }\n }, []);\n\n const getMousePoint = React.useCallback(\n (event: MouseEvent | React.MouseEvent) => ({\n x: Number(event.clientX),\n y: Number(event.clientY)\n }),\n []\n );\n\n const getTouchPoint = React.useCallback(\n (touch: Touch | React.Touch) => ({\n x: Number(touch.clientX),\n y: Number(touch.clientY)\n }),\n []\n );\n\n const onContentPositionChange = React.useCallback(() => {\n if (context.rootRef?.current) {\n const bounds = context.rootRef.current.getBoundingClientRect();\n contentPositionRef.current = { x: bounds.left, y: bounds.top };\n }\n }, [context.rootRef]);\n\n const getPointOnContent = React.useCallback(\n ({ x, y }: Point, contentTopLeft: Point): Point => {\n if (!context.rootRef?.current) {\n return { x: 0, y: 0 };\n }\n const contentRect = context.rootRef.current.getBoundingClientRect();\n return {\n x: contentRect.width / 2 - (x - contentTopLeft.x),\n y: contentRect.height / 2 - (y - contentTopLeft.y)\n };\n },\n [context.rootRef]\n );\n\n const getPointOnMedia = React.useCallback(\n ({ x, y }: Point) => {\n return {\n x: (x + crop.x) / zoom,\n y: (y + crop.y) / zoom\n };\n },\n [crop, zoom]\n );\n\n const recomputeCropPosition = React.useCallback(() => {\n if (!cropSize || !mediaSize) return;\n\n const newPosition = !context.allowOverflow ? onPositionClamp(crop, mediaSize, cropSize, zoom, rotation) : crop;\n\n if (Math.abs(newPosition.x - crop.x) > 0.001 || Math.abs(newPosition.y - crop.y) > 0.001) {\n store.setState(\"crop\", newPosition);\n }\n }, [cropSize, mediaSize, context.allowOverflow, crop, zoom, rotation, store]);\n\n const onZoomChange = React.useCallback(\n (newZoom: number, point: Point, shouldUpdatePosition = true) => {\n if (!cropSize || !mediaSize) return;\n\n const clampedZoom = clamp(newZoom, context.minZoom, context.maxZoom);\n\n store.batch(() => {\n if (shouldUpdatePosition) {\n const zoomPoint = getPointOnContent(point, contentPositionRef.current);\n const zoomTarget = getPointOnMedia(zoomPoint);\n const requestedPosition = {\n x: zoomTarget.x * clampedZoom - zoomPoint.x,\n y: zoomTarget.y * clampedZoom - zoomPoint.y\n };\n\n const newPosition = !context.allowOverflow\n ? onPositionClamp(requestedPosition, mediaSize, cropSize, clampedZoom, rotation)\n : requestedPosition;\n\n store.setState(\"crop\", newPosition);\n }\n store.setState(\"zoom\", clampedZoom);\n });\n\n requestAnimationFrame(() => {\n recomputeCropPosition();\n });\n },\n [\n cropSize,\n mediaSize,\n context.minZoom,\n context.maxZoom,\n context.allowOverflow,\n getPointOnContent,\n getPointOnMedia,\n rotation,\n store,\n recomputeCropPosition\n ]\n );\n\n const onDragStart = React.useCallback(\n ({ x, y }: Point) => {\n dragStartPositionRef.current = { x, y };\n dragStartCropRef.current = { ...crop };\n store.setState(\"isDragging\", true);\n },\n [crop, store]\n );\n\n const onDrag = React.useCallback(\n ({ x, y }: Point) => {\n if (rafDragTimeoutRef.current) {\n cancelAnimationFrame(rafDragTimeoutRef.current);\n }\n\n rafDragTimeoutRef.current = requestAnimationFrame(() => {\n if (!cropSize || !mediaSize) return;\n if (x === undefined || y === undefined) return;\n\n const offsetX = x - dragStartPositionRef.current.x;\n const offsetY = y - dragStartPositionRef.current.y;\n\n if (Math.abs(offsetX) < 2 && Math.abs(offsetY) < 2) {\n return;\n }\n\n const requestedPosition = {\n x: dragStartCropRef.current.x + offsetX,\n y: dragStartCropRef.current.y + offsetY\n };\n\n const newPosition = !context.allowOverflow\n ? onPositionClamp(requestedPosition, mediaSize, cropSize, zoom, rotation)\n : requestedPosition;\n\n const currentCrop = store.getState().crop;\n if (Math.abs(newPosition.x - currentCrop.x) > 1 || Math.abs(newPosition.y - currentCrop.y) > 1) {\n store.setState(\"crop\", newPosition);\n }\n });\n },\n [cropSize, mediaSize, context.allowOverflow, zoom, rotation, store]\n );\n\n const onMouseMove = React.useCallback((event: MouseEvent) => onDrag(getMousePoint(event)), [getMousePoint, onDrag]);\n\n const onTouchMove = React.useCallback(\n (event: TouchEvent) => {\n event.preventDefault();\n if (event.touches.length === 2) {\n const [firstTouch, secondTouch] = event.touches ?? [];\n if (firstTouch && secondTouch) {\n const pointA = getTouchPoint(firstTouch);\n const pointB = getTouchPoint(secondTouch);\n const center = getCenter(pointA, pointB);\n onDrag(center);\n\n if (rafPinchTimeoutRef.current) {\n cancelAnimationFrame(rafPinchTimeoutRef.current);\n }\n\n rafPinchTimeoutRef.current = requestAnimationFrame(() => {\n const distance = getDistanceBetweenPoints(pointA, pointB);\n const distanceRatio = distance / lastPinchDistanceRef.current;\n\n if (Math.abs(distanceRatio - 1) > 0.01) {\n const newZoom = zoom * distanceRatio;\n onZoomChange(newZoom, center, false);\n lastPinchDistanceRef.current = distance;\n }\n\n const rotationAngle = getRotationBetweenPoints(pointA, pointB);\n const rotationDiff = rotationAngle - lastPinchRotationRef.current;\n\n if (Math.abs(rotationDiff) > 0.5) {\n const newRotation = rotation + rotationDiff;\n store.setState(\"rotation\", newRotation);\n lastPinchRotationRef.current = rotationAngle;\n }\n });\n }\n } else if (event.touches.length === 1) {\n const firstTouch = event.touches[0];\n if (firstTouch) {\n onDrag(getTouchPoint(firstTouch));\n }\n }\n },\n [getTouchPoint, onDrag, zoom, onZoomChange, rotation, store]\n );\n\n const onGestureChange = React.useCallback(\n (event: GestureEvent) => {\n event.preventDefault();\n if (isTouchingRef.current) {\n return;\n }\n\n const point = { x: Number(event.clientX), y: Number(event.clientY) };\n const newZoom = gestureZoomStartRef.current - 1 + event.scale;\n onZoomChange(newZoom, point, true);\n\n const newRotation = gestureRotationStartRef.current + event.rotation;\n store.setState(\"rotation\", newRotation);\n },\n [onZoomChange, store]\n );\n\n const onGestureEnd = React.useCallback(() => {\n document.removeEventListener(\"gesturechange\", onGestureChange as EventListener);\n document.removeEventListener(\"gestureend\", onGestureEnd as EventListener);\n }, [onGestureChange]);\n\n const onGestureStart = React.useCallback(\n (event: GestureEvent) => {\n event.preventDefault();\n // ⚠ Khác thượng nguồn: cập nhật toạ độ khung trước khi phóng — xem chú thích ở `onWheelZoom`.\n onContentPositionChange();\n document.addEventListener(\"gesturechange\", onGestureChange as EventListener);\n document.addEventListener(\"gestureend\", onGestureEnd as EventListener);\n gestureZoomStartRef.current = zoom;\n gestureRotationStartRef.current = rotation;\n },\n [zoom, rotation, onGestureChange, onGestureEnd, onContentPositionChange]\n );\n\n const onSafariZoomPrevent = React.useCallback((event: Event) => event.preventDefault(), []);\n\n const onEventsCleanup = React.useCallback(() => {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"touchmove\", onTouchMove);\n document.removeEventListener(\"gesturechange\", onGestureChange as EventListener);\n document.removeEventListener(\"gestureend\", onGestureEnd as EventListener);\n }, [onMouseMove, onTouchMove, onGestureChange, onGestureEnd]);\n\n const onDragStopped = React.useCallback(() => {\n isTouchingRef.current = false;\n store.setState(\"isDragging\", false);\n onRefsCleanup();\n document.removeEventListener(\"mouseup\", onDragStopped);\n document.removeEventListener(\"touchend\", onDragStopped);\n onEventsCleanup();\n }, [store, onEventsCleanup, onRefsCleanup]);\n\n const getWheelDelta = React.useCallback((event: WheelEvent) => {\n let deltaX = event.deltaX;\n let deltaY = event.deltaY;\n let deltaZ = event.deltaZ;\n\n if (event.deltaMode === 1) {\n deltaX *= 16;\n deltaY *= 16;\n deltaZ *= 16;\n } else if (event.deltaMode === 2) {\n deltaX *= 400;\n deltaY *= 400;\n deltaZ *= 400;\n }\n\n return { deltaX, deltaY, deltaZ };\n }, []);\n\n const onWheelZoom = React.useCallback(\n (event: WheelEvent) => {\n propsRef.current.onWheelZoom?.(event);\n if (event.defaultPrevented) return;\n\n event.preventDefault();\n /*\n * ⚠ Khác thượng nguồn: bản gốc chỉ cập nhật `contentPositionRef` ở `mousedown`/`touchstart`,\n * nên lăn chuột khi CHƯA kéo lần nào (hoặc sau khi trang đã cuộn) tính điểm neo bằng toạ độ\n * khung cũ — mặc định (0, 0) — và ảnh phóng lệch khỏi con trỏ đúng bằng khoảng cách từ mép\n * trang tới khung cắt.\n */\n onContentPositionChange();\n const point = getMousePoint(event);\n const { deltaY } = getWheelDelta(event);\n const newZoom = zoom - (deltaY * context.zoomSpeed) / 200;\n onZoomChange(newZoom, point, true);\n\n store.batch(() => {\n const currentState = store.getState();\n if (!currentState.isWheelZooming) {\n store.setState(\"isWheelZooming\", true);\n }\n if (!currentState.isDragging) {\n store.setState(\"isDragging\", true);\n }\n });\n\n if (wheelTimerRef.current) {\n clearTimeout(wheelTimerRef.current);\n }\n wheelTimerRef.current = window.setTimeout(() => {\n store.batch(() => {\n store.setState(\"isWheelZooming\", false);\n store.setState(\"isDragging\", false);\n });\n }, 250);\n },\n [propsRef, getMousePoint, zoom, context.zoomSpeed, onZoomChange, getWheelDelta, store, onContentPositionChange]\n );\n\n const onKeyUp = React.useCallback(\n (event: React.KeyboardEvent<RootElement>) => {\n propsRef.current.onKeyUp?.(event);\n if (event.defaultPrevented) return;\n\n const arrowKeys = new Set([\"ArrowUp\", \"ArrowDown\", \"ArrowLeft\", \"ArrowRight\"]);\n\n // Phím thu phóng cũng khép một lượt tương tác, để `onCropComplete` bắn như khi thả phím mũi tên.\n if (arrowKeys.has(event.key) || ZOOM_IN_KEYS.has(event.key) || ZOOM_OUT_KEYS.has(event.key)) {\n event.preventDefault();\n store.setState(\"isDragging\", false);\n }\n },\n [propsRef, store]\n );\n\n const onKeyDown = React.useCallback(\n (event: React.KeyboardEvent<RootElement>) => {\n propsRef.current.onKeyDown?.(event);\n if (event.defaultPrevented || !cropSize || !mediaSize) return;\n\n /*\n * ⚠ Khác thượng nguồn: thêm + / - để thu phóng quanh TÂM khung. Bản gốc chỉ phóng được bằng\n * con lăn chuột và cử chỉ chụm ngón — người chỉ dùng bàn phím không có đường nào đổi `zoom`\n * nếu app không tự dựng thêm Slider (WCAG 2.1.1). Ctrl/Cmd/Alt + phím là zoom TRÌNH DUYỆT,\n * nên để nguyên cho trình duyệt.\n */\n const zoomDirection = ZOOM_IN_KEYS.has(event.key) ? 1 : ZOOM_OUT_KEYS.has(event.key) ? -1 : 0;\n if (zoomDirection !== 0) {\n if (event.metaKey || event.ctrlKey || event.altKey) return;\n const root = context.rootRef.current;\n if (!root) return;\n\n event.preventDefault();\n\n if (!event.repeat) {\n store.setState(\"isDragging\", true);\n }\n\n const nextZoom = clamp(zoom + zoomDirection * KEYBOARD_ZOOM_STEP, context.minZoom, context.maxZoom);\n onContentPositionChange();\n const rect = root.getBoundingClientRect();\n onZoomChange(nextZoom, { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 }, true);\n setZoomStatus(context.labels.zoomStatus(nextZoom));\n return;\n }\n\n let step = context.keyboardStep;\n if (event.shiftKey) {\n step *= 0.2;\n }\n\n const keyCallbacks: Record<string, () => Point> = {\n ArrowUp: () => ({ ...crop, y: crop.y - step }),\n ArrowDown: () => ({ ...crop, y: crop.y + step }),\n ArrowLeft: () => ({ ...crop, x: crop.x - step }),\n ArrowRight: () => ({ ...crop, x: crop.x + step })\n } as const;\n\n const callback = keyCallbacks[event.key];\n if (!callback) return;\n\n event.preventDefault();\n\n let newCrop = callback();\n\n if (!context.allowOverflow) {\n newCrop = onPositionClamp(newCrop, mediaSize, cropSize, zoom, rotation);\n }\n\n if (!event.repeat) {\n store.setState(\"isDragging\", true);\n }\n\n store.setState(\"crop\", newCrop);\n },\n [\n propsRef,\n cropSize,\n mediaSize,\n context.keyboardStep,\n context.allowOverflow,\n context.rootRef,\n context.minZoom,\n context.maxZoom,\n context.labels,\n crop,\n zoom,\n rotation,\n store,\n onContentPositionChange,\n onZoomChange\n ]\n );\n\n const onMouseDown = React.useCallback(\n (event: React.MouseEvent<RootElement>) => {\n propsRef.current.onMouseDown?.(event);\n if (event.defaultPrevented) return;\n\n event.preventDefault();\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onDragStopped);\n onContentPositionChange();\n onDragStart(getMousePoint(event));\n },\n [propsRef, getMousePoint, onDragStart, onDragStopped, onMouseMove, onContentPositionChange]\n );\n\n const onTouchStart = React.useCallback(\n (event: React.TouchEvent<RootElement>) => {\n propsRef.current.onTouchStart?.(event);\n if (event.defaultPrevented) return;\n\n isTouchingRef.current = true;\n document.addEventListener(\"touchmove\", onTouchMove, { passive: false });\n document.addEventListener(\"touchend\", onDragStopped);\n onContentPositionChange();\n\n if (event.touches.length === 2) {\n const [firstTouch, secondTouch] = event.touches ? Array.from(event.touches) : [];\n if (firstTouch && secondTouch) {\n const pointA = getTouchPoint(firstTouch);\n const pointB = getTouchPoint(secondTouch);\n lastPinchDistanceRef.current = getDistanceBetweenPoints(pointA, pointB);\n lastPinchRotationRef.current = getRotationBetweenPoints(pointA, pointB);\n onDragStart(getCenter(pointA, pointB));\n }\n } else if (event.touches.length === 1) {\n const firstTouch = event.touches[0];\n if (firstTouch) {\n onDragStart(getTouchPoint(firstTouch));\n }\n }\n },\n [propsRef, onDragStopped, onTouchMove, onContentPositionChange, getTouchPoint, onDragStart]\n );\n\n React.useEffect(() => {\n const content = context.rootRef?.current;\n if (!content) return;\n\n if (!context.preventScrollZoom) {\n content.addEventListener(\"wheel\", onWheelZoom, { passive: false });\n }\n\n content.addEventListener(\"gesturestart\", onSafariZoomPrevent);\n content.addEventListener(\"gesturestart\", onGestureStart as EventListener);\n\n return () => {\n if (!context.preventScrollZoom) {\n content.removeEventListener(\"wheel\", onWheelZoom);\n }\n content.removeEventListener(\"gesturestart\", onSafariZoomPrevent);\n content.removeEventListener(\"gesturestart\", onGestureStart as EventListener);\n /*\n * ⚠ Khác thượng nguồn: bản gốc gọi `onRefsCleanup()` ở đây. Effect này chạy lại MỖI lần\n * `zoom` đổi (vì `onWheelZoom` phụ thuộc `zoom`), nên nó huỷ đúng cái hẹn giờ 250ms mà lượt\n * lăn chuột vừa đặt để khép tương tác: `isDragging` kẹt ở `true`, và `onCropComplete` /\n * `onInteractionEnd` KHÔNG BAO GIỜ bắn sau khi phóng bằng con lăn (đo trên trình duyệt thật:\n * lăn một nấc, chờ 400ms, `onCropComplete` 0 lần). Dọn khi gỡ component đã có effect ngay dưới.\n */\n };\n }, [context.rootRef, context.preventScrollZoom, onWheelZoom, onSafariZoomPrevent, onGestureStart]);\n\n React.useEffect(() => {\n return () => {\n onRefsCleanup();\n onCacheCleanup();\n };\n }, [onRefsCleanup, onCacheCleanup]);\n\n const RootPrimitive = asChild ? Slot : \"div\";\n\n const describedBy = [descriptionId, ariaDescribedByProp].filter(Boolean).join(\" \");\n\n return (\n <>\n <RootPrimitive\n /*\n * ⚠ Khác thượng nguồn: bản gốc là một `<div tabIndex={0}>` KHÔNG role, KHÔNG tên — Tab tới\n * được nhưng trình đọc màn hình không biết đó là gì, cũng không biết phím nào dùng được.\n * `role=\"application\"` (không phải `group`) là CỐ Ý: vùng này sống nhờ phím mũi tên và\n * + / -, mà với `group` thì NVDA/JAWS ở chế độ duyệt nuốt mất phím mũi tên trước khi tới\n * handler; `application` bật chế độ focus. Bên trong không có nội dung đọc được nào bị\n * che đi — chỉ có ảnh đang cắt và khung vẽ. App ghi đè được cả ba qua `labels` hoặc\n * truyền thẳng `aria-label` / `aria-roledescription`.\n */\n role=\"application\"\n aria-roledescription={context.labels.roleDescription}\n aria-label={context.labels.label}\n aria-describedby={describedBy}\n data-slot=\"cropper\"\n tabIndex={0}\n {...rootImplProps}\n ref={composedRef}\n className={cn(\n \"absolute inset-0 flex cursor-move touch-none items-center justify-center overflow-hidden outline-none select-none\",\n /*\n * ⚠ Khác thượng nguồn: bản gốc `outline-none` mà KHÔNG có chỉ báo focus nào (WCAG 2.4.7).\n * Vòng focus vẽ trên `::after` phủ kín khung chứ không trên chính phần tử: ảnh và khung\n * cắt là con `absolute` nên được vẽ ĐÈ lên outline của cha, còn `::after` là con cuối\n * cùng nên nằm trên cùng. Cùng khuôn `outline-ring` với `Button`, thụt vào trong vì hai\n * lớp bọc ngoài đều `overflow-hidden`.\n */\n \"after:pointer-events-none after:absolute after:inset-0 focus-visible:after:-outline-offset-2 focus-visible:after:outline-2 focus-visible:after:outline-ring\",\n className\n )}\n onKeyUp={onKeyUp}\n onKeyDown={onKeyDown}\n onMouseDown={onMouseDown}\n onTouchStart={onTouchStart}\n />\n {/* `hidden` vẫn làm nguồn được cho `aria-describedby`, và không bị đọc lặp ở chế độ duyệt. */}\n <span id={descriptionId} hidden>\n {context.labels.instructions}\n </span>\n <span aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {zoomStatus}\n </span>\n </>\n );\n}\n\nconst cropperMediaVariants = cva(\"will-change-transform\", {\n variants: {\n objectFit: {\n contain: \"absolute inset-0 m-auto max-h-full max-w-full\",\n cover: \"h-auto w-full\",\n \"horizontal-cover\": \"h-auto w-full\",\n \"vertical-cover\": \"h-full w-auto\"\n }\n },\n defaultVariants: {\n objectFit: \"contain\"\n }\n});\n\ninterface UseMediaComputationProps<T extends HTMLImageElement | HTMLVideoElement> {\n mediaRef: React.RefObject<T | null>;\n context: CropperContextValue;\n store: Store;\n rotation: number;\n getNaturalDimensions: (media: T) => Size;\n}\n\nfunction useMediaComputation<T extends HTMLImageElement | HTMLVideoElement>({\n mediaRef,\n context,\n store,\n rotation,\n getNaturalDimensions\n}: UseMediaComputationProps<T>) {\n const computeSizes = React.useCallback(() => {\n const media = mediaRef.current;\n const content = context.rootRef?.current;\n if (!media || !content) return;\n\n const contentRect = content.getBoundingClientRect();\n const containerAspect = contentRect.width / contentRect.height;\n const { width: naturalWidth, height: naturalHeight } = getNaturalDimensions(media);\n const isScaledDown = media.offsetWidth < naturalWidth || media.offsetHeight < naturalHeight;\n const mediaAspect = naturalWidth / naturalHeight;\n\n let renderedMediaSize: Size;\n\n if (isScaledDown) {\n const objectFitCallbacks = {\n contain: () =>\n containerAspect > mediaAspect\n ? {\n width: contentRect.height * mediaAspect,\n height: contentRect.height\n }\n : {\n width: contentRect.width,\n height: contentRect.width / mediaAspect\n },\n \"horizontal-cover\": () => ({\n width: contentRect.width,\n height: contentRect.width / mediaAspect\n }),\n \"vertical-cover\": () => ({\n width: contentRect.height * mediaAspect,\n height: contentRect.height\n }),\n cover: () =>\n containerAspect < mediaAspect\n ? {\n width: contentRect.width,\n height: contentRect.width / mediaAspect\n }\n : {\n width: contentRect.height * mediaAspect,\n height: contentRect.height\n }\n } as const;\n\n const callback = objectFitCallbacks[context.objectFit];\n renderedMediaSize = callback\n ? callback()\n : containerAspect > mediaAspect\n ? {\n width: contentRect.height * mediaAspect,\n height: contentRect.height\n }\n : {\n width: contentRect.width,\n height: contentRect.width / mediaAspect\n };\n } else {\n renderedMediaSize = {\n width: media.offsetWidth,\n height: media.offsetHeight\n };\n }\n\n const mediaSize: MediaSize = {\n ...renderedMediaSize,\n naturalWidth,\n naturalHeight\n };\n\n store.setState(\"mediaSize\", mediaSize);\n\n const cropSize = getCropSize(\n mediaSize.width,\n mediaSize.height,\n contentRect.width,\n contentRect.height,\n context.aspectRatio,\n rotation\n );\n\n store.setState(\"cropSize\", cropSize);\n\n requestAnimationFrame(() => {\n const currentState = store.getState();\n if (currentState.cropSize && currentState.mediaSize) {\n const newPosition = onPositionClamp(\n currentState.crop,\n currentState.mediaSize,\n currentState.cropSize,\n currentState.zoom,\n currentState.rotation\n );\n\n if (\n Math.abs(newPosition.x - currentState.crop.x) > 0.001 ||\n Math.abs(newPosition.y - currentState.crop.y) > 0.001\n ) {\n store.setState(\"crop\", newPosition);\n }\n }\n });\n\n return { mediaSize, cropSize };\n }, [mediaRef, context.aspectRatio, context.rootRef, context.objectFit, store, rotation, getNaturalDimensions]);\n\n return { computeSizes };\n}\n\ninterface CropperImageProps extends React.ComponentProps<\"img\">, VariantProps<typeof cropperMediaVariants> {\n asChild?: boolean;\n snapPixels?: boolean;\n}\n\n/** Ảnh cần cắt. Nhớ truyền `alt` — ảnh ở đây mang nội dung, không phải trang trí. */\nfunction CropperImage(props: CropperImageProps) {\n const { className, style, asChild, ref, onLoad, objectFit, snapPixels = false, ...imageProps } = props;\n\n const context = useCropperContext(IMAGE_NAME);\n const store = useStoreContext(IMAGE_NAME);\n const crop = useStore((state) => state.crop);\n const zoom = useStore((state) => state.zoom);\n const rotation = useStore((state) => state.rotation);\n const dpr = useDevicePixelRatio();\n\n const imageRef = React.useRef<HTMLImageElement>(null);\n const composedRef = useComposedRefs(ref, imageRef);\n\n const getNaturalDimensions = React.useCallback(\n (image: HTMLImageElement) => ({\n width: image.naturalWidth,\n height: image.naturalHeight\n }),\n []\n );\n\n const { computeSizes } = useMediaComputation({\n mediaRef: imageRef,\n context,\n store,\n rotation,\n getNaturalDimensions\n });\n\n const onMediaLoad = React.useCallback(() => {\n const image = imageRef.current;\n if (!image) return;\n\n computeSizes();\n\n onLoad?.(new Event(\"load\") as unknown as React.SyntheticEvent<HTMLImageElement>);\n }, [computeSizes, onLoad]);\n\n React.useEffect(() => {\n const image = imageRef.current;\n if (image?.complete && image.naturalWidth > 0) {\n onMediaLoad();\n }\n }, [onMediaLoad]);\n\n React.useEffect(() => {\n const content = context.rootRef?.current;\n if (!content) return;\n\n if (typeof ResizeObserver !== \"undefined\") {\n let isFirstResize = true;\n const resizeObserver = new ResizeObserver(() => {\n if (isFirstResize) {\n isFirstResize = false;\n return;\n }\n\n const callback = () => {\n const image = imageRef.current;\n if (image?.complete && image.naturalWidth > 0) {\n computeSizes();\n }\n };\n\n if (\"requestIdleCallback\" in window) {\n requestIdleCallback(callback);\n } else {\n setTimeout(callback, 16);\n }\n });\n\n resizeObserver.observe(content);\n\n return () => {\n resizeObserver.disconnect();\n };\n } else {\n const onWindowResize = () => {\n const image = imageRef.current;\n if (image?.complete && image.naturalWidth > 0) {\n computeSizes();\n }\n };\n\n window.addEventListener(\"resize\", onWindowResize);\n return () => {\n window.removeEventListener(\"resize\", onWindowResize);\n };\n }\n }, [context.rootRef, computeSizes]);\n\n const ImagePrimitive = asChild ? Slot : \"img\";\n\n return (\n <ImagePrimitive\n data-slot=\"cropper-image\"\n {...imageProps}\n ref={composedRef}\n className={cn(\n cropperMediaVariants({\n objectFit: objectFit ?? context.objectFit,\n className\n })\n )}\n style={{\n transform: snapPixels\n ? `translate(${snapToDevicePixel(crop.x, dpr)}px, ${snapToDevicePixel(crop.y, dpr)}px) rotate(${rotation}deg) scale(${zoom})`\n : `translate(${crop.x}px, ${crop.y}px) rotate(${rotation}deg) scale(${zoom})`,\n ...style\n }}\n onLoad={onMediaLoad}\n />\n );\n}\n\ninterface CropperVideoProps extends React.ComponentProps<\"video\">, VariantProps<typeof cropperMediaVariants> {\n asChild?: boolean;\n snapPixels?: boolean;\n}\n\nfunction CropperVideo(props: CropperVideoProps) {\n const { className, style, asChild, ref, onLoadedMetadata, objectFit, snapPixels = false, ...videoProps } = props;\n\n const context = useCropperContext(VIDEO_NAME);\n const store = useStoreContext(VIDEO_NAME);\n const crop = useStore((state) => state.crop);\n const zoom = useStore((state) => state.zoom);\n const rotation = useStore((state) => state.rotation);\n const dpr = useDevicePixelRatio();\n\n const videoRef = React.useRef<HTMLVideoElement>(null);\n const composedRef = useComposedRefs(ref, videoRef);\n\n const getNaturalDimensions = React.useCallback(\n (video: HTMLVideoElement) => ({\n width: video.videoWidth,\n height: video.videoHeight\n }),\n []\n );\n\n const { computeSizes } = useMediaComputation({\n mediaRef: videoRef,\n context,\n store,\n rotation,\n getNaturalDimensions\n });\n\n const onMediaLoad = React.useCallback(() => {\n const video = videoRef.current;\n if (!video) return;\n\n computeSizes();\n\n onLoadedMetadata?.(new Event(\"loadedmetadata\") as unknown as React.SyntheticEvent<HTMLVideoElement>);\n }, [computeSizes, onLoadedMetadata]);\n\n React.useEffect(() => {\n const content = context.rootRef?.current;\n if (!content) return;\n\n if (typeof ResizeObserver !== \"undefined\") {\n let isFirstResize = true;\n const resizeObserver = new ResizeObserver(() => {\n if (isFirstResize) {\n isFirstResize = false;\n return;\n }\n\n const callback = () => {\n const video = videoRef.current;\n if (video && video.videoWidth > 0 && video.videoHeight > 0) {\n computeSizes();\n }\n };\n\n if (\"requestIdleCallback\" in window) {\n requestIdleCallback(callback);\n } else {\n setTimeout(callback, 16);\n }\n });\n\n resizeObserver.observe(content);\n\n return () => {\n resizeObserver.disconnect();\n };\n } else {\n const onWindowResize = () => {\n const video = videoRef.current;\n if (video && video.videoWidth > 0 && video.videoHeight > 0) {\n computeSizes();\n }\n };\n\n window.addEventListener(\"resize\", onWindowResize);\n return () => {\n window.removeEventListener(\"resize\", onWindowResize);\n };\n }\n }, [context.rootRef, computeSizes]);\n\n const VideoPrimitive = asChild ? Slot : \"video\";\n\n return (\n <VideoPrimitive\n data-slot=\"cropper-video\"\n autoPlay\n playsInline\n loop\n muted\n controls={false}\n {...videoProps}\n ref={composedRef}\n className={cn(\n cropperMediaVariants({\n objectFit: objectFit ?? context.objectFit,\n className\n })\n )}\n style={{\n transform: snapPixels\n ? `translate(${snapToDevicePixel(crop.x, dpr)}px, ${snapToDevicePixel(crop.y, dpr)}px) rotate(${rotation}deg) scale(${zoom})`\n : `translate(${crop.x}px, ${crop.y}px) rotate(${rotation}deg) scale(${zoom})`,\n ...style\n }}\n onLoadedMetadata={onMediaLoad}\n />\n );\n}\n\n/*\n * ⚠ Khác thượng nguồn — màu. Khung cắt nằm trên ẢNH, không trên nền trang, nên cặp màu ở đây\n * không đổi theo theme (ảnh là ảnh ở cả sáng lẫn tối):\n * · vùng ngoài khung tối đi bằng MÀU LỚP PHỦ của `DialogOverlay` (`ink/60`, `ink/80` ở tối),\n * vẽ bằng bóng 9999em như bản gốc — bản gốc viết thẳng `rgba(0,0,0,0.5)`. Đây là một tấm\n * phủ, KHÔNG phải bóng đổ độ cao, nên không đi qua thang `shadow-card`/`shadow-overlay`;\n * màu vẫn là token nên đổi theo `--ink` của app.\n * · khung là `paper` (bản gốc `white/90`): màu đi cặp với lớp phủ mực, sáng ở MỌI theme và mọi\n * bảng màu. Ca tệ nhất — điểm ảnh TRẮNG nằm dưới lớp phủ: 4,65:1 (ink/60) · 9,60:1 (ink/80),\n * ≥ 3:1 cho thành phần phi văn bản. `--background`/`--foreground` KHÔNG dùng được: chúng đảo\n * theo theme trong khi lớp phủ thì luôn tối.\n * · lưới 1/3 (`paper/50`) là đường gióng bố cục thuần trang trí, nằm trên phần ảnh KHÔNG phủ.\n */\nconst cropperAreaVariants = cva(\n \"absolute top-1/2 left-1/2 box-border -translate-x-1/2 -translate-y-1/2 overflow-hidden border border-[2.5px] border-paper shadow-[0_0_0_9999em] shadow-ink/60 dark:shadow-ink/80\",\n {\n variants: {\n shape: {\n rectangle: \"\",\n circle: \"rounded-full\"\n },\n withGrid: {\n true: \"before:absolute before:top-0 before:right-1/3 before:bottom-0 before:left-1/3 before:box-border before:border before:border-t-0 before:border-b-0 before:border-paper/50 before:content-[''] after:absolute after:top-1/3 after:right-0 after:bottom-1/3 after:left-0 after:box-border after:border after:border-r-0 after:border-l-0 after:border-paper/50 after:content-['']\",\n false: \"\"\n }\n },\n defaultVariants: {\n shape: \"rectangle\",\n withGrid: false\n }\n }\n);\n\ninterface CropperAreaProps extends DivProps, VariantProps<typeof cropperAreaVariants> {\n snapPixels?: boolean;\n}\n\nfunction CropperArea(props: CropperAreaProps) {\n const { className, style, asChild, ref, snapPixels = false, shape, withGrid, ...areaProps } = props;\n\n const context = useCropperContext(AREA_NAME);\n const cropSize = useStore((state) => state.cropSize);\n\n if (!cropSize) return null;\n\n const AreaPrimitive = asChild ? Slot : \"div\";\n\n return (\n <AreaPrimitive\n data-slot=\"cropper-area\"\n {...areaProps}\n ref={ref}\n className={cn(\n cropperAreaVariants({\n shape: shape ?? context.shape,\n withGrid: withGrid ?? context.withGrid,\n className\n })\n )}\n style={{\n width: snapPixels ? Math.round(cropSize.width) : cropSize.width,\n height: snapPixels ? Math.round(cropSize.height) : cropSize.height,\n ...style\n }}\n />\n );\n}\n\nexport {\n type Area as CropperAreaData,\n Cropper,\n CropperArea,\n type CropperAreaProps,\n CropperImage,\n type CropperImageProps,\n type CropperProps,\n CropperVideo,\n type CropperVideoProps,\n type MediaSize as CropperMediaSize,\n type ObjectFit as CropperObjectFit,\n type Point as CropperPoint,\n type Shape as CropperShape,\n type Size as CropperSize,\n useStore as useCropper\n};\n"],"mappings":"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","names":[]}
@@ -9,7 +9,7 @@ import { Drawer as Drawer$1 } from 'vaul';
9
9
  * style bám theo thuộc tính `data-vaul-drawer-direction` mà vaul tự gắn.
10
10
  */
11
11
  type DrawerProps = React.ComponentProps<typeof Drawer$1.Root>;
12
- declare function Drawer({ ...props }: DrawerProps): React.JSX.Element;
12
+ declare function Drawer({ autoFocus, ...props }: DrawerProps): React.JSX.Element;
13
13
  type DrawerTriggerProps = React.ComponentProps<typeof Drawer$1.Trigger>;
14
14
  declare function DrawerTrigger({ ...props }: DrawerTriggerProps): React.JSX.Element;
15
15
  type DrawerPortalProps = React.ComponentProps<typeof Drawer$1.Portal>;
@@ -2,8 +2,8 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Drawer as DrawerPrimitive } from "vaul";
4
4
  import { cn } from "../../lib/cn.js";
5
- function Drawer({ ...props }) {
6
- return /* @__PURE__ */ jsx(DrawerPrimitive.Root, { "data-slot": "drawer", ...props });
5
+ function Drawer({ autoFocus = true, ...props }) {
6
+ return /* @__PURE__ */ jsx(DrawerPrimitive.Root, { "data-slot": "drawer", autoFocus, ...props });
7
7
  }
8
8
  function DrawerTrigger({ ...props }) {
9
9
  return /* @__PURE__ */ jsx(DrawerPrimitive.Trigger, { "data-slot": "drawer-trigger", ...props });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/ui/drawer.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { Drawer as DrawerPrimitive } from \"vaul\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Drawer — panel kéo-thả (vaul), có quán tính và tay cầm kéo như app di động.\n *\n * Khác `Sheet` (Radix Dialog, trượt cứng): Drawer cho phép người dùng vuốt để đóng,\n * hợp với màn hình cảm ứng. Hướng đặt bằng prop `direction` của vaul —\n * style bám theo thuộc tính `data-vaul-drawer-direction` mà vaul tự gắn.\n */\n\nexport type DrawerProps = React.ComponentProps<typeof DrawerPrimitive.Root>;\n\nfunction Drawer({ ...props }: DrawerProps) {\n return <DrawerPrimitive.Root data-slot=\"drawer\" {...props} />;\n}\n\nexport type DrawerTriggerProps = React.ComponentProps<typeof DrawerPrimitive.Trigger>;\n\nfunction DrawerTrigger({ ...props }: DrawerTriggerProps) {\n return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />;\n}\n\nexport type DrawerPortalProps = React.ComponentProps<typeof DrawerPrimitive.Portal>;\n\nfunction DrawerPortal({ ...props }: DrawerPortalProps) {\n return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />;\n}\n\nexport type DrawerCloseProps = React.ComponentProps<typeof DrawerPrimitive.Close>;\n\nfunction DrawerClose({ ...props }: DrawerCloseProps) {\n return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />;\n}\n\nexport type DrawerOverlayProps = React.ComponentProps<typeof DrawerPrimitive.Overlay>;\n\nfunction DrawerOverlay({ className, ...props }: DrawerOverlayProps) {\n return (\n <DrawerPrimitive.Overlay\n data-slot=\"drawer-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-ink/60 dark:bg-ink/80 backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type DrawerContentProps = React.ComponentProps<typeof DrawerPrimitive.Content>;\n\nfunction DrawerContent({ className, children, ...props }: DrawerContentProps) {\n return (\n <DrawerPortal data-slot=\"drawer-portal\">\n <DrawerOverlay />\n <DrawerPrimitive.Content\n data-slot=\"drawer-content\"\n className={cn(\n /*\n * `overflow-y-auto`: `max-h-[80vh]` ở hai hướng trên/dưới đặt trần chiều cao nhưng\n * KHÔNG có gì cuộn phần vượt trần — nội dung dài bị cắt im lặng. Cùng lỗi với\n * `DialogContent` trước 1.3.2.\n */\n \"group/drawer-content bg-background fixed z-50 flex h-auto flex-col overflow-y-auto\",\n \"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b\",\n \"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t\",\n \"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm\",\n \"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm\",\n className\n )}\n {...props}\n >\n {/*\n Tay cầm kéo — chỉ hiện khi drawer trượt từ đáy lên.\n Màu `--control-edge`, KHÔNG phải `--muted`: mảng `bg-*` này CHÍNH LÀ hình dạng của dấu\n duy nhất nói \"kéo ở đây\", nên nó chịu ngưỡng 3:1 (WCAG 1.4.11) chứ không phải là một\n bề mặt. `bg-muted` trên `bg-background` của DrawerContent chỉ 1,07:1 (light) · 1,17:1\n (dark) — coi như không hiện. `bg-control-edge`: 3,83:1 (light) · 4,94:1 (dark) ✓\n */}\n <div className=\"bg-control-edge mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n {children}\n </DrawerPrimitive.Content>\n </DrawerPortal>\n );\n}\n\nexport type DrawerHeaderProps = React.ComponentProps<\"div\">;\n\nfunction DrawerHeader({ className, ...props }: DrawerHeaderProps) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn(\n \"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type DrawerFooterProps = React.ComponentProps<\"div\">;\n\nfunction DrawerFooter({ className, ...props }: DrawerFooterProps) {\n return <div data-slot=\"drawer-footer\" className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />;\n}\n\nexport type DrawerTitleProps = React.ComponentProps<typeof DrawerPrimitive.Title>;\n\nfunction DrawerTitle({ className, ...props }: DrawerTitleProps) {\n return (\n <DrawerPrimitive.Title\n data-slot=\"drawer-title\"\n className={cn(\"text-foreground font-semibold\", className)}\n {...props}\n />\n );\n}\n\nexport type DrawerDescriptionProps = React.ComponentProps<typeof DrawerPrimitive.Description>;\n\nfunction DrawerDescription({ className, ...props }: DrawerDescriptionProps) {\n return (\n <DrawerPrimitive.Description\n data-slot=\"drawer-description\"\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Drawer,\n DrawerClose,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerOverlay,\n DrawerPortal,\n DrawerTitle,\n DrawerTrigger\n};\n"],"mappings":";AAiBS,cA0CH,YA1CG;AAdT,SAAS,UAAU,uBAAuB;AAC1C,SAAS,UAAU;AAYnB,SAAS,OAAO,EAAE,GAAG,MAAM,GAAgB;AACzC,SAAO,oBAAC,gBAAgB,MAAhB,EAAqB,aAAU,UAAU,GAAG,OAAO;AAC7D;AAIA,SAAS,cAAc,EAAE,GAAG,MAAM,GAAuB;AACvD,SAAO,oBAAC,gBAAgB,SAAhB,EAAwB,aAAU,kBAAkB,GAAG,OAAO;AACxE;AAIA,SAAS,aAAa,EAAE,GAAG,MAAM,GAAsB;AACrD,SAAO,oBAAC,gBAAgB,QAAhB,EAAuB,aAAU,iBAAiB,GAAG,OAAO;AACtE;AAIA,SAAS,YAAY,EAAE,GAAG,MAAM,GAAqB;AACnD,SAAO,oBAAC,gBAAgB,OAAhB,EAAsB,aAAU,gBAAgB,GAAG,OAAO;AACpE;AAIA,SAAS,cAAc,EAAE,WAAW,GAAG,MAAM,GAAuB;AAClE,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,MAAM,GAAuB;AAC5E,SACE,qBAAC,gBAAa,aAAU,iBACtB;AAAA,wBAAC,iBAAc;AAAA,IACf;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QASJ;AAAA,8BAAC,SAAI,WAAU,0IAAyI;AAAA,UACvJ;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SAAO,oBAAC,SAAI,aAAU,iBAAgB,WAAW,GAAG,mCAAmC,SAAS,GAAI,GAAG,OAAO;AAChH;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAA2B;AAC1E,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/ui/drawer.tsx"],"sourcesContent":["\"use client\";\n\nimport type * as React from \"react\";\nimport { Drawer as DrawerPrimitive } from \"vaul\";\nimport { cn } from \"../../lib/cn.js\";\n\n/**\n * Drawer — panel kéo-thả (vaul), có quán tính và tay cầm kéo như app di động.\n *\n * Khác `Sheet` (Radix Dialog, trượt cứng): Drawer cho phép người dùng vuốt để đóng,\n * hợp với màn hình cảm ứng. Hướng đặt bằng prop `direction` của vaul —\n * style bám theo thuộc tính `data-vaul-drawer-direction` mà vaul tự gắn.\n */\n\nexport type DrawerProps = React.ComponentProps<typeof DrawerPrimitive.Root>;\n\n/*\n * ⚠ `autoFocus` mặc định `true` — NGƯỢC với vaul. vaul 1.1.2 để `autoFocus = false`, và khi đó\n * `Drawer.Content` gọi `preventDefault()` lên `onOpenAutoFocus` của Radix Dialog bên dưới: focus\n * ở lại trên nút mở, NẰM SAU lớp phủ. Bẫy focus của Radix (FocusScope) chỉ giữ focus đã vào\n * trong nó, nên nó không bao giờ bắt đầu — Tab đi khắp trang phía sau một hộp modal (WCAG 2.4.3).\n * Đã đo trên trình duyệt thật ở 375px, lúc port `ResponsiveDialog` (2.1.0). Với `true`, Radix\n * đưa focus vào phần tử focus được ĐẦU TIÊN trong ngăn kéo — đúng như `Dialog`/`Sheet` của gói.\n *\n * Cái giá: phần tử đầu tiên là ô nhập thì bàn phím ảo bật lên ngay lúc mở. Muốn focus chỗ khác\n * thì gọi `event.preventDefault()` trong `onOpenAutoFocus` của `DrawerContent` rồi tự `focus()`\n * phần tử muốn nhận. `autoFocus={false}` vẫn trả về hành vi gốc của vaul, nhưng khi đó chính app\n * phải tự đưa focus vào trong — không làm thì ngăn kéo lại thành một modal mà bàn phím đi xuyên qua.\n */\nfunction Drawer({ autoFocus = true, ...props }: DrawerProps) {\n return <DrawerPrimitive.Root data-slot=\"drawer\" autoFocus={autoFocus} {...props} />;\n}\n\nexport type DrawerTriggerProps = React.ComponentProps<typeof DrawerPrimitive.Trigger>;\n\nfunction DrawerTrigger({ ...props }: DrawerTriggerProps) {\n return <DrawerPrimitive.Trigger data-slot=\"drawer-trigger\" {...props} />;\n}\n\nexport type DrawerPortalProps = React.ComponentProps<typeof DrawerPrimitive.Portal>;\n\nfunction DrawerPortal({ ...props }: DrawerPortalProps) {\n return <DrawerPrimitive.Portal data-slot=\"drawer-portal\" {...props} />;\n}\n\nexport type DrawerCloseProps = React.ComponentProps<typeof DrawerPrimitive.Close>;\n\nfunction DrawerClose({ ...props }: DrawerCloseProps) {\n return <DrawerPrimitive.Close data-slot=\"drawer-close\" {...props} />;\n}\n\nexport type DrawerOverlayProps = React.ComponentProps<typeof DrawerPrimitive.Overlay>;\n\nfunction DrawerOverlay({ className, ...props }: DrawerOverlayProps) {\n return (\n <DrawerPrimitive.Overlay\n data-slot=\"drawer-overlay\"\n className={cn(\n \"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-ink/60 dark:bg-ink/80 backdrop-blur-sm\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type DrawerContentProps = React.ComponentProps<typeof DrawerPrimitive.Content>;\n\nfunction DrawerContent({ className, children, ...props }: DrawerContentProps) {\n return (\n <DrawerPortal data-slot=\"drawer-portal\">\n <DrawerOverlay />\n <DrawerPrimitive.Content\n data-slot=\"drawer-content\"\n className={cn(\n /*\n * `overflow-y-auto`: `max-h-[80vh]` ở hai hướng trên/dưới đặt trần chiều cao nhưng\n * KHÔNG có gì cuộn phần vượt trần — nội dung dài bị cắt im lặng. Cùng lỗi với\n * `DialogContent` trước 1.3.2.\n */\n \"group/drawer-content bg-background fixed z-50 flex h-auto flex-col overflow-y-auto\",\n \"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b\",\n \"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t\",\n \"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm\",\n \"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm\",\n className\n )}\n {...props}\n >\n {/*\n Tay cầm kéo — chỉ hiện khi drawer trượt từ đáy lên.\n Màu `--control-edge`, KHÔNG phải `--muted`: mảng `bg-*` này CHÍNH LÀ hình dạng của dấu\n duy nhất nói \"kéo ở đây\", nên nó chịu ngưỡng 3:1 (WCAG 1.4.11) chứ không phải là một\n bề mặt. `bg-muted` trên `bg-background` của DrawerContent chỉ 1,07:1 (light) · 1,17:1\n (dark) — coi như không hiện. `bg-control-edge`: 3,83:1 (light) · 4,94:1 (dark) ✓\n */}\n <div className=\"bg-control-edge mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full group-data-[vaul-drawer-direction=bottom]/drawer-content:block\" />\n {children}\n </DrawerPrimitive.Content>\n </DrawerPortal>\n );\n}\n\nexport type DrawerHeaderProps = React.ComponentProps<\"div\">;\n\nfunction DrawerHeader({ className, ...props }: DrawerHeaderProps) {\n return (\n <div\n data-slot=\"drawer-header\"\n className={cn(\n \"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left\",\n className\n )}\n {...props}\n />\n );\n}\n\nexport type DrawerFooterProps = React.ComponentProps<\"div\">;\n\nfunction DrawerFooter({ className, ...props }: DrawerFooterProps) {\n return <div data-slot=\"drawer-footer\" className={cn(\"mt-auto flex flex-col gap-2 p-4\", className)} {...props} />;\n}\n\nexport type DrawerTitleProps = React.ComponentProps<typeof DrawerPrimitive.Title>;\n\nfunction DrawerTitle({ className, ...props }: DrawerTitleProps) {\n return (\n <DrawerPrimitive.Title\n data-slot=\"drawer-title\"\n className={cn(\"text-foreground font-semibold\", className)}\n {...props}\n />\n );\n}\n\nexport type DrawerDescriptionProps = React.ComponentProps<typeof DrawerPrimitive.Description>;\n\nfunction DrawerDescription({ className, ...props }: DrawerDescriptionProps) {\n return (\n <DrawerPrimitive.Description\n data-slot=\"drawer-description\"\n className={cn(\"text-muted-foreground text-sm\", className)}\n {...props}\n />\n );\n}\n\nexport {\n Drawer,\n DrawerClose,\n DrawerContent,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerOverlay,\n DrawerPortal,\n DrawerTitle,\n DrawerTrigger\n};\n"],"mappings":";AA8BS,cA0CH,YA1CG;AA3BT,SAAS,UAAU,uBAAuB;AAC1C,SAAS,UAAU;AAyBnB,SAAS,OAAO,EAAE,YAAY,MAAM,GAAG,MAAM,GAAgB;AAC3D,SAAO,oBAAC,gBAAgB,MAAhB,EAAqB,aAAU,UAAS,WAAuB,GAAG,OAAO;AACnF;AAIA,SAAS,cAAc,EAAE,GAAG,MAAM,GAAuB;AACvD,SAAO,oBAAC,gBAAgB,SAAhB,EAAwB,aAAU,kBAAkB,GAAG,OAAO;AACxE;AAIA,SAAS,aAAa,EAAE,GAAG,MAAM,GAAsB;AACrD,SAAO,oBAAC,gBAAgB,QAAhB,EAAuB,aAAU,iBAAiB,GAAG,OAAO;AACtE;AAIA,SAAS,YAAY,EAAE,GAAG,MAAM,GAAqB;AACnD,SAAO,oBAAC,gBAAgB,OAAhB,EAAsB,aAAU,gBAAgB,GAAG,OAAO;AACpE;AAIA,SAAS,cAAc,EAAE,WAAW,GAAG,MAAM,GAAuB;AAClE,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,cAAc,EAAE,WAAW,UAAU,GAAG,MAAM,GAAuB;AAC5E,SACE,qBAAC,gBAAa,aAAU,iBACtB;AAAA,wBAAC,iBAAc;AAAA,IACf;AAAA,MAAC,gBAAgB;AAAA,MAAhB;AAAA,QACC,aAAU;AAAA,QACV,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMT;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QASJ;AAAA,8BAAC,SAAI,WAAU,0IAAyI;AAAA,UACvJ;AAAA;AAAA;AAAA,IACH;AAAA,KACF;AAEJ;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,aAAU;AAAA,MACV,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,aAAa,EAAE,WAAW,GAAG,MAAM,GAAsB;AAChE,SAAO,oBAAC,SAAI,aAAU,iBAAgB,WAAW,GAAG,mCAAmC,SAAS,GAAI,GAAG,OAAO;AAChH;AAIA,SAAS,YAAY,EAAE,WAAW,GAAG,MAAM,GAAqB;AAC9D,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;AAIA,SAAS,kBAAkB,EAAE,WAAW,GAAG,MAAM,GAA2B;AAC1E,SACE;AAAA,IAAC,gBAAgB;AAAA,IAAhB;AAAA,MACC,aAAU;AAAA,MACV,WAAW,GAAG,iCAAiC,SAAS;AAAA,MACvD,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,61 @@
1
+ import { UniqueIdentifier } from '@dnd-kit/core';
2
+
3
+ /**
4
+ * Nhãn mặc định của `Kanban` — lời xướng cho trình đọc màn hình và tên của tay cầm kéo.
5
+ *
6
+ * Module **CỐ Ý không có `"use client"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy đủ:
7
+ * mọi export của một file `"use client"` đến tay Server Component dưới dạng client-reference
8
+ * proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. `DEFAULT_KANBAN_LABELS` là một object
9
+ * literal (dù phần lớn giá trị là HÀM) nên phải sống ngoài ranh giới client.
10
+ *
11
+ * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `kanban.tsx` chỉ được `import`, KHÔNG được
12
+ * `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).
13
+ */
14
+
15
+ /** Dữ liệu dnd-kit đã tính sẵn, đưa vào các hàm xướng của `KanbanLabels`. */
16
+ interface KanbanAnnouncementInfo {
17
+ /** `"column"` khi đang kéo cả CỘT, `"item"` khi đang kéo một THẺ. */
18
+ variant: "column" | "item";
19
+ /** `value` của cột/thẻ đang kéo — dùng để tra ra TÊN hiển thị khi tự viết nhãn. */
20
+ value: UniqueIdentifier;
21
+ /** Vị trí hiện tại, đếm từ 1. */
22
+ position: number;
23
+ /** Tổng số cột (khi kéo cột) hoặc tổng số thẻ trong cột đang đứng (khi kéo thẻ). */
24
+ total: number;
25
+ /**
26
+ * `value` của cột ĐÍCH — chỉ có khi thẻ vừa sang một cột KHÁC; `undefined` khi thẻ còn trong
27
+ * cột cũ, và luôn `undefined` khi kéo cột. Là khoá máy (`"inProgress"`), không phải tên hiển
28
+ * thị: app có tên cột thì tự viết hàm xướng và tra tên từ khoá này.
29
+ */
30
+ column?: UniqueIdentifier;
31
+ }
32
+ /**
33
+ * Mọi chuỗi mà `Kanban` tự đọc/tự gắn. Chuỗi có số chèn vào là HÀM, không phải chuỗi mẫu.
34
+ *
35
+ * Bốn hàm đầu đi vào `accessibility.announcements` của dnd-kit (vùng `aria-live`). App vẫn có thể
36
+ * truyền thẳng `accessibility.announcements` như thượng nguồn — khi đó nó thắng hoàn toàn.
37
+ */
38
+ interface KanbanLabels {
39
+ /** Xướng lúc nhấc một cột/thẻ lên. */
40
+ pickedUp: (info: KanbanAnnouncementInfo) => string;
41
+ /** Xướng mỗi lần cột/thẻ đang kéo đổi chỗ. */
42
+ moved: (info: KanbanAnnouncementInfo) => string;
43
+ /** Xướng lúc thả. */
44
+ dropped: (info: KanbanAnnouncementInfo) => string;
45
+ /** Xướng lúc huỷ kéo (Escape) — cột/thẻ trở về chỗ cũ. */
46
+ cancelled: (info: Pick<KanbanAnnouncementInfo, "variant" | "value">) => string;
47
+ /** Hướng dẫn thao tác bàn phím, gắn vào mọi phần tử kéo được qua `aria-describedby`. */
48
+ instructions: string;
49
+ /** `aria-roledescription` của cột kéo được (và tay cầm của nó) — dnd-kit mặc định là "sortable". */
50
+ columnRoleDescription: string;
51
+ /** `aria-roledescription` của thẻ kéo được (và tay cầm của nó) — dnd-kit mặc định là "sortable". */
52
+ itemRoleDescription: string;
53
+ /** Tên mặc định của `KanbanColumnHandle` — tay cầm thường chỉ có một icon. */
54
+ columnHandleLabel: string;
55
+ /** Tên mặc định của `KanbanItemHandle` — tay cầm thường chỉ có một icon. */
56
+ itemHandleLabel: string;
57
+ }
58
+ /** Nhãn tiếng Việt mặc định — mọi khoá override được qua prop `labels` của `Kanban`. */
59
+ declare const DEFAULT_KANBAN_LABELS: KanbanLabels;
60
+
61
+ export { DEFAULT_KANBAN_LABELS, type KanbanAnnouncementInfo, type KanbanLabels };
@@ -0,0 +1,21 @@
1
+ function noun(variant) {
2
+ return variant === "column" ? "c\u1ED9t" : "th\u1EBB";
3
+ }
4
+ function capitalizedNoun(variant) {
5
+ return variant === "column" ? "C\u1ED9t" : "Th\u1EBB";
6
+ }
7
+ const DEFAULT_KANBAN_LABELS = {
8
+ pickedUp: ({ variant, position, total }) => `\u0110\xE3 nh\u1EA5c ${noun(variant)} \u1EDF v\u1ECB tr\xED ${position} tr\xEAn ${total}.`,
9
+ moved: ({ variant, position, total, column }) => column === void 0 ? `${capitalizedNoun(variant)} \u0111ang \u1EDF v\u1ECB tr\xED ${position} tr\xEAn ${total}.` : `${capitalizedNoun(variant)} \u0111ang \u1EDF v\u1ECB tr\xED ${position} tr\xEAn ${total} trong c\u1ED9t ${column}.`,
10
+ dropped: ({ variant, position, total, column }) => column === void 0 ? `\u0110\xE3 th\u1EA3 ${noun(variant)} \u1EDF v\u1ECB tr\xED ${position} tr\xEAn ${total}.` : `\u0110\xE3 th\u1EA3 ${noun(variant)} \u1EDF v\u1ECB tr\xED ${position} tr\xEAn ${total} trong c\u1ED9t ${column}.`,
11
+ cancelled: ({ variant }) => `\u0110\xE3 hu\u1EF7 k\xE9o. ${capitalizedNoun(variant)} tr\u1EDF v\u1EC1 v\u1ECB tr\xED ban \u0111\u1EA7u.`,
12
+ instructions: "\u0110\u1EC3 nh\u1EA5c m\u1ED9t th\u1EBB ho\u1EB7c m\u1ED9t c\u1ED9t, nh\u1EA5n Space ho\u1EB7c Enter. Trong l\xFAc k\xE9o, d\xF9ng c\xE1c ph\xEDm m\u0169i t\xEAn \u0111\u1EC3 di chuy\u1EC3n. Nh\u1EA5n Space ho\u1EB7c Enter l\u1EA7n n\u1EEFa \u0111\u1EC3 th\u1EA3 v\xE0o v\u1ECB tr\xED m\u1EDBi, ho\u1EB7c nh\u1EA5n Escape \u0111\u1EC3 hu\u1EF7.",
13
+ columnRoleDescription: "c\u1ED9t k\xE9o th\u1EA3 \u0111\u01B0\u1EE3c",
14
+ itemRoleDescription: "th\u1EBB k\xE9o th\u1EA3 \u0111\u01B0\u1EE3c",
15
+ columnHandleLabel: "K\xE9o \u0111\u1EC3 di chuy\u1EC3n c\u1ED9t",
16
+ itemHandleLabel: "K\xE9o \u0111\u1EC3 di chuy\u1EC3n th\u1EBB"
17
+ };
18
+ export {
19
+ DEFAULT_KANBAN_LABELS
20
+ };
21
+ //# sourceMappingURL=kanban-constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/ui/kanban-constants.ts"],"sourcesContent":["/**\n * Nhãn mặc định của `Kanban` — lời xướng cho trình đọc màn hình và tên của tay cầm kéo.\n *\n * Module **CỐ Ý không có `\"use client\"`** — xem `src/chart/chart-colors.ts` để biết lý do đầy đủ:\n * mọi export của một file `\"use client\"` đến tay Server Component dưới dạng client-reference\n * proxy, đọc ra `undefined` mà KHÔNG throw, KHÔNG warning. `DEFAULT_KANBAN_LABELS` là một object\n * literal (dù phần lớn giá trị là HÀM) nên phải sống ngoài ranh giới client.\n *\n * ⚠ Barrel `src/index.ts` xuất module này TRỰC TIẾP. `kanban.tsx` chỉ được `import`, KHÔNG được\n * `export` lại (re-export qua file client là vô hiệu, và đụng tên với `export *`).\n */\n\nimport type { UniqueIdentifier } from \"@dnd-kit/core\";\n\n/** Dữ liệu dnd-kit đã tính sẵn, đưa vào các hàm xướng của `KanbanLabels`. */\nexport interface KanbanAnnouncementInfo {\n /** `\"column\"` khi đang kéo cả CỘT, `\"item\"` khi đang kéo một THẺ. */\n variant: \"column\" | \"item\";\n /** `value` của cột/thẻ đang kéo — dùng để tra ra TÊN hiển thị khi tự viết nhãn. */\n value: UniqueIdentifier;\n /** Vị trí hiện tại, đếm từ 1. */\n position: number;\n /** Tổng số cột (khi kéo cột) hoặc tổng số thẻ trong cột đang đứng (khi kéo thẻ). */\n total: number;\n /**\n * `value` của cột ĐÍCH — chỉ có khi thẻ vừa sang một cột KHÁC; `undefined` khi thẻ còn trong\n * cột cũ, và luôn `undefined` khi kéo cột. Là khoá máy (`\"inProgress\"`), không phải tên hiển\n * thị: app có tên cột thì tự viết hàm xướng và tra tên từ khoá này.\n */\n column?: UniqueIdentifier;\n}\n\n/**\n * Mọi chuỗi mà `Kanban` tự đọc/tự gắn. Chuỗi có số chèn vào là HÀM, không phải chuỗi mẫu.\n *\n * Bốn hàm đầu đi vào `accessibility.announcements` của dnd-kit (vùng `aria-live`). App vẫn có thể\n * truyền thẳng `accessibility.announcements` như thượng nguồn — khi đó nó thắng hoàn toàn.\n */\nexport interface KanbanLabels {\n /** Xướng lúc nhấc một cột/thẻ lên. */\n pickedUp: (info: KanbanAnnouncementInfo) => string;\n /** Xướng mỗi lần cột/thẻ đang kéo đổi chỗ. */\n moved: (info: KanbanAnnouncementInfo) => string;\n /** Xướng lúc thả. */\n dropped: (info: KanbanAnnouncementInfo) => string;\n /** Xướng lúc huỷ kéo (Escape) — cột/thẻ trở về chỗ cũ. */\n cancelled: (info: Pick<KanbanAnnouncementInfo, \"variant\" | \"value\">) => string;\n /** Hướng dẫn thao tác bàn phím, gắn vào mọi phần tử kéo được qua `aria-describedby`. */\n instructions: string;\n /** `aria-roledescription` của cột kéo được (và tay cầm của nó) — dnd-kit mặc định là \"sortable\". */\n columnRoleDescription: string;\n /** `aria-roledescription` của thẻ kéo được (và tay cầm của nó) — dnd-kit mặc định là \"sortable\". */\n itemRoleDescription: string;\n /** Tên mặc định của `KanbanColumnHandle` — tay cầm thường chỉ có một icon. */\n columnHandleLabel: string;\n /** Tên mặc định của `KanbanItemHandle` — tay cầm thường chỉ có một icon. */\n itemHandleLabel: string;\n}\n\nfunction noun(variant: KanbanAnnouncementInfo[\"variant\"]) {\n return variant === \"column\" ? \"cột\" : \"thẻ\";\n}\n\nfunction capitalizedNoun(variant: KanbanAnnouncementInfo[\"variant\"]) {\n return variant === \"column\" ? \"Cột\" : \"Thẻ\";\n}\n\n/** Nhãn tiếng Việt mặc định — mọi khoá override được qua prop `labels` của `Kanban`. */\nexport const DEFAULT_KANBAN_LABELS: KanbanLabels = {\n pickedUp: ({ variant, position, total }) => `Đã nhấc ${noun(variant)} ở vị trí ${position} trên ${total}.`,\n moved: ({ variant, position, total, column }) =>\n column === undefined\n ? `${capitalizedNoun(variant)} đang ở vị trí ${position} trên ${total}.`\n : `${capitalizedNoun(variant)} đang ở vị trí ${position} trên ${total} trong cột ${column}.`,\n dropped: ({ variant, position, total, column }) =>\n column === undefined\n ? `Đã thả ${noun(variant)} ở vị trí ${position} trên ${total}.`\n : `Đã thả ${noun(variant)} ở vị trí ${position} trên ${total} trong cột ${column}.`,\n cancelled: ({ variant }) => `Đã huỷ kéo. ${capitalizedNoun(variant)} trở về vị trí ban đầu.`,\n instructions:\n \"Để nhấc một thẻ hoặc một cột, nhấn Space hoặc Enter. Trong lúc kéo, dùng các phím mũi tên để di chuyển. Nhấn Space hoặc Enter lần nữa để thả vào vị trí mới, hoặc nhấn Escape để huỷ.\",\n columnRoleDescription: \"cột kéo thả được\",\n itemRoleDescription: \"thẻ kéo thả được\",\n columnHandleLabel: \"Kéo để di chuyển cột\",\n itemHandleLabel: \"Kéo để di chuyển thẻ\"\n};\n"],"mappings":"AA2DA,SAAS,KAAK,SAA4C;AACxD,SAAO,YAAY,WAAW,aAAQ;AACxC;AAEA,SAAS,gBAAgB,SAA4C;AACnE,SAAO,YAAY,WAAW,aAAQ;AACxC;AAGO,MAAM,wBAAsC;AAAA,EACjD,UAAU,CAAC,EAAE,SAAS,UAAU,MAAM,MAAM,wBAAW,KAAK,OAAO,CAAC,0BAAa,QAAQ,YAAS,KAAK;AAAA,EACvG,OAAO,CAAC,EAAE,SAAS,UAAU,OAAO,OAAO,MACzC,WAAW,SACP,GAAG,gBAAgB,OAAO,CAAC,oCAAkB,QAAQ,YAAS,KAAK,MACnE,GAAG,gBAAgB,OAAO,CAAC,oCAAkB,QAAQ,YAAS,KAAK,mBAAc,MAAM;AAAA,EAC7F,SAAS,CAAC,EAAE,SAAS,UAAU,OAAO,OAAO,MAC3C,WAAW,SACP,uBAAU,KAAK,OAAO,CAAC,0BAAa,QAAQ,YAAS,KAAK,MAC1D,uBAAU,KAAK,OAAO,CAAC,0BAAa,QAAQ,YAAS,KAAK,mBAAc,MAAM;AAAA,EACpF,WAAW,CAAC,EAAE,QAAQ,MAAM,+BAAe,gBAAgB,OAAO,CAAC;AAAA,EACnE,cACE;AAAA,EACF,uBAAuB;AAAA,EACvB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,iBAAiB;AACnB;","names":[]}
@@ -0,0 +1,72 @@
1
+ import { DndContextProps, UniqueIdentifier, DragEndEvent, DragOverlay } from '@dnd-kit/core';
2
+ import { SortableContextProps } from '@dnd-kit/sortable';
3
+ import * as React from 'react';
4
+ import { KanbanLabels } from './kanban-constants.js';
5
+
6
+ interface GetItemValue<T> {
7
+ /**
8
+ * Trả về định danh duy nhất của mỗi thẻ. BẮT BUỘC khi thẻ là object.
9
+ * @example getItemValue={(item) => item.id}
10
+ */
11
+ getItemValue: (item: T) => UniqueIdentifier;
12
+ }
13
+ type KanbanProps<T> = Omit<DndContextProps, "collisionDetection"> & (T extends object ? GetItemValue<T> : Partial<GetItemValue<T>>) & {
14
+ value: Record<UniqueIdentifier, T[]>;
15
+ onValueChange?: (columns: Record<UniqueIdentifier, T[]>) => void;
16
+ onMove?: (event: DragEndEvent & {
17
+ activeIndex: number;
18
+ overIndex: number;
19
+ }) => void;
20
+ strategy?: SortableContextProps["strategy"];
21
+ orientation?: "horizontal" | "vertical";
22
+ flatCursor?: boolean;
23
+ /**
24
+ * Lời xướng cho trình đọc màn hình, hướng dẫn bàn phím và tên tay cầm kéo — mặc định tiếng
25
+ * Việt (`DEFAULT_KANBAN_LABELS`), ghi đè TỪNG khoá.
26
+ *
27
+ * ⚠ Thêm so với thượng nguồn: bản gốc chôn cứng câu tiếng Anh ("Picked up item at position
28
+ * 2 of 5") trong component; chỉ đổi được bằng cách viết lại cả `accessibility.announcements`,
29
+ * tức tự tính lại vị trí/tổng số. Ở đây các hàm nhận sẵn `{ variant, value, position, total,
30
+ * column }`. `accessibility` truyền thẳng vẫn được tôn trọng và THẮNG `labels`.
31
+ */
32
+ labels?: Partial<KanbanLabels>;
33
+ };
34
+ declare function Kanban<T>(props: KanbanProps<T>): React.JSX.Element;
35
+ interface KanbanBoardProps extends React.ComponentProps<"div"> {
36
+ children: React.ReactNode;
37
+ asChild?: boolean;
38
+ }
39
+ declare function KanbanBoard(props: KanbanBoardProps): React.JSX.Element;
40
+ interface KanbanColumnProps extends React.ComponentProps<"div"> {
41
+ value: UniqueIdentifier;
42
+ children: React.ReactNode;
43
+ asChild?: boolean;
44
+ asHandle?: boolean;
45
+ disabled?: boolean;
46
+ }
47
+ declare function KanbanColumn(props: KanbanColumnProps): React.JSX.Element;
48
+ interface KanbanColumnHandleProps extends React.ComponentProps<"button"> {
49
+ asChild?: boolean;
50
+ }
51
+ declare function KanbanColumnHandle(props: KanbanColumnHandleProps): React.JSX.Element;
52
+ interface KanbanItemProps extends React.ComponentProps<"div"> {
53
+ value: UniqueIdentifier;
54
+ asHandle?: boolean;
55
+ asChild?: boolean;
56
+ disabled?: boolean;
57
+ }
58
+ declare function KanbanItem(props: KanbanItemProps): React.JSX.Element;
59
+ interface KanbanItemHandleProps extends React.ComponentProps<"button"> {
60
+ asChild?: boolean;
61
+ }
62
+ declare function KanbanItemHandle(props: KanbanItemHandleProps): React.JSX.Element;
63
+ interface KanbanOverlayProps extends Omit<React.ComponentProps<typeof DragOverlay>, "children"> {
64
+ container?: Element | DocumentFragment | null;
65
+ children?: React.ReactNode | ((params: {
66
+ value: UniqueIdentifier;
67
+ variant: "column" | "item";
68
+ }) => React.ReactNode);
69
+ }
70
+ declare function KanbanOverlay(props: KanbanOverlayProps): React.ReactPortal | null;
71
+
72
+ export { Kanban, KanbanBoard, type KanbanBoardProps, KanbanColumn, KanbanColumnHandle, type KanbanColumnHandleProps, type KanbanColumnProps, KanbanItem, KanbanItemHandle, type KanbanItemHandleProps, type KanbanItemProps, KanbanOverlay, type KanbanOverlayProps, type KanbanProps };