@mezzanine-ui/react 1.0.0-beta.6 → 1.0.0-beta.7

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 (142) hide show
  1. package/Accordion/Accordion.d.ts +23 -1
  2. package/Accordion/Accordion.js +59 -11
  3. package/Accordion/AccordionActions.d.ts +13 -0
  4. package/Accordion/AccordionActions.js +24 -0
  5. package/Accordion/AccordionContent.d.ts +9 -0
  6. package/Accordion/{AccordionDetails.js → AccordionContent.js} +4 -6
  7. package/Accordion/AccordionControlContext.d.ts +2 -2
  8. package/Accordion/AccordionGroup.d.ts +10 -0
  9. package/Accordion/AccordionGroup.js +26 -0
  10. package/Accordion/AccordionTitle.d.ts +14 -0
  11. package/Accordion/AccordionTitle.js +56 -0
  12. package/Accordion/index.d.ts +8 -4
  13. package/Accordion/index.js +4 -2
  14. package/AutoComplete/AutoComplete.d.ts +20 -6
  15. package/AutoComplete/AutoComplete.js +118 -30
  16. package/Backdrop/Backdrop.js +15 -19
  17. package/Calendar/CalendarDays.js +1 -1
  18. package/Card/BaseCard.d.ts +11 -0
  19. package/Card/BaseCard.js +48 -0
  20. package/Card/BaseCardSkeleton.d.ts +14 -0
  21. package/Card/BaseCardSkeleton.js +18 -0
  22. package/Card/CardGroup.d.ts +47 -0
  23. package/Card/CardGroup.js +147 -0
  24. package/Card/FourThumbnailCard.d.ts +14 -0
  25. package/Card/FourThumbnailCard.js +73 -0
  26. package/Card/FourThumbnailCardSkeleton.d.ts +14 -0
  27. package/Card/FourThumbnailCardSkeleton.js +20 -0
  28. package/Card/QuickActionCard.d.ts +12 -0
  29. package/Card/QuickActionCard.js +23 -0
  30. package/Card/QuickActionCardSkeleton.d.ts +14 -0
  31. package/Card/QuickActionCardSkeleton.js +18 -0
  32. package/Card/SingleThumbnailCard.d.ts +13 -0
  33. package/Card/SingleThumbnailCard.js +44 -0
  34. package/Card/SingleThumbnailCardSkeleton.d.ts +19 -0
  35. package/Card/SingleThumbnailCardSkeleton.js +18 -0
  36. package/Card/Thumbnail.d.ts +12 -0
  37. package/Card/Thumbnail.js +18 -0
  38. package/Card/ThumbnailCardInfo.d.ts +34 -0
  39. package/Card/ThumbnailCardInfo.js +43 -0
  40. package/Card/index.d.ts +43 -4
  41. package/Card/index.js +19 -2
  42. package/Card/typings.d.ts +442 -0
  43. package/Checkbox/Checkbox.d.ts +8 -0
  44. package/Checkbox/Checkbox.js +3 -2
  45. package/Checkbox/CheckboxGroup.js +1 -1
  46. package/ContentHeader/ContentHeader.d.ts +22 -70
  47. package/ContentHeader/ContentHeader.js +1 -1
  48. package/ContentHeader/ContentHeaderResponsive.d.ts +9 -0
  49. package/ContentHeader/ContentHeaderResponsive.js +7 -0
  50. package/ContentHeader/utils.d.ts +3 -3
  51. package/ContentHeader/utils.js +66 -20
  52. package/Cropper/Cropper.d.ts +66 -0
  53. package/Cropper/Cropper.js +115 -0
  54. package/Cropper/CropperElement.d.ts +10 -0
  55. package/Cropper/CropperElement.js +892 -0
  56. package/Cropper/index.d.ts +18 -0
  57. package/Cropper/index.js +8 -0
  58. package/Cropper/tools.d.ts +90 -0
  59. package/Cropper/tools.js +143 -0
  60. package/Cropper/typings.d.ts +69 -0
  61. package/Cropper/utils/cropper-calculations.d.ts +39 -0
  62. package/Cropper/utils/cropper-calculations.js +95 -0
  63. package/DateTimePicker/DateTimePicker.d.ts +1 -1
  64. package/DateTimePicker/DateTimePicker.js +14 -1
  65. package/Dropdown/Dropdown.d.ts +7 -1
  66. package/Dropdown/Dropdown.js +31 -14
  67. package/Dropdown/DropdownItem.d.ts +7 -1
  68. package/Dropdown/DropdownItem.js +36 -6
  69. package/Dropdown/DropdownItemCard.js +2 -1
  70. package/FloatingButton/FloatingButton.d.ts +21 -0
  71. package/FloatingButton/FloatingButton.js +18 -0
  72. package/FloatingButton/index.d.ts +2 -0
  73. package/FloatingButton/index.js +1 -0
  74. package/Form/FormField.d.ts +21 -10
  75. package/Form/FormField.js +12 -4
  76. package/Input/Input.js +9 -2
  77. package/Message/Message.js +1 -1
  78. package/MultipleDatePicker/MultipleDatePicker.js +2 -2
  79. package/Navigation/NavigationHeader.js +1 -1
  80. package/Picker/FormattedInput.d.ts +1 -1
  81. package/Picker/FormattedInput.js +2 -1
  82. package/Picker/PickerTriggerWithSeparator.d.ts +10 -0
  83. package/Picker/PickerTriggerWithSeparator.js +2 -2
  84. package/Picker/useDateInputFormatter.d.ts +6 -0
  85. package/Picker/useDateInputFormatter.js +4 -1
  86. package/Select/Select.d.ts +2 -8
  87. package/Select/Select.js +12 -33
  88. package/Select/SelectTrigger.js +21 -7
  89. package/Select/index.d.ts +0 -4
  90. package/Select/index.js +0 -2
  91. package/Select/typings.d.ts +0 -4
  92. package/Select/useSelectTriggerTags.d.ts +1 -1
  93. package/Select/useSelectTriggerTags.js +9 -6
  94. package/Separator/Separator.d.ts +14 -0
  95. package/Separator/Separator.js +17 -0
  96. package/Separator/index.d.ts +2 -0
  97. package/Separator/index.js +1 -0
  98. package/Table/utils/useTableRowSelection.js +6 -0
  99. package/Tag/TagGroup.d.ts +4 -2
  100. package/Tag/TagGroup.js +7 -4
  101. package/TextField/TextField.d.ts +1 -1
  102. package/TextField/TextField.js +63 -9
  103. package/TimePanel/TimePanelColumn.js +19 -12
  104. package/index.d.ts +27 -28
  105. package/index.js +23 -25
  106. package/package.json +4 -4
  107. package/Accordion/AccordionDetails.d.ts +0 -9
  108. package/Accordion/AccordionSummary.d.ts +0 -18
  109. package/Accordion/AccordionSummary.js +0 -51
  110. package/Alert/Alert.d.ts +0 -20
  111. package/Alert/Alert.js +0 -18
  112. package/Alert/index.d.ts +0 -3
  113. package/Alert/index.js +0 -1
  114. package/Card/Card.d.ts +0 -51
  115. package/Card/Card.js +0 -20
  116. package/Card/CardActions.d.ts +0 -34
  117. package/Card/CardActions.js +0 -15
  118. package/ConfirmActions/ConfirmActions.d.ts +0 -46
  119. package/ConfirmActions/ConfirmActions.js +0 -15
  120. package/ConfirmActions/index.d.ts +0 -2
  121. package/ConfirmActions/index.js +0 -1
  122. package/Select/Option.d.ts +0 -18
  123. package/Select/Option.js +0 -45
  124. package/Select/TreeSelect.d.ts +0 -72
  125. package/Select/TreeSelect.js +0 -205
  126. package/Tree/Tree.d.ts +0 -70
  127. package/Tree/Tree.js +0 -139
  128. package/Tree/TreeNode.d.ts +0 -40
  129. package/Tree/TreeNode.js +0 -50
  130. package/Tree/TreeNodeList.d.ts +0 -24
  131. package/Tree/TreeNodeList.js +0 -28
  132. package/Tree/getTreeNodeEntities.d.ts +0 -11
  133. package/Tree/getTreeNodeEntities.js +0 -92
  134. package/Tree/index.d.ts +0 -13
  135. package/Tree/index.js +0 -7
  136. package/Tree/toggleValue.d.ts +0 -4
  137. package/Tree/toggleValue.js +0 -19
  138. package/Tree/traverseTree.d.ts +0 -2
  139. package/Tree/traverseTree.js +0 -11
  140. package/Tree/typings.d.ts +0 -16
  141. package/Tree/useTreeExpandedValue.d.ts +0 -14
  142. package/Tree/useTreeExpandedValue.js +0 -33
@@ -0,0 +1,18 @@
1
+ import { PropsWithoutRef, ReactElement, RefAttributes } from 'react';
2
+ import { CropperModal, CropperProps } from './Cropper';
3
+ import { CropArea, CropperComponent, CropperElementComponent, CropperPropsBase } from './typings';
4
+ export type { CropperSize } from '@mezzanine-ui/core/cropper';
5
+ export type { CropperModalConfirmContext, CropperModalOpenOptions, CropperModalProps, CropperModalResult, CropperModalType } from './Cropper';
6
+ export { default as CropperElement } from './CropperElement';
7
+ export type { CropperElementProps } from './CropperElement';
8
+ export { cropToBlob, cropToDataURL, cropToFile } from './tools';
9
+ export type { CropToBlobOptions } from './tools';
10
+ export { CropperModal };
11
+ export type { CropArea, CropperComponent, CropperElementComponent, CropperProps, CropperPropsBase };
12
+ /**
13
+ * @remark
14
+ * Add type alias here for parsable to react docgen typescript.
15
+ */
16
+ type GenericCropper = <C extends CropperComponent = 'div'>(props: PropsWithoutRef<CropperProps<C>> & RefAttributes<HTMLDivElement>) => ReactElement<any>;
17
+ declare const _default: GenericCropper;
18
+ export default _default;
@@ -0,0 +1,8 @@
1
+ import Cropper from './Cropper.js';
2
+ export { CropperModal } from './Cropper.js';
3
+ export { default as CropperElement } from './CropperElement.js';
4
+ export { cropToBlob, cropToDataURL, cropToFile } from './tools.js';
5
+
6
+
7
+
8
+ export { Cropper as default };
@@ -0,0 +1,90 @@
1
+ import { CropArea } from './typings';
2
+ export interface CropToBlobOptions {
3
+ /**
4
+ * The image source (URL, File, or Blob).
5
+ */
6
+ imageSrc: string | File | Blob;
7
+ /**
8
+ * The crop area coordinates and dimensions in image pixel space.
9
+ */
10
+ cropArea: CropArea;
11
+ /**
12
+ * The canvas element used for cropping.
13
+ * If not provided, a temporary canvas will be created.
14
+ */
15
+ canvas?: HTMLCanvasElement;
16
+ /**
17
+ * The output image format.
18
+ * @default 'image/png'
19
+ */
20
+ format?: string;
21
+ /**
22
+ * The output image quality (0-1).
23
+ * Only applies to 'image/jpeg' and 'image/webp' formats.
24
+ * @default 0.92
25
+ */
26
+ quality?: number;
27
+ /**
28
+ * The output image width in pixels.
29
+ * If not provided, uses the crop area width.
30
+ */
31
+ outputWidth?: number;
32
+ /**
33
+ * The output image height in pixels.
34
+ * If not provided, uses the crop area height.
35
+ */
36
+ outputHeight?: number;
37
+ }
38
+ /**
39
+ * Converts a cropped image area to a Blob.
40
+ *
41
+ * @param options - The cropping options
42
+ * @returns A Promise that resolves to a Blob of the cropped image
43
+ *
44
+ * @example
45
+ * ```tsx
46
+ * const blob = await cropToBlob({
47
+ * imageSrc: 'https://example.com/image.jpg',
48
+ * cropArea: { x: 100, y: 100, width: 200, height: 200 },
49
+ * format: 'image/jpeg',
50
+ * quality: 0.9,
51
+ * });
52
+ * ```
53
+ */
54
+ export declare function cropToBlob(options: CropToBlobOptions): Promise<Blob>;
55
+ /**
56
+ * Converts a cropped image area to a File.
57
+ *
58
+ * @param options - The cropping options
59
+ * @param filename - The filename for the output file
60
+ * @returns A Promise that resolves to a File of the cropped image
61
+ *
62
+ * @example
63
+ * ```tsx
64
+ * const file = await cropToFile(
65
+ * {
66
+ * imageSrc: fileInput.files[0],
67
+ * cropArea: { x: 100, y: 100, width: 200, height: 200 },
68
+ * },
69
+ * 'cropped-image.jpg',
70
+ * );
71
+ * ```
72
+ */
73
+ export declare function cropToFile(options: CropToBlobOptions, filename: string): Promise<File>;
74
+ /**
75
+ * Converts a cropped image area to a data URL.
76
+ *
77
+ * @param options - The cropping options
78
+ * @returns A Promise that resolves to a data URL string of the cropped image
79
+ *
80
+ * @example
81
+ * ```tsx
82
+ * const dataUrl = await cropToDataURL({
83
+ * imageSrc: 'https://example.com/image.jpg',
84
+ * cropArea: { x: 100, y: 100, width: 200, height: 200 },
85
+ * format: 'image/jpeg',
86
+ * quality: 0.9,
87
+ * });
88
+ * ```
89
+ */
90
+ export declare function cropToDataURL(options: CropToBlobOptions): Promise<string>;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Converts a cropped image area to a Blob.
3
+ *
4
+ * @param options - The cropping options
5
+ * @returns A Promise that resolves to a Blob of the cropped image
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const blob = await cropToBlob({
10
+ * imageSrc: 'https://example.com/image.jpg',
11
+ * cropArea: { x: 100, y: 100, width: 200, height: 200 },
12
+ * format: 'image/jpeg',
13
+ * quality: 0.9,
14
+ * });
15
+ * ```
16
+ */
17
+ async function cropToBlob(options) {
18
+ const { imageSrc, cropArea, canvas: providedCanvas, format = 'image/png', quality = 0.92, outputWidth, outputHeight, } = options;
19
+ // Load image
20
+ const img = await loadImage(imageSrc);
21
+ // Create or use provided canvas
22
+ const canvas = providedCanvas || document.createElement('canvas');
23
+ const ctx = canvas.getContext('2d');
24
+ if (!ctx) {
25
+ throw new Error('Failed to get canvas context');
26
+ }
27
+ // Crop area is already in image pixel space
28
+ const actualCropX = cropArea.x;
29
+ const actualCropY = cropArea.y;
30
+ const actualCropWidth = cropArea.width;
31
+ const actualCropHeight = cropArea.height;
32
+ // Set canvas size to output dimensions or crop area dimensions
33
+ const finalWidth = outputWidth || cropArea.width;
34
+ const finalHeight = outputHeight || cropArea.height;
35
+ canvas.width = finalWidth;
36
+ canvas.height = finalHeight;
37
+ // Draw the cropped portion of the image
38
+ ctx.drawImage(img, actualCropX, actualCropY, actualCropWidth, actualCropHeight, 0, 0, finalWidth, finalHeight);
39
+ // Convert to blob
40
+ return new Promise((resolve, reject) => {
41
+ canvas.toBlob((blob) => {
42
+ if (blob) {
43
+ resolve(blob);
44
+ }
45
+ else {
46
+ reject(new Error('Failed to convert canvas to blob'));
47
+ }
48
+ }, format, quality);
49
+ });
50
+ }
51
+ /**
52
+ * Converts a cropped image area to a File.
53
+ *
54
+ * @param options - The cropping options
55
+ * @param filename - The filename for the output file
56
+ * @returns A Promise that resolves to a File of the cropped image
57
+ *
58
+ * @example
59
+ * ```tsx
60
+ * const file = await cropToFile(
61
+ * {
62
+ * imageSrc: fileInput.files[0],
63
+ * cropArea: { x: 100, y: 100, width: 200, height: 200 },
64
+ * },
65
+ * 'cropped-image.jpg',
66
+ * );
67
+ * ```
68
+ */
69
+ async function cropToFile(options, filename) {
70
+ const blob = await cropToBlob(options);
71
+ return new File([blob], filename, { type: blob.type });
72
+ }
73
+ /**
74
+ * Converts a cropped image area to a data URL.
75
+ *
76
+ * @param options - The cropping options
77
+ * @returns A Promise that resolves to a data URL string of the cropped image
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * const dataUrl = await cropToDataURL({
82
+ * imageSrc: 'https://example.com/image.jpg',
83
+ * cropArea: { x: 100, y: 100, width: 200, height: 200 },
84
+ * format: 'image/jpeg',
85
+ * quality: 0.9,
86
+ * });
87
+ * ```
88
+ */
89
+ async function cropToDataURL(options) {
90
+ const { imageSrc, cropArea, canvas: providedCanvas, format = 'image/png', quality = 0.92, outputWidth, outputHeight, } = options;
91
+ // Load image
92
+ const img = await loadImage(imageSrc);
93
+ // Create or use provided canvas
94
+ const canvas = providedCanvas || document.createElement('canvas');
95
+ const ctx = canvas.getContext('2d');
96
+ if (!ctx) {
97
+ throw new Error('Failed to get canvas context');
98
+ }
99
+ // Crop area is already in image pixel space
100
+ const actualCropX = cropArea.x;
101
+ const actualCropY = cropArea.y;
102
+ const actualCropWidth = cropArea.width;
103
+ const actualCropHeight = cropArea.height;
104
+ // Set canvas size to output dimensions or crop area dimensions
105
+ const finalWidth = outputWidth || cropArea.width;
106
+ const finalHeight = outputHeight || cropArea.height;
107
+ canvas.width = finalWidth;
108
+ canvas.height = finalHeight;
109
+ // Draw the cropped portion of the image
110
+ ctx.drawImage(img, actualCropX, actualCropY, actualCropWidth, actualCropHeight, 0, 0, finalWidth, finalHeight);
111
+ return canvas.toDataURL(format, quality);
112
+ }
113
+ /**
114
+ * Helper function to load an image from various sources.
115
+ */
116
+ async function loadImage(src) {
117
+ return new Promise((resolve, reject) => {
118
+ const img = new Image();
119
+ let objectUrl = null;
120
+ img.crossOrigin = 'anonymous';
121
+ img.onload = () => {
122
+ if (objectUrl) {
123
+ URL.revokeObjectURL(objectUrl);
124
+ }
125
+ resolve(img);
126
+ };
127
+ img.onerror = (error) => {
128
+ if (objectUrl) {
129
+ URL.revokeObjectURL(objectUrl);
130
+ }
131
+ reject(error);
132
+ };
133
+ if (typeof src === 'string') {
134
+ img.src = src;
135
+ }
136
+ else {
137
+ objectUrl = URL.createObjectURL(src);
138
+ img.src = objectUrl;
139
+ }
140
+ });
141
+ }
142
+
143
+ export { cropToBlob, cropToDataURL, cropToFile };
@@ -0,0 +1,69 @@
1
+ import { CropperSize } from '@mezzanine-ui/core/cropper';
2
+ import { ReactNode } from 'react';
3
+ export type CropperComponent = 'div' | 'span';
4
+ export type CropperElementComponent = 'canvas';
5
+ export interface CropperPropsBase {
6
+ /**
7
+ * The size of cropper.
8
+ * @default 'main'
9
+ */
10
+ size?: CropperSize;
11
+ /**
12
+ * The cropper content.
13
+ */
14
+ children?: ReactNode;
15
+ /**
16
+ * The image source to crop.
17
+ * Can be a URL string, File, or Blob.
18
+ */
19
+ imageSrc?: string | File | Blob;
20
+ /**
21
+ * Callback fired when the crop area changes.
22
+ */
23
+ onCropChange?: (cropArea: CropArea) => void;
24
+ /**
25
+ * Callback fired when crop area drag ends.
26
+ */
27
+ onCropDragEnd?: (cropArea: CropArea) => void;
28
+ /**
29
+ * Callback fired when image drag ends.
30
+ */
31
+ onImageDragEnd?: () => void;
32
+ /**
33
+ * Callback fired when scale (zoom) changes.
34
+ */
35
+ onScaleChange?: (scale: number) => void;
36
+ /**
37
+ * Callback fired when image loads successfully.
38
+ */
39
+ onImageLoad?: () => void;
40
+ /**
41
+ * Callback fired when image fails to load.
42
+ */
43
+ onImageError?: (error: Error) => void;
44
+ /**
45
+ * Initial crop area.
46
+ */
47
+ initialCropArea?: CropArea;
48
+ /**
49
+ * Aspect ratio for the crop area (width / height).
50
+ * If not provided, free aspect ratio is allowed.
51
+ */
52
+ aspectRatio?: number;
53
+ /**
54
+ * Minimum crop area width in pixels.
55
+ * @default 50
56
+ */
57
+ minWidth?: number;
58
+ /**
59
+ * Minimum crop area height in pixels.
60
+ * @default 50
61
+ */
62
+ minHeight?: number;
63
+ }
64
+ export interface CropArea {
65
+ x: number;
66
+ y: number;
67
+ width: number;
68
+ height: number;
69
+ }
@@ -0,0 +1,39 @@
1
+ import type { CropArea } from '../typings';
2
+ export interface ImagePosition {
3
+ offsetX: number;
4
+ offsetY: number;
5
+ }
6
+ export interface BaseDisplaySize {
7
+ width: number;
8
+ height: number;
9
+ }
10
+ export interface InitialCropAreaResult {
11
+ baseDisplayHeight: number;
12
+ baseDisplayWidth: number;
13
+ cropArea: CropArea;
14
+ imagePosition: ImagePosition;
15
+ }
16
+ /**
17
+ * Calculate base scale for image to fit canvas height.
18
+ */
19
+ export declare function getBaseScale(rect: DOMRect, img: HTMLImageElement): number;
20
+ /**
21
+ * Calculate base display size of image.
22
+ */
23
+ export declare function getBaseDisplaySize(rect: DOMRect, img: HTMLImageElement): BaseDisplaySize;
24
+ /**
25
+ * Calculate initial crop area and image position.
26
+ */
27
+ export declare function calculateInitialCropArea(img: HTMLImageElement, rect: DOMRect, aspectRatio?: number): InitialCropAreaResult;
28
+ /**
29
+ * Constrain image position to ensure it covers crop area.
30
+ */
31
+ export declare function constrainImagePosition(newOffsetX: number, newOffsetY: number, displayWidth: number, displayHeight: number, cropArea: CropArea): ImagePosition;
32
+ /**
33
+ * Check if two crop areas are similar (within threshold).
34
+ */
35
+ export declare function isCropAreaSimilar(a: CropArea | null, b: CropArea | null, threshold?: number): boolean;
36
+ /**
37
+ * Check if two image positions are similar (within threshold).
38
+ */
39
+ export declare function isImagePositionSimilar(a: ImagePosition | null, b: ImagePosition | null, threshold?: number): boolean;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Calculate base scale for image to fit canvas height.
3
+ */
4
+ function getBaseScale(rect, img) {
5
+ if (!rect.height)
6
+ return 1;
7
+ return img.height / rect.height;
8
+ }
9
+ /**
10
+ * Calculate base display size of image.
11
+ */
12
+ function getBaseDisplaySize(rect, img) {
13
+ const baseScale = getBaseScale(rect, img);
14
+ return {
15
+ width: img.width / baseScale,
16
+ height: img.height / baseScale,
17
+ };
18
+ }
19
+ /**
20
+ * Calculate initial crop area and image position.
21
+ */
22
+ function calculateInitialCropArea(img, rect, aspectRatio) {
23
+ const baseScale = getBaseScale(rect, img);
24
+ const baseDisplayWidth = img.width / baseScale;
25
+ const baseDisplayHeight = img.height / baseScale;
26
+ const initialOffsetX = (rect.width - baseDisplayWidth) / 2;
27
+ const initialOffsetY = (rect.height - baseDisplayHeight) / 2;
28
+ let initialWidth = baseDisplayWidth;
29
+ let initialHeight = baseDisplayHeight;
30
+ if (aspectRatio) {
31
+ const maxWidthByHeight = baseDisplayHeight * aspectRatio;
32
+ const maxHeightByWidth = baseDisplayWidth / aspectRatio;
33
+ if (maxWidthByHeight <= baseDisplayWidth) {
34
+ initialWidth = maxWidthByHeight;
35
+ initialHeight = baseDisplayHeight;
36
+ }
37
+ else {
38
+ initialWidth = baseDisplayWidth;
39
+ initialHeight = maxHeightByWidth;
40
+ }
41
+ }
42
+ const initialX = initialOffsetX + (baseDisplayWidth - initialWidth) / 2;
43
+ const initialY = initialOffsetY + (baseDisplayHeight - initialHeight) / 2;
44
+ return {
45
+ baseDisplayHeight,
46
+ baseDisplayWidth,
47
+ cropArea: {
48
+ height: initialHeight,
49
+ width: initialWidth,
50
+ x: initialX,
51
+ y: initialY,
52
+ },
53
+ imagePosition: {
54
+ offsetX: initialOffsetX,
55
+ offsetY: initialOffsetY,
56
+ },
57
+ };
58
+ }
59
+ /**
60
+ * Constrain image position to ensure it covers crop area.
61
+ */
62
+ function constrainImagePosition(newOffsetX, newOffsetY, displayWidth, displayHeight, cropArea) {
63
+ const { x: cx, y: cy, width: cw, height: ch } = cropArea;
64
+ // Ensure image always covers crop area (no white space in crop area)
65
+ const minOffsetX = cx + cw - displayWidth;
66
+ const maxOffsetX = cx;
67
+ const minOffsetY = cy + ch - displayHeight;
68
+ const maxOffsetY = cy;
69
+ return {
70
+ offsetX: Math.max(minOffsetX, Math.min(newOffsetX, maxOffsetX)),
71
+ offsetY: Math.max(minOffsetY, Math.min(newOffsetY, maxOffsetY)),
72
+ };
73
+ }
74
+ /**
75
+ * Check if two crop areas are similar (within threshold).
76
+ */
77
+ function isCropAreaSimilar(a, b, threshold = 0.5) {
78
+ if (!a || !b)
79
+ return a === b;
80
+ return (Math.abs(a.x - b.x) < threshold &&
81
+ Math.abs(a.y - b.y) < threshold &&
82
+ Math.abs(a.width - b.width) < threshold &&
83
+ Math.abs(a.height - b.height) < threshold);
84
+ }
85
+ /**
86
+ * Check if two image positions are similar (within threshold).
87
+ */
88
+ function isImagePositionSimilar(a, b, threshold = 0.1) {
89
+ if (!a || !b)
90
+ return a === b;
91
+ return (Math.abs(a.offsetX - b.offsetX) < threshold &&
92
+ Math.abs(a.offsetY - b.offsetY) < threshold);
93
+ }
94
+
95
+ export { calculateInitialCropArea, constrainImagePosition, getBaseDisplaySize, getBaseScale, isCropAreaSimilar, isImagePositionSimilar };
@@ -3,7 +3,7 @@ import { DatePickerCalendarProps } from '../DatePicker';
3
3
  import { PickerTriggerWithSeparatorProps } from '../Picker';
4
4
  import { TimePickerPanelProps } from '../TimePicker';
5
5
  type FocusedInput = 'left' | 'right' | null;
6
- export interface DateTimePickerProps extends Omit<DatePickerCalendarProps, 'anchor' | 'onChange' | 'open' | 'referenceDate' | 'value'>, Omit<TimePickerPanelProps, 'anchor' | 'onChange' | 'open' | 'popperProps' | 'value'>, Omit<PickerTriggerWithSeparatorProps, 'formatLeft' | 'formatRight' | 'inputLeftRef' | 'inputRightRef' | 'onBlurLeft' | 'onBlurRight' | 'onChangeLeft' | 'onChangeRight' | 'onFocusLeft' | 'onFocusRight' | 'onLeftComplete' | 'onRightComplete' | 'suffix' | 'valueLeft' | 'valueRight'> {
6
+ export interface DateTimePickerProps extends Omit<DatePickerCalendarProps, 'anchor' | 'onChange' | 'open' | 'referenceDate' | 'value'>, Omit<TimePickerPanelProps, 'anchor' | 'onChange' | 'open' | 'popperProps' | 'value'>, Omit<PickerTriggerWithSeparatorProps, 'formatLeft' | 'formatRight' | 'inputLeftRef' | 'inputRightRef' | 'onBlurLeft' | 'onBlurRight' | 'onChangeLeft' | 'onChangeRight' | 'onFocusLeft' | 'onFocusRight' | 'onLeftComplete' | 'onPasteIsoValueLeft' | 'onPasteIsoValueRight' | 'onRightComplete' | 'suffix' | 'valueLeft' | 'valueRight'> {
7
7
  /**
8
8
  * Default value for date-time picker.
9
9
  */
@@ -153,6 +153,19 @@ const DateTimePicker = forwardRef(function DateTimePicker(props, ref) {
153
153
  notifyChange(dateValue, isoValue);
154
154
  }
155
155
  }, [dateValue, isValid, notifyChange]);
156
+ const onPasteIsoValueLeft = useCallback((isoValue) => {
157
+ if (!timeValue && isValid(isoValue)) {
158
+ // Time is empty, update it from pasted value
159
+ setTimeValue(isoValue);
160
+ }
161
+ }, [isValid, timeValue]);
162
+ const onPasteIsoValueRight = useCallback((isoValue) => {
163
+ if (!dateValue && isValid(isoValue)) {
164
+ // Date is empty, update it from pasted value
165
+ setDateValue(isoValue);
166
+ setReferenceDate(startOf(isoValue, 'day'));
167
+ }
168
+ }, [dateValue, isValid, startOf]);
156
169
  const onCalendarChange = useCallback((target) => {
157
170
  setDateValue(target);
158
171
  setReferenceDate(startOf(target, 'day'));
@@ -204,7 +217,7 @@ const DateTimePicker = forwardRef(function DateTimePicker(props, ref) {
204
217
  }
205
218
  }, [disabled, focusedInput, onPanelToggle, readOnly]);
206
219
  const suffix = (jsx(Icon, { "aria-label": "Open calendar", icon: CalendarTimeIcon, onClick: onCalendarIconClick }));
207
- return (jsxs(Fragment, { children: [jsx(PickerTriggerWithSeparator, { ...restTriggerProps, ref: triggerComposedRef, className: className, clearable: clearable, disabled: disabled, error: error, formatLeft: formatDate, formatRight: formatTime, fullWidth: fullWidth, inputLeftRef: inputLeftRef, inputRightRef: inputRightRef, onBlurLeft: () => { }, onBlurRight: () => { }, onChangeLeft: onChangeLeft, onChangeRight: onChangeRight, onClear: onClear, onFocusLeft: onFocusLeft, onFocusRight: onFocusRight, onLeftComplete: onLeftComplete, onRightComplete: onRightComplete, placeholderLeft: placeholderLeft, placeholderRight: placeholderRight, prefix: prefix, readOnly: readOnly, required: required, size: size, suffix: suffix, valueLeft: displayDateValue, valueRight: displayTimeValue }), jsx(DatePickerCalendar, { ref: calendarPanelRef, anchor: anchorRef, calendarProps: calendarProps, calendarRef: calendarRef, disabledMonthSwitch: disabledMonthSwitch, disableOnDoubleNext: disableOnDoubleNext, disableOnDoublePrev: disableOnDoublePrev, disableOnNext: disableOnNext, disableOnPrev: disableOnPrev, disabledYearSwitch: disabledYearSwitch, displayMonthLocale: displayMonthLocale, fadeProps: fadeProps, isDateDisabled: isDateDisabled, isHalfYearDisabled: isHalfYearDisabled, isMonthDisabled: isMonthDisabled, isQuarterDisabled: isQuarterDisabled, isWeekDisabled: isWeekDisabled, isYearDisabled: isYearDisabled, mode: mode, onChange: onCalendarChange, open: openCalendar, popperProps: popperProps, referenceDate: referenceDate, value: dateValue }), jsx(TimePickerPanel, { ref: timePanelRef, anchor: anchorRef, fadeProps: fadeProps, hideHour: hideHour, hideMinute: hideMinute, hideSecond: hideSecond, hourStep: hourStep, minuteStep: minuteStep, onChange: onTimePanelChange, open: openTimePanel, popperProps: popperPropsTime, secondStep: secondStep, value: timeValue })] }));
220
+ return (jsxs(Fragment, { children: [jsx(PickerTriggerWithSeparator, { ...restTriggerProps, ref: triggerComposedRef, className: className, clearable: clearable, disabled: disabled, error: error, formatLeft: formatDate, formatRight: formatTime, fullWidth: fullWidth, inputLeftRef: inputLeftRef, inputRightRef: inputRightRef, onBlurLeft: () => { }, onBlurRight: () => { }, onChangeLeft: onChangeLeft, onChangeRight: onChangeRight, onClear: onClear, onFocusLeft: onFocusLeft, onFocusRight: onFocusRight, onLeftComplete: onLeftComplete, onPasteIsoValueLeft: onPasteIsoValueLeft, onPasteIsoValueRight: onPasteIsoValueRight, onRightComplete: onRightComplete, placeholderLeft: placeholderLeft, placeholderRight: placeholderRight, prefix: prefix, readOnly: readOnly, required: required, size: size, suffix: suffix, valueLeft: displayDateValue, valueRight: displayTimeValue }), jsx(DatePickerCalendar, { ref: calendarPanelRef, anchor: anchorRef, calendarProps: calendarProps, calendarRef: calendarRef, disabledMonthSwitch: disabledMonthSwitch, disableOnDoubleNext: disableOnDoubleNext, disableOnDoublePrev: disableOnDoublePrev, disableOnNext: disableOnNext, disableOnPrev: disableOnPrev, disabledYearSwitch: disabledYearSwitch, displayMonthLocale: displayMonthLocale, fadeProps: fadeProps, isDateDisabled: isDateDisabled, isHalfYearDisabled: isHalfYearDisabled, isMonthDisabled: isMonthDisabled, isQuarterDisabled: isQuarterDisabled, isWeekDisabled: isWeekDisabled, isYearDisabled: isYearDisabled, mode: mode, onChange: onCalendarChange, open: openCalendar, popperProps: popperProps, referenceDate: referenceDate, value: dateValue }), jsx(TimePickerPanel, { ref: timePanelRef, anchor: anchorRef, fadeProps: fadeProps, hideHour: hideHour, hideMinute: hideMinute, hideSecond: hideSecond, hourStep: hourStep, minuteStep: minuteStep, onChange: onTimePanelChange, open: openTimePanel, popperProps: popperPropsTime, secondStep: secondStep, value: timeValue })] }));
208
221
  });
209
222
 
210
223
  export { DateTimePicker as default };
@@ -1,5 +1,5 @@
1
1
  import { ReactElement } from 'react';
2
- import { DropdownInputPosition, DropdownItemSharedProps, DropdownOption, DropdownStatus as DropdownStatusType, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
2
+ import { DropdownInputPosition, DropdownItemSharedProps, DropdownLoadingPosition, DropdownOption, DropdownStatus as DropdownStatusType, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
3
3
  import { ButtonProps } from '../Button';
4
4
  import { InputProps } from '../Input';
5
5
  import { PopperPlacement } from '../Popper';
@@ -162,6 +162,12 @@ export interface DropdownProps extends DropdownItemSharedProps {
162
162
  * The icon of the dropdown empty status.
163
163
  */
164
164
  emptyIcon?: IconDefinition;
165
+ /**
166
+ * The position to display the loading status.
167
+ * Only takes effect when `status === 'loading'`.
168
+ * @default 'full'
169
+ */
170
+ loadingPosition?: DropdownLoadingPosition;
165
171
  /**
166
172
  * Whether to enable portal.
167
173
  * This prop is only relevant when `inputPosition` is set to 'outside'.
@@ -13,8 +13,19 @@ import { composeRefs } from '../utils/composeRefs.js';
13
13
  import DropdownItem from './DropdownItem.js';
14
14
  import Popper from '../Popper/Popper.js';
15
15
 
16
+ /**
17
+ * Extracts ref from a ReactElement, supporting both React 18 and 19.
18
+ * In React 18, ref is on the element itself; in React 19, ref is in props.
19
+ */
20
+ function getElementRef(element) {
21
+ var _a;
22
+ // React 19: ref is in props
23
+ const propsRef = (_a = element.props) === null || _a === void 0 ? void 0 : _a.ref;
24
+ // React 18: ref is on the element itself
25
+ return propsRef !== null && propsRef !== void 0 ? propsRef : element.ref;
26
+ }
16
27
  function Dropdown(props) {
17
- const { activeIndex: activeIndexProp, id, children, options = [], type = 'default', maxHeight, disabled = false, showDropdownActions = false, actionCancelText, actionConfirmText, actionText, actionClearText, actionCustomButtonProps, showActionShowTopBar, isMatchInputValue = false, inputPosition = 'outside', placement = 'bottom', customWidth, sameWidth = false, listboxId: listboxIdProp, listboxLabel, onClose, onOpen, open: openProp, onVisibilityChange, onSelect, onActionConfirm, onActionCancel, onActionCustom, onActionClear, onItemHover, zIndex, status, loadingText, emptyText, emptyIcon, followText: followTextProp, globalPortal = true, onReachBottom, onLeaveBottom, onScroll, mode, value, scrollbarDefer, scrollbarDisabled, scrollbarMaxWidth, scrollbarOptions, } = props;
28
+ const { activeIndex: activeIndexProp, id, children, options = [], type = 'default', maxHeight, disabled = false, showDropdownActions = false, actionCancelText, actionConfirmText, actionText, actionClearText, actionCustomButtonProps, showActionShowTopBar, isMatchInputValue = false, inputPosition = 'outside', placement = 'bottom', customWidth, sameWidth = false, listboxId: listboxIdProp, listboxLabel, onClose, onOpen, open: openProp, onVisibilityChange, onSelect, onActionConfirm, onActionCancel, onActionCustom, onActionClear, onItemHover, zIndex, status, loadingText, emptyText, emptyIcon, loadingPosition = 'full', followText: followTextProp, globalPortal = true, onReachBottom, onLeaveBottom, onScroll, mode, value, scrollbarDefer, scrollbarDisabled, scrollbarMaxWidth, scrollbarOptions, } = props;
18
29
  const isInline = inputPosition === 'inside';
19
30
  const inputId = useId();
20
31
  const defaultListboxId = `${inputId}-listbox`;
@@ -179,8 +190,8 @@ function Dropdown(props) {
179
190
  const popperControllerRef = useRef(null);
180
191
  // Extract combobox props logic to avoid duplication
181
192
  const getComboboxProps = useMemo(() => {
182
- const childWithRef = children;
183
- const isInput = childWithRef.type !== Button;
193
+ // Only access the type property to check if it's a Button component
194
+ const isInput = children.type !== Button;
184
195
  if (!isInput)
185
196
  return {};
186
197
  return {
@@ -216,6 +227,7 @@ function Dropdown(props) {
216
227
  loadingText,
217
228
  emptyText,
218
229
  emptyIcon,
230
+ loadingPosition,
219
231
  mode,
220
232
  value,
221
233
  scrollbarDefer,
@@ -242,6 +254,7 @@ function Dropdown(props) {
242
254
  loadingText,
243
255
  emptyText,
244
256
  emptyIcon,
257
+ loadingPosition,
245
258
  mode,
246
259
  value,
247
260
  scrollbarDefer,
@@ -251,13 +264,15 @@ function Dropdown(props) {
251
264
  ]);
252
265
  const triggerElement = useMemo(() => {
253
266
  const childWithRef = children;
254
- const composedRef = composeRefs([anchorRef, childWithRef.ref]);
267
+ const childProps = childWithRef.props;
268
+ const childRef = getElementRef(childWithRef);
269
+ const composedRef = composeRefs([anchorRef, childRef]);
270
+ const originalOnClick = childProps.onClick;
255
271
  return cloneElement(childWithRef, {
256
272
  ref: composedRef,
257
273
  ...getComboboxProps,
258
274
  onClick: (event) => {
259
- var _a, _b;
260
- (_b = (_a = childWithRef.props) === null || _a === void 0 ? void 0 : _a.onClick) === null || _b === void 0 ? void 0 : _b.call(_a, event);
275
+ originalOnClick === null || originalOnClick === void 0 ? void 0 : originalOnClick(event);
261
276
  if (event === null || event === void 0 ? void 0 : event.defaultPrevented)
262
277
  return;
263
278
  if (!isInline) {
@@ -271,13 +286,17 @@ function Dropdown(props) {
271
286
  return null;
272
287
  }
273
288
  const childWithRef = children;
274
- const composedRef = composeRefs([childWithRef.ref]);
289
+ const childProps = childWithRef.props;
290
+ const childRef = getElementRef(childWithRef);
291
+ const composedRef = composeRefs([childRef]);
292
+ const originalOnBlur = childProps.onBlur;
293
+ const originalOnClick = childProps.onClick;
294
+ const originalOnFocus = childProps.onFocus;
275
295
  return cloneElement(childWithRef, {
276
296
  ref: composedRef,
277
297
  ...getComboboxProps,
278
298
  onBlur: (event) => {
279
- var _a, _b;
280
- (_b = (_a = childWithRef.props) === null || _a === void 0 ? void 0 : _a.onBlur) === null || _b === void 0 ? void 0 : _b.call(_a, event);
299
+ originalOnBlur === null || originalOnBlur === void 0 ? void 0 : originalOnBlur(event);
281
300
  if (event === null || event === void 0 ? void 0 : event.defaultPrevented)
282
301
  return;
283
302
  // When open is controlled, don't automatically close on blur
@@ -295,21 +314,19 @@ function Dropdown(props) {
295
314
  setOpen(false);
296
315
  },
297
316
  onClick: (event) => {
298
- var _a, _b;
299
- (_b = (_a = childWithRef.props) === null || _a === void 0 ? void 0 : _a.onClick) === null || _b === void 0 ? void 0 : _b.call(_a, event);
317
+ originalOnClick === null || originalOnClick === void 0 ? void 0 : originalOnClick(event);
300
318
  if (event === null || event === void 0 ? void 0 : event.defaultPrevented)
301
319
  return;
302
320
  setOpen(true);
303
321
  },
304
322
  onFocus: (event) => {
305
- var _a, _b;
306
- (_b = (_a = childWithRef.props) === null || _a === void 0 ? void 0 : _a.onFocus) === null || _b === void 0 ? void 0 : _b.call(_a, event);
323
+ originalOnFocus === null || originalOnFocus === void 0 ? void 0 : originalOnFocus(event);
307
324
  if (event === null || event === void 0 ? void 0 : event.defaultPrevented)
308
325
  return;
309
326
  setOpen(true);
310
327
  },
311
328
  });
312
- }, [children, getComboboxProps, isInline, setOpen, isOpenControlled]);
329
+ }, [children, getComboboxProps, isInline, isOpenControlled, setOpen]);
313
330
  useDocumentEvents(() => {
314
331
  if (!isOpen) {
315
332
  return;
@@ -1,5 +1,5 @@
1
1
  import { ReactNode } from 'react';
2
- import { DropdownItemSharedProps, DropdownOptionsByType, DropdownStatus as DropdownStatusType, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
2
+ import { DropdownItemSharedProps, DropdownLoadingPosition, DropdownOptionsByType, DropdownStatus as DropdownStatusType, DropdownType } from '@mezzanine-ui/core/dropdown/dropdown';
3
3
  import { type IconDefinition } from '@mezzanine-ui/icons';
4
4
  import type { PartialOptions } from 'overlayscrollbars';
5
5
  import { type DropdownActionProps } from './DropdownAction';
@@ -74,6 +74,12 @@ export interface DropdownItemProps<T extends DropdownType | undefined = Dropdown
74
74
  * The icon of the dropdown empty status.
75
75
  */
76
76
  emptyIcon?: IconDefinition;
77
+ /**
78
+ * The position to display the loading status.
79
+ * Only takes effect when `status === 'loading'`.
80
+ * @default 'full'
81
+ */
82
+ loadingPosition?: DropdownLoadingPosition;
77
83
  /**
78
84
  * Callback fired when the dropdown list reaches the bottom.
79
85
  * Only fires when `maxHeight` is set and the list is scrollable.