@topconsultnpm/sdkui-react 6.20.0-dev1.13 → 6.20.0-dev1.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/assets/Toppy-help-center.png +0 -0
- package/lib/assets/headergradient.svg +87 -0
- package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +322 -30
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +8 -1
- package/lib/components/NewComponents/ContextMenu/hooks.js +80 -8
- package/lib/components/NewComponents/ContextMenu/index.d.ts +3 -0
- package/lib/components/NewComponents/ContextMenu/index.js +2 -0
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +9 -1
- package/lib/components/NewComponents/ContextMenu/styles.js +146 -47
- package/lib/components/NewComponents/ContextMenu/types.d.ts +22 -3
- package/lib/components/NewComponents/ContextMenu/useLongPress.d.ts +21 -0
- package/lib/components/NewComponents/ContextMenu/useLongPress.js +112 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +620 -125
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +25 -5
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +215 -59
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +12 -3
- package/lib/components/base/TMAccordionNew.js +35 -14
- package/lib/components/base/TMButton.js +6 -0
- package/lib/components/base/TMClosableList.js +4 -0
- package/lib/components/base/TMCustomButton.js +61 -17
- package/lib/components/base/TMDataGrid.d.ts +7 -4
- package/lib/components/base/TMDataGrid.js +153 -11
- package/lib/components/base/TMDropDownMenu.js +21 -18
- package/lib/components/base/TMFileManager.d.ts +4 -3
- package/lib/components/base/TMFileManager.js +32 -24
- package/lib/components/base/TMFileManagerDataGridView.d.ts +3 -2
- package/lib/components/base/TMFileManagerDataGridView.js +1 -11
- package/lib/components/base/TMFileManagerThumbnailItems.d.ts +7 -1
- package/lib/components/base/TMFileManagerThumbnailItems.js +5 -2
- package/lib/components/base/TMFileManagerThumbnailsView.d.ts +17 -4
- package/lib/components/base/TMFileManagerThumbnailsView.js +18 -6
- package/lib/components/base/TMFileManagerUtils.d.ts +0 -12
- package/lib/components/base/TMListView.js +33 -15
- package/lib/components/base/TMPanel.d.ts +1 -1
- package/lib/components/base/TMPanel.js +4 -2
- package/lib/components/base/TMPopUp.js +6 -0
- package/lib/components/base/TMToolbarCard.js +2 -0
- package/lib/components/base/TMTreeView.d.ts +2 -1
- package/lib/components/base/TMTreeView.js +33 -26
- package/lib/components/choosers/TMDataListItemChooser.d.ts +2 -0
- package/lib/components/choosers/TMDataListItemChooser.js +8 -2
- package/lib/components/choosers/TMDcmtTypeChooser.d.ts +1 -0
- package/lib/components/choosers/TMDcmtTypeChooser.js +11 -3
- package/lib/components/choosers/TMDistinctValues.js +2 -2
- package/lib/components/choosers/TMDynDataListItemChooser.d.ts +2 -0
- package/lib/components/choosers/TMDynDataListItemChooser.js +8 -2
- package/lib/components/choosers/TMInvoiceRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMMetadataChooser.d.ts +2 -0
- package/lib/components/choosers/TMMetadataChooser.js +19 -4
- package/lib/components/choosers/TMOrderRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMUserChooser.d.ts +2 -5
- package/lib/components/choosers/TMUserChooser.js +33 -47
- package/lib/components/editors/TMCheckBox.js +2 -0
- package/lib/components/editors/TMDateBox.js +18 -9
- package/lib/components/editors/TMEditorStyled.js +7 -0
- package/lib/components/editors/TMLocalizedTextBox.d.ts +3 -1
- package/lib/components/editors/TMLocalizedTextBox.js +16 -14
- package/lib/components/editors/TMMetadataEditor.d.ts +1 -0
- package/lib/components/editors/TMMetadataEditor.js +4 -4
- package/lib/components/editors/TMMetadataTextBox.d.ts +9 -0
- package/lib/components/editors/TMMetadataTextBox.js +92 -0
- package/lib/components/editors/TMMetadataValues.d.ts +2 -0
- package/lib/components/editors/TMMetadataValues.js +26 -8
- package/lib/components/editors/TMRadioButton.js +2 -0
- package/lib/components/editors/TMTextArea.js +18 -30
- package/lib/components/editors/TMTextBox.d.ts +1 -1
- package/lib/components/editors/TMTextBox.js +29 -4
- package/lib/components/editors/TMTextExpression.js +6 -91
- package/lib/components/features/archive/TMArchive.js +2 -2
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.d.ts +15 -0
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +462 -0
- package/lib/components/features/assistant/TMToppySpeechBubble.d.ts +11 -0
- package/lib/components/features/assistant/TMToppySpeechBubble.js +126 -0
- package/lib/components/features/documents/TMDcmtBlog.js +1 -1
- package/lib/components/features/documents/TMDcmtForm.d.ts +14 -2
- package/lib/components/features/documents/TMDcmtForm.js +576 -292
- package/lib/components/features/documents/TMDcmtPreview.js +42 -155
- package/lib/components/features/documents/TMDcmtTasks.js +9 -9
- package/lib/components/features/documents/TMMasterDetailDcmts.js +38 -53
- package/lib/components/features/documents/TMRelationViewer.d.ts +1 -1
- package/lib/components/features/documents/TMRelationViewer.js +2 -2
- package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +8 -0
- package/lib/components/features/search/{TMSearchResultCheckoutInfoForm.js → TMDcmtCheckoutInfoForm.js} +2 -2
- package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
- package/lib/components/features/search/TMSearch.d.ts +3 -0
- package/lib/components/features/search/TMSearch.js +50 -11
- package/lib/components/features/search/TMSearchQueryEditor.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryEditor.js +10 -10
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryPanel.js +40 -25
- package/lib/components/features/search/TMSearchResult.d.ts +3 -0
- package/lib/components/features/search/TMSearchResult.js +370 -252
- package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
- package/lib/components/features/search/TMSearchResultsMenuItems.js +227 -171
- package/lib/components/features/search/TMSignSettingsForm.js +1 -1
- package/lib/components/features/search/TMSignatureInfoContent.d.ts +6 -0
- package/lib/components/features/search/TMSignatureInfoContent.js +140 -0
- package/lib/components/features/search/TMViewHistoryDcmt.js +47 -52
- package/lib/components/features/tasks/TMTaskForm.js +75 -25
- package/lib/components/features/tasks/TMTasksAgenda.d.ts +3 -1
- package/lib/components/features/tasks/TMTasksAgenda.js +48 -9
- package/lib/components/features/tasks/TMTasksCalendar.d.ts +2 -0
- package/lib/components/features/tasks/TMTasksCalendar.js +19 -7
- package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
- package/lib/components/features/tasks/TMTasksUtils.js +57 -37
- package/lib/components/features/tasks/TMTasksView.js +28 -19
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +33 -2
- package/lib/components/features/workflow/TMWorkflowPopup.js +140 -34
- package/lib/components/features/workflow/diagram/DiagramItemComponent.d.ts +2 -0
- package/lib/components/features/workflow/diagram/DiagramItemComponent.js +14 -7
- package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
- package/lib/components/features/workflow/diagram/RecipientList.js +3 -2
- package/lib/components/features/workflow/diagram/WFDiagram.d.ts +4 -0
- package/lib/components/features/workflow/diagram/WFDiagram.js +164 -13
- package/lib/components/forms/Login/LoginValidatorService.d.ts +2 -0
- package/lib/components/forms/Login/LoginValidatorService.js +7 -2
- package/lib/components/forms/Login/TMLoginForm.js +35 -7
- package/lib/components/forms/TMChooserForm.js +1 -1
- package/lib/components/grids/TMBlogsPost.js +56 -31
- package/lib/components/grids/TMRecentsManager.js +20 -10
- package/lib/components/grids/TMValidationItemsList.js +6 -0
- package/lib/components/index.d.ts +6 -3
- package/lib/components/index.js +6 -3
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +13 -5
- package/lib/components/query/TMQueryEditor.d.ts +6 -1
- package/lib/components/query/TMQueryEditor.js +105 -101
- package/lib/components/settings/SettingsAppearance.d.ts +2 -1
- package/lib/components/settings/SettingsAppearance.js +99 -30
- package/lib/components/sidebar/TMHeader.js +11 -7
- package/lib/components/sidebar/TMSidebar.d.ts +0 -1
- package/lib/components/sidebar/TMSidebar.js +16 -44
- package/lib/components/sidebar/TMSidebarItem.js +36 -17
- package/lib/components/viewers/TMDataListItemViewer.d.ts +2 -1
- package/lib/components/viewers/TMDataListItemViewer.js +35 -71
- package/lib/components/viewers/TMDataUserIdItemViewer.d.ts +8 -0
- package/lib/components/viewers/TMDataUserIdItemViewer.js +39 -0
- package/lib/css/tm-sdkui.css +1 -1
- package/lib/helper/SDKUI_Globals.d.ts +22 -0
- package/lib/helper/SDKUI_Globals.js +10 -1
- package/lib/helper/SDKUI_Localizator.d.ts +21 -3
- package/lib/helper/SDKUI_Localizator.js +196 -10
- package/lib/helper/TMCommandsContextMenu.d.ts +4 -2
- package/lib/helper/TMCommandsContextMenu.js +15 -4
- package/lib/helper/TMIcons.d.ts +4 -0
- package/lib/helper/TMIcons.js +13 -3
- package/lib/helper/TMPdfViewer.d.ts +8 -0
- package/lib/helper/TMPdfViewer.js +373 -0
- package/lib/helper/TMToppyMessage.js +4 -0
- package/lib/helper/checkinCheckoutManager.d.ts +31 -1
- package/lib/helper/checkinCheckoutManager.js +112 -30
- package/lib/helper/devextremeCustomMessages.d.ts +30 -0
- package/lib/helper/devextremeCustomMessages.js +30 -0
- package/lib/helper/helpers.d.ts +30 -2
- package/lib/helper/helpers.js +132 -4
- package/lib/helper/index.d.ts +2 -0
- package/lib/helper/index.js +2 -0
- package/lib/helper/queryHelper.d.ts +2 -2
- package/lib/helper/queryHelper.js +80 -24
- package/lib/helper/workItemsHelper.d.ts +6 -0
- package/lib/helper/workItemsHelper.js +230 -0
- package/lib/hooks/useCheckInOutOperations.d.ts +28 -0
- package/lib/hooks/useCheckInOutOperations.js +223 -0
- package/lib/hooks/useDataListItem.d.ts +12 -0
- package/lib/hooks/useDataListItem.js +132 -0
- package/lib/hooks/useDataUserIdItem.d.ts +10 -0
- package/lib/hooks/useDataUserIdItem.js +96 -0
- package/lib/hooks/useFloatingBarPinnedItems.d.ts +11 -0
- package/lib/hooks/useFloatingBarPinnedItems.js +54 -0
- package/lib/hooks/useMetadataExpression.d.ts +19 -0
- package/lib/hooks/useMetadataExpression.js +99 -0
- package/lib/hooks/useSettingsFeedback.d.ts +11 -0
- package/lib/hooks/useSettingsFeedback.js +38 -0
- package/lib/hooks/useWorkflowApprove.d.ts +4 -0
- package/lib/hooks/useWorkflowApprove.js +14 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +3 -2
- package/lib/services/platform_services.d.ts +3 -3
- package/lib/ts/types.d.ts +61 -1
- package/lib/utils/theme.d.ts +1 -1
- package/lib/utils/theme.js +1 -1
- package/package.json +6 -5
- package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
- package/lib/components/NewComponents/Notification/Notification.js +0 -60
- package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
- package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
- package/lib/components/NewComponents/Notification/index.d.ts +0 -2
- package/lib/components/NewComponents/Notification/index.js +0 -2
- package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
- package/lib/components/NewComponents/Notification/styles.js +0 -180
- package/lib/components/NewComponents/Notification/types.d.ts +0 -18
- package/lib/components/NewComponents/Notification/types.js +0 -1
- package/lib/components/base/TMContextMenu.d.ts +0 -25
- package/lib/components/base/TMContextMenu.js +0 -109
- package/lib/components/base/TMContextMenuOLD.d.ts +0 -26
- package/lib/components/base/TMContextMenuOLD.js +0 -56
- package/lib/components/base/TMFloatingToolbar.d.ts +0 -9
- package/lib/components/base/TMFloatingToolbar.js +0 -101
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.d.ts +0 -30
- package/lib/components/features/assistant/ToppyDraggableHelpCenter.js +0 -482
- package/lib/components/features/assistant/ToppySpeechBubble.d.ts +0 -9
- package/lib/components/features/assistant/ToppySpeechBubble.js +0 -117
- package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
1
|
+
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
|
+
export const useIsIOS = () => {
|
|
3
|
+
const [isIOS, setIsIOS] = useState(false);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
6
|
+
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
7
|
+
setIsIOS(iOS);
|
|
8
|
+
}, []);
|
|
9
|
+
return isIOS;
|
|
10
|
+
};
|
|
2
11
|
export const useIsMobile = () => {
|
|
3
12
|
const [isMobile, setIsMobile] = useState(false);
|
|
4
13
|
useEffect(() => {
|
|
@@ -29,20 +38,83 @@ export const useClickOutside = (callback) => {
|
|
|
29
38
|
}, [callback]);
|
|
30
39
|
return ref;
|
|
31
40
|
};
|
|
32
|
-
export const useMenuPosition = (menuRef, position) => {
|
|
33
|
-
const [adjustedPosition, setAdjustedPosition] = useState({ openLeft: false, openUp: false });
|
|
34
|
-
|
|
35
|
-
|
|
41
|
+
export const useMenuPosition = (menuRef, position, itemsCount) => {
|
|
42
|
+
const [adjustedPosition, setAdjustedPosition] = useState({ openLeft: false, openUp: false, needsScroll: false, maxHeight: undefined });
|
|
43
|
+
const [isCalculated, setIsCalculated] = useState(false);
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
if (!menuRef.current) {
|
|
46
|
+
setIsCalculated(false);
|
|
36
47
|
return;
|
|
48
|
+
}
|
|
37
49
|
const menuRect = menuRef.current.getBoundingClientRect();
|
|
38
50
|
const viewportWidth = window.innerWidth;
|
|
39
51
|
const viewportHeight = window.innerHeight;
|
|
52
|
+
const isMobile = viewportWidth <= 768;
|
|
40
53
|
const spaceRight = viewportWidth - position.x;
|
|
41
54
|
const spaceBottom = viewportHeight - position.y;
|
|
55
|
+
const spaceAbove = position.y;
|
|
56
|
+
const padding = 8; // Minimal padding from viewport edges - be more aggressive about using available space
|
|
57
|
+
// Use scrollHeight to get natural content height, not constrained height
|
|
58
|
+
const menuHeight = menuRef.current.scrollHeight;
|
|
59
|
+
// Mobile: Always calculate max-height based on position to prevent overflow
|
|
60
|
+
if (isMobile) {
|
|
61
|
+
const maxHeightFromBottom = spaceBottom - padding;
|
|
62
|
+
const maxHeightFromTop = spaceAbove - padding;
|
|
63
|
+
const mobileMaxHeight = Math.max(maxHeightFromBottom, maxHeightFromTop);
|
|
64
|
+
setAdjustedPosition({
|
|
65
|
+
openLeft: spaceRight < menuRect.width + 20,
|
|
66
|
+
openUp: maxHeightFromTop > maxHeightFromBottom && menuHeight > maxHeightFromBottom,
|
|
67
|
+
needsScroll: menuHeight > mobileMaxHeight,
|
|
68
|
+
maxHeight: mobileMaxHeight,
|
|
69
|
+
});
|
|
70
|
+
setIsCalculated(true);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Desktop: Check if menu is too tall to fit in either direction
|
|
74
|
+
const fitsBelow = menuHeight + padding <= spaceBottom;
|
|
75
|
+
const fitsAbove = menuHeight + padding <= spaceAbove;
|
|
76
|
+
const needsScroll = !fitsBelow && !fitsAbove;
|
|
77
|
+
// Calculate max height when scrolling is needed
|
|
78
|
+
let maxHeight = undefined;
|
|
79
|
+
let shouldOpenUp = false;
|
|
80
|
+
if (needsScroll) {
|
|
81
|
+
// When scrolling is needed, open in the direction with MORE space
|
|
82
|
+
const availableSpace = Math.max(spaceBottom, spaceAbove);
|
|
83
|
+
maxHeight = availableSpace - padding;
|
|
84
|
+
shouldOpenUp = spaceAbove > spaceBottom; // Open upward if more space above
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// Normal behavior: open up only if it fits above but not below
|
|
88
|
+
shouldOpenUp = !fitsBelow && fitsAbove;
|
|
89
|
+
}
|
|
42
90
|
setAdjustedPosition({
|
|
43
91
|
openLeft: spaceRight < menuRect.width + 20,
|
|
44
|
-
openUp:
|
|
92
|
+
openUp: shouldOpenUp,
|
|
93
|
+
needsScroll,
|
|
94
|
+
maxHeight,
|
|
45
95
|
});
|
|
46
|
-
|
|
47
|
-
|
|
96
|
+
setIsCalculated(true);
|
|
97
|
+
}, [position, menuRef, itemsCount]); // Added itemsCount to recalculate when menu content changes
|
|
98
|
+
return { ...adjustedPosition, isCalculated };
|
|
99
|
+
};
|
|
100
|
+
export const getContextMenuTarget = (event, focusedItem, selectedItem, dataSource, idPrefix, isMobile) => {
|
|
101
|
+
if (!event)
|
|
102
|
+
return undefined;
|
|
103
|
+
let targetItem = focusedItem ?? selectedItem;
|
|
104
|
+
if (!focusedItem && isMobile) {
|
|
105
|
+
// Find the actual item that was long-pressed by traversing up from the event target
|
|
106
|
+
let element = event.target;
|
|
107
|
+
while (element && element !== event.currentTarget) {
|
|
108
|
+
if (element.id && element.id.startsWith(`${idPrefix}-`)) {
|
|
109
|
+
const itemId = element.id.replace(`${idPrefix}-`, '');
|
|
110
|
+
const foundItem = dataSource.find(item => item.id === itemId);
|
|
111
|
+
if (foundItem) {
|
|
112
|
+
targetItem = foundItem;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
element = element.parentElement;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return targetItem;
|
|
48
120
|
};
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { default as ContextMenu } from './TMContextMenu';
|
|
2
2
|
export type { TMContextMenuItemProps, TMContextMenuProps } from './types';
|
|
3
|
+
export { useLongPress, triggerContextMenuEvent } from './useLongPress';
|
|
4
|
+
export type { UseLongPressOptions } from './useLongPress';
|
|
5
|
+
export { useIsIOS } from './hooks';
|
|
@@ -3,6 +3,10 @@ export declare const MenuContainer: import("styled-components/dist/types").IStyl
|
|
|
3
3
|
$y: number;
|
|
4
4
|
$openLeft: boolean;
|
|
5
5
|
$openUp: boolean;
|
|
6
|
+
$isPositioned: boolean;
|
|
7
|
+
$externalControl?: boolean;
|
|
8
|
+
$needsScroll?: boolean;
|
|
9
|
+
$maxHeight?: number;
|
|
6
10
|
}>> & string;
|
|
7
11
|
export declare const MenuItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
8
12
|
$disabled?: boolean;
|
|
@@ -12,13 +16,17 @@ export declare const MenuItem: import("styled-components/dist/types").IStyledCom
|
|
|
12
16
|
export declare const MenuItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
13
17
|
export declare const IconWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>> & string;
|
|
14
18
|
export declare const MenuItemName: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>> & string;
|
|
15
|
-
export declare const RightIconButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
|
|
19
|
+
export declare const RightIconButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
|
|
20
|
+
ref?: ((instance: HTMLButtonElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLButtonElement> | null | undefined;
|
|
21
|
+
}>, never>, never>> & string;
|
|
16
22
|
export declare const SubmenuIndicator: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, {
|
|
17
23
|
$isMobile?: boolean;
|
|
18
24
|
}>> & string;
|
|
19
25
|
export declare const Submenu: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
20
26
|
$parentRect: DOMRect;
|
|
21
27
|
$openUp?: boolean;
|
|
28
|
+
$needsScroll?: boolean;
|
|
29
|
+
$maxHeight?: number;
|
|
22
30
|
}>> & string;
|
|
23
31
|
export declare const MobileMenuHeader: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
24
32
|
export declare const BackButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
|
|
@@ -25,7 +25,7 @@ export const MenuContainer = styled.div `
|
|
|
25
25
|
right: ${props => props.$openLeft ? `${window.innerWidth - props.$x}px` : 'auto'};
|
|
26
26
|
top: ${props => props.$openUp ? 'auto' : `${props.$y}px`};
|
|
27
27
|
bottom: ${props => props.$openUp ? `${window.innerHeight - props.$y}px` : 'auto'};
|
|
28
|
-
z-index:
|
|
28
|
+
z-index: 10100;
|
|
29
29
|
background: #ffffff;
|
|
30
30
|
border-radius: 12px;
|
|
31
31
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
|
|
@@ -36,6 +36,42 @@ export const MenuContainer = styled.div `
|
|
|
36
36
|
animation: ${fadeIn} 0.15s ease-out;
|
|
37
37
|
backdrop-filter: blur(10px);
|
|
38
38
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
39
|
+
opacity: ${props => props.$isPositioned ? 1 : 0};
|
|
40
|
+
transition: opacity 0.05s ease-in;
|
|
41
|
+
|
|
42
|
+
/* Add scrolling when menu is too tall to fit in either direction */
|
|
43
|
+
${props => props.$needsScroll && props.$maxHeight && `
|
|
44
|
+
max-height: ${props.$maxHeight}px;
|
|
45
|
+
overflow-y: auto;
|
|
46
|
+
overflow-x: hidden;
|
|
47
|
+
|
|
48
|
+
/* Smooth scrolling */
|
|
49
|
+
scroll-behavior: smooth;
|
|
50
|
+
|
|
51
|
+
/* Custom scrollbar styling */
|
|
52
|
+
&::-webkit-scrollbar {
|
|
53
|
+
width: 8px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&::-webkit-scrollbar-track {
|
|
57
|
+
background: rgba(0, 0, 0, 0.05);
|
|
58
|
+
border-radius: 4px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&::-webkit-scrollbar-thumb {
|
|
62
|
+
background: rgba(0, 0, 0, 0.2);
|
|
63
|
+
border-radius: 4px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
67
|
+
background: rgba(0, 0, 0, 0.3);
|
|
68
|
+
}
|
|
69
|
+
`}
|
|
70
|
+
|
|
71
|
+
/* Reset color inheritance from parent with !important to override panel header styles */
|
|
72
|
+
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
73
|
+
color: #1a1a1a !important;
|
|
74
|
+
}
|
|
39
75
|
|
|
40
76
|
[data-theme='dark'] & {
|
|
41
77
|
background: #2a2a2a;
|
|
@@ -43,18 +79,33 @@ export const MenuContainer = styled.div `
|
|
|
43
79
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
|
44
80
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
45
81
|
}
|
|
82
|
+
|
|
83
|
+
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
84
|
+
color: #e0e0e0 !important;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
${props => props.$externalControl && `
|
|
88
|
+
@media (max-width: 768px) {
|
|
89
|
+
left: 75px !important;
|
|
90
|
+
right: 75px !important;
|
|
91
|
+
max-width: calc(100vw - 150px);
|
|
92
|
+
width: auto;
|
|
93
|
+
min-width: auto;
|
|
94
|
+
}
|
|
95
|
+
`}
|
|
46
96
|
`;
|
|
47
97
|
export const MenuItem = styled.div `
|
|
48
98
|
display: flex;
|
|
49
99
|
align-items: center;
|
|
50
100
|
justify-content: space-between;
|
|
51
101
|
padding: 4px 12px;
|
|
52
|
-
cursor: ${props => props.$disabled ? '
|
|
53
|
-
opacity: ${props => props.$disabled ? 0.4 : 1};
|
|
102
|
+
cursor: ${props => props.$disabled ? 'default' : 'pointer'};
|
|
54
103
|
transition: all 0.15s ease;
|
|
55
104
|
position: relative;
|
|
56
105
|
user-select: none;
|
|
57
|
-
|
|
106
|
+
-webkit-touch-callout: none;
|
|
107
|
+
-webkit-user-select: none;
|
|
108
|
+
font-size: var(--base-font-size, 13px);
|
|
58
109
|
color: ${props => props.$disabled ? '#999' : '#1a1a1a'};
|
|
59
110
|
font-weight: 500;
|
|
60
111
|
${props => props.$beginGroup && `
|
|
@@ -63,12 +114,26 @@ export const MenuItem = styled.div `
|
|
|
63
114
|
padding-top: 8px;
|
|
64
115
|
`}
|
|
65
116
|
|
|
117
|
+
/* Apply opacity only to direct children except right-icon-btn */
|
|
118
|
+
& > *:not(.right-icon-btn) {
|
|
119
|
+
opacity: ${props => props.$disabled ? 0.4 : 1};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* Right icon button hidden by default, shown on hover */
|
|
123
|
+
& .right-icon-btn {
|
|
124
|
+
cursor: pointer !important;
|
|
125
|
+
}
|
|
126
|
+
|
|
66
127
|
&:hover {
|
|
67
128
|
${props => !props.$disabled && `
|
|
68
129
|
background: linear-gradient(90deg, #f0f7ff 0%, #e6f2ff 100%);
|
|
69
130
|
color: #0066cc;
|
|
70
|
-
padding-left: 14px;
|
|
71
131
|
`}
|
|
132
|
+
|
|
133
|
+
/* Show right icon on hover */
|
|
134
|
+
& .right-icon-btn {
|
|
135
|
+
opacity: 1 !important;
|
|
136
|
+
}
|
|
72
137
|
}
|
|
73
138
|
|
|
74
139
|
&:active {
|
|
@@ -99,8 +164,8 @@ export const MenuItem = styled.div `
|
|
|
99
164
|
}
|
|
100
165
|
|
|
101
166
|
@media (max-width: 768px) {
|
|
102
|
-
padding:
|
|
103
|
-
font-size:
|
|
167
|
+
padding: 4px 10px;
|
|
168
|
+
font-size: calc(var(--base-font-size, 13px) * 0.92);
|
|
104
169
|
}
|
|
105
170
|
`;
|
|
106
171
|
export const MenuItemContent = styled.div `
|
|
@@ -113,7 +178,7 @@ export const IconWrapper = styled.span `
|
|
|
113
178
|
display: flex;
|
|
114
179
|
align-items: center;
|
|
115
180
|
justify-content: center;
|
|
116
|
-
font-size:
|
|
181
|
+
font-size: calc(var(--base-font-size, 13px) * 1.08);
|
|
117
182
|
width: 18px;
|
|
118
183
|
height: 18px;
|
|
119
184
|
`;
|
|
@@ -124,23 +189,23 @@ export const MenuItemName = styled.span `
|
|
|
124
189
|
overflow-wrap: break-word;
|
|
125
190
|
line-height: 1.4;
|
|
126
191
|
`;
|
|
127
|
-
export const RightIconButton = styled.button
|
|
192
|
+
export const RightIconButton = styled.button.attrs({
|
|
193
|
+
className: 'right-icon-btn'
|
|
194
|
+
}) `
|
|
128
195
|
display: flex;
|
|
129
196
|
align-items: center;
|
|
130
197
|
justify-content: center;
|
|
131
198
|
background: transparent;
|
|
132
199
|
border: none;
|
|
133
|
-
cursor: pointer;
|
|
200
|
+
cursor: pointer !important;
|
|
134
201
|
padding: 4px 8px;
|
|
135
202
|
margin-left: 8px;
|
|
136
203
|
border-radius: 6px;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
transition: all 0.15s ease;
|
|
204
|
+
font-size: calc(var(--base-font-size, 13px) * 1.08);
|
|
205
|
+
opacity: 0 !important;
|
|
206
|
+
transition: opacity 0.15s ease, background 0.15s ease, transform 0.15s ease;
|
|
141
207
|
|
|
142
208
|
&:hover {
|
|
143
|
-
opacity: 1;
|
|
144
209
|
background: rgba(0, 0, 0, 0.05);
|
|
145
210
|
transform: scale(1.1);
|
|
146
211
|
}
|
|
@@ -158,12 +223,12 @@ export const RightIconButton = styled.button `
|
|
|
158
223
|
export const SubmenuIndicator = styled.span `
|
|
159
224
|
display: flex;
|
|
160
225
|
align-items: center;
|
|
161
|
-
font-size:
|
|
226
|
+
font-size: calc(var(--base-font-size, 13px) * 0.92);
|
|
162
227
|
margin-left: 8px;
|
|
163
228
|
opacity: 0.6;
|
|
164
229
|
transition: transform 0.15s ease;
|
|
165
230
|
|
|
166
|
-
${MenuItem}:hover & {
|
|
231
|
+
${MenuItem}:hover:not([data-disabled="true"]) & {
|
|
167
232
|
${props => !props.$isMobile && `
|
|
168
233
|
transform: translateX(2px);
|
|
169
234
|
opacity: 1;
|
|
@@ -174,11 +239,11 @@ export const Submenu = styled.div `
|
|
|
174
239
|
position: fixed;
|
|
175
240
|
left: ${props => {
|
|
176
241
|
const spaceOnRight = globalThis.innerWidth - props.$parentRect.right;
|
|
177
|
-
return spaceOnRight > 240 ? `${props.$parentRect.right -
|
|
242
|
+
return spaceOnRight > 240 ? `${props.$parentRect.right - 4}px` : 'auto';
|
|
178
243
|
}};
|
|
179
244
|
right: ${props => {
|
|
180
245
|
const spaceOnRight = globalThis.innerWidth - props.$parentRect.right;
|
|
181
|
-
return spaceOnRight > 240 ? 'auto' : `${globalThis.innerWidth - props.$parentRect.left +
|
|
246
|
+
return spaceOnRight > 240 ? 'auto' : `${globalThis.innerWidth - props.$parentRect.left + 4}px`;
|
|
182
247
|
}};
|
|
183
248
|
/* Vertical positioning: Each submenu independently decides direction based on its own space */
|
|
184
249
|
top: ${props => {
|
|
@@ -189,7 +254,7 @@ export const Submenu = styled.div `
|
|
|
189
254
|
// If openUp is true, anchor to bottom and grow upward
|
|
190
255
|
return props.$openUp ? `${globalThis.innerHeight - props.$parentRect.bottom - 8}px` : 'auto';
|
|
191
256
|
}};
|
|
192
|
-
z-index:
|
|
257
|
+
z-index: 10101;
|
|
193
258
|
background: #ffffff;
|
|
194
259
|
border-radius: 12px;
|
|
195
260
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
|
|
@@ -201,39 +266,88 @@ export const Submenu = styled.div `
|
|
|
201
266
|
backdrop-filter: blur(10px);
|
|
202
267
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
203
268
|
|
|
204
|
-
/*
|
|
269
|
+
/* Invisible hover bridge on the LEFT side (for submenus opening left) */
|
|
205
270
|
&::before {
|
|
206
271
|
content: '';
|
|
207
272
|
position: absolute;
|
|
208
273
|
right: 100%;
|
|
209
|
-
top:
|
|
210
|
-
bottom:
|
|
211
|
-
width:
|
|
274
|
+
top: -20px;
|
|
275
|
+
bottom: -20px;
|
|
276
|
+
width: 25px;
|
|
212
277
|
background: transparent;
|
|
213
278
|
}
|
|
214
279
|
|
|
215
|
-
/*
|
|
280
|
+
/* Invisible hover bridge on the RIGHT side (for submenus opening right) */
|
|
216
281
|
&::after {
|
|
217
282
|
content: '';
|
|
218
283
|
position: absolute;
|
|
219
284
|
left: 100%;
|
|
220
|
-
top:
|
|
221
|
-
bottom:
|
|
222
|
-
width:
|
|
285
|
+
top: -20px;
|
|
286
|
+
bottom: -20px;
|
|
287
|
+
width: 25px;
|
|
223
288
|
background: transparent;
|
|
224
289
|
}
|
|
225
290
|
|
|
291
|
+
/* Reset color inheritance from parent with !important to override panel header styles */
|
|
292
|
+
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
293
|
+
color: #1a1a1a !important;
|
|
294
|
+
}
|
|
295
|
+
|
|
226
296
|
[data-theme='dark'] & {
|
|
227
297
|
background: #2a2a2a;
|
|
228
298
|
border-color: rgba(255, 255, 255, 0.1);
|
|
229
299
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
|
230
300
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
231
301
|
}
|
|
302
|
+
|
|
303
|
+
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
304
|
+
color: #e0e0e0 !important;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* Dynamic scroll handling when submenu is too tall */
|
|
308
|
+
${props => props.$needsScroll && props.$maxHeight ? `
|
|
309
|
+
max-height: ${props.$maxHeight}px;
|
|
310
|
+
overflow-y: auto;
|
|
311
|
+
overflow-x: hidden;
|
|
312
|
+
|
|
313
|
+
/* Custom scrollbar styling for submenus */
|
|
314
|
+
&::-webkit-scrollbar {
|
|
315
|
+
width: 8px;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
&::-webkit-scrollbar-track {
|
|
319
|
+
background: rgba(0, 0, 0, 0.05);
|
|
320
|
+
border-radius: 4px;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
&::-webkit-scrollbar-thumb {
|
|
324
|
+
background: rgba(0, 0, 0, 0.2);
|
|
325
|
+
border-radius: 4px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
329
|
+
background: rgba(0, 0, 0, 0.3);
|
|
330
|
+
}
|
|
331
|
+
` : ``}
|
|
332
|
+
|
|
333
|
+
[data-theme='dark'] & {
|
|
334
|
+
&::-webkit-scrollbar-track {
|
|
335
|
+
background: rgba(255, 255, 255, 0.05);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
&::-webkit-scrollbar-thumb {
|
|
339
|
+
background: rgba(255, 255, 255, 0.2);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
343
|
+
background: rgba(255, 255, 255, 0.3);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
232
346
|
`;
|
|
233
347
|
export const MobileMenuHeader = styled.div `
|
|
234
348
|
display: flex;
|
|
235
349
|
align-items: center;
|
|
236
|
-
padding:
|
|
350
|
+
padding: 4px 8px;
|
|
237
351
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
238
352
|
margin-bottom: 8px;
|
|
239
353
|
gap: 12px;
|
|
@@ -249,37 +363,22 @@ export const BackButton = styled.button `
|
|
|
249
363
|
display: flex;
|
|
250
364
|
align-items: center;
|
|
251
365
|
justify-content: center;
|
|
252
|
-
background: #0066cc;
|
|
253
|
-
color: white;
|
|
254
366
|
border: none;
|
|
255
367
|
border-radius: 8px;
|
|
256
368
|
width: 32px;
|
|
257
369
|
height: 32px;
|
|
258
370
|
cursor: pointer;
|
|
259
|
-
font-size: 18px;
|
|
260
371
|
transition: all 0.15s ease;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
background: #0052a3;
|
|
264
|
-
transform: scale(1.05);
|
|
265
|
-
}
|
|
372
|
+
font-size: 16px;
|
|
373
|
+
transform: translateY(-2px);
|
|
266
374
|
|
|
267
375
|
&:active {
|
|
268
376
|
transform: scale(0.95);
|
|
269
377
|
}
|
|
270
|
-
|
|
271
|
-
[data-theme='dark'] & {
|
|
272
|
-
background: #4db8ff;
|
|
273
|
-
color: #1a1a1a;
|
|
274
|
-
|
|
275
|
-
&:hover {
|
|
276
|
-
background: #66c2ff;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
378
|
`;
|
|
280
379
|
export const HeaderTitle = styled.h3 `
|
|
281
380
|
margin: 0;
|
|
282
|
-
font-size:
|
|
381
|
+
font-size: calc(var(--base-font-size, 13px) * 1.23);
|
|
283
382
|
font-weight: 600;
|
|
284
383
|
color: #1a1a1a;
|
|
285
384
|
flex: 1;
|
|
@@ -1,18 +1,37 @@
|
|
|
1
|
+
export interface RightIconProps {
|
|
2
|
+
icon: React.ReactNode;
|
|
3
|
+
activeColor?: string;
|
|
4
|
+
inactiveColor?: string;
|
|
5
|
+
isActive?: boolean;
|
|
6
|
+
onClick?: () => void;
|
|
7
|
+
}
|
|
1
8
|
export interface TMContextMenuItemProps {
|
|
9
|
+
id?: string;
|
|
2
10
|
name: string;
|
|
3
11
|
icon?: React.ReactNode;
|
|
4
12
|
disabled?: boolean;
|
|
5
|
-
onClick?: () => void;
|
|
13
|
+
onClick?: (data?: any) => void;
|
|
6
14
|
submenu?: TMContextMenuItemProps[];
|
|
7
15
|
visible?: boolean;
|
|
8
|
-
|
|
9
|
-
onRightIconClick?: () => void;
|
|
16
|
+
rightIconProps?: RightIconProps;
|
|
10
17
|
beginGroup?: boolean;
|
|
18
|
+
tooltip?: string;
|
|
19
|
+
operationType?: 'singleRow' | 'multiRow';
|
|
11
20
|
}
|
|
12
21
|
export interface TMContextMenuProps {
|
|
13
22
|
items: TMContextMenuItemProps[];
|
|
14
23
|
trigger?: 'right' | 'left';
|
|
15
24
|
children?: React.ReactNode;
|
|
25
|
+
target?: string;
|
|
26
|
+
externalControl?: {
|
|
27
|
+
visible: boolean;
|
|
28
|
+
position: {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
};
|
|
32
|
+
onClose: () => void;
|
|
33
|
+
};
|
|
34
|
+
keepOpenOnClick?: boolean;
|
|
16
35
|
}
|
|
17
36
|
export interface Position {
|
|
18
37
|
x: number;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface UseLongPressOptions {
|
|
2
|
+
containerRef: React.RefObject<HTMLElement>;
|
|
3
|
+
targetSelector: string | string[];
|
|
4
|
+
onLongPress: (event: {
|
|
5
|
+
clientX: number;
|
|
6
|
+
clientY: number;
|
|
7
|
+
target: HTMLElement;
|
|
8
|
+
}) => void;
|
|
9
|
+
onTouchStart?: (event: {
|
|
10
|
+
clientX: number;
|
|
11
|
+
clientY: number;
|
|
12
|
+
target: HTMLElement;
|
|
13
|
+
rowElement: HTMLElement;
|
|
14
|
+
}) => void;
|
|
15
|
+
duration?: number;
|
|
16
|
+
moveThreshold?: number;
|
|
17
|
+
hapticFeedback?: boolean;
|
|
18
|
+
enabled?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare function useLongPress({ containerRef, targetSelector, onLongPress, onTouchStart, duration, moveThreshold, hapticFeedback, enabled, }: UseLongPressOptions): void;
|
|
21
|
+
export declare function triggerContextMenuEvent(target: HTMLElement, clientX: number, clientY: number): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { useIsIOS } from './hooks';
|
|
3
|
+
export function useLongPress({ containerRef, targetSelector, onLongPress, onTouchStart, duration = 500, moveThreshold = 10, hapticFeedback = true, enabled = true, }) {
|
|
4
|
+
const isIOS = useIsIOS();
|
|
5
|
+
const longPressTriggeredRef = useRef(false);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (!isIOS || !enabled || !containerRef.current)
|
|
8
|
+
return;
|
|
9
|
+
const container = containerRef.current;
|
|
10
|
+
let longPressTimeout = null;
|
|
11
|
+
let touchStartPos = null;
|
|
12
|
+
let longPressTarget = null;
|
|
13
|
+
const matchesSelector = (element) => {
|
|
14
|
+
const selectors = Array.isArray(targetSelector) ? targetSelector : [targetSelector];
|
|
15
|
+
for (const selector of selectors) {
|
|
16
|
+
const match = element.closest(selector);
|
|
17
|
+
if (match)
|
|
18
|
+
return match;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
};
|
|
22
|
+
const handleTouchStart = (e) => {
|
|
23
|
+
const touch = e.touches[0];
|
|
24
|
+
const target = touch.target;
|
|
25
|
+
// Check if target matches any of the specified selectors
|
|
26
|
+
const matchedElement = matchesSelector(target);
|
|
27
|
+
if (!matchedElement)
|
|
28
|
+
return;
|
|
29
|
+
touchStartPos = { x: touch.clientX, y: touch.clientY };
|
|
30
|
+
longPressTriggeredRef.current = false;
|
|
31
|
+
longPressTarget = target;
|
|
32
|
+
// Call optional onTouchStart callback
|
|
33
|
+
if (onTouchStart) {
|
|
34
|
+
onTouchStart({
|
|
35
|
+
clientX: touch.clientX,
|
|
36
|
+
clientY: touch.clientY,
|
|
37
|
+
target,
|
|
38
|
+
rowElement: matchedElement,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
if (longPressTimeout)
|
|
42
|
+
clearTimeout(longPressTimeout);
|
|
43
|
+
longPressTimeout = setTimeout(() => {
|
|
44
|
+
longPressTriggeredRef.current = true;
|
|
45
|
+
// Haptic feedback
|
|
46
|
+
if (hapticFeedback && 'vibrate' in navigator) {
|
|
47
|
+
navigator.vibrate(50);
|
|
48
|
+
}
|
|
49
|
+
// Call onLongPress callback
|
|
50
|
+
onLongPress({
|
|
51
|
+
clientX: touch.clientX,
|
|
52
|
+
clientY: touch.clientY,
|
|
53
|
+
target: longPressTarget,
|
|
54
|
+
});
|
|
55
|
+
longPressTimeout = null;
|
|
56
|
+
}, duration);
|
|
57
|
+
};
|
|
58
|
+
const handleTouchMove = (e) => {
|
|
59
|
+
if (!touchStartPos || !longPressTimeout)
|
|
60
|
+
return;
|
|
61
|
+
const touch = e.touches[0];
|
|
62
|
+
const dx = Math.abs(touch.clientX - touchStartPos.x);
|
|
63
|
+
const dy = Math.abs(touch.clientY - touchStartPos.y);
|
|
64
|
+
// Cancel long-press if finger moved too much
|
|
65
|
+
if (dx > moveThreshold || dy > moveThreshold) {
|
|
66
|
+
clearTimeout(longPressTimeout);
|
|
67
|
+
longPressTimeout = null;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const handleTouchEnd = () => {
|
|
71
|
+
if (longPressTimeout) {
|
|
72
|
+
clearTimeout(longPressTimeout);
|
|
73
|
+
longPressTimeout = null;
|
|
74
|
+
}
|
|
75
|
+
touchStartPos = null;
|
|
76
|
+
longPressTarget = null;
|
|
77
|
+
};
|
|
78
|
+
// Prevent click after long-press to avoid unintended actions
|
|
79
|
+
const handleClick = (e) => {
|
|
80
|
+
if (longPressTriggeredRef.current) {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
e.stopPropagation();
|
|
83
|
+
e.stopImmediatePropagation();
|
|
84
|
+
longPressTriggeredRef.current = false;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
container.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
88
|
+
container.addEventListener('touchmove', handleTouchMove, { passive: true });
|
|
89
|
+
container.addEventListener('touchend', handleTouchEnd);
|
|
90
|
+
container.addEventListener('touchcancel', handleTouchEnd);
|
|
91
|
+
container.addEventListener('click', handleClick, { capture: true });
|
|
92
|
+
return () => {
|
|
93
|
+
if (longPressTimeout)
|
|
94
|
+
clearTimeout(longPressTimeout);
|
|
95
|
+
container.removeEventListener('touchstart', handleTouchStart);
|
|
96
|
+
container.removeEventListener('touchmove', handleTouchMove);
|
|
97
|
+
container.removeEventListener('touchend', handleTouchEnd);
|
|
98
|
+
container.removeEventListener('touchcancel', handleTouchEnd);
|
|
99
|
+
container.removeEventListener('click', handleClick, { capture: true });
|
|
100
|
+
};
|
|
101
|
+
}, [isIOS, enabled, containerRef, targetSelector, onLongPress, onTouchStart, duration, moveThreshold, hapticFeedback]);
|
|
102
|
+
}
|
|
103
|
+
export function triggerContextMenuEvent(target, clientX, clientY) {
|
|
104
|
+
const contextMenuEvent = new MouseEvent('contextmenu', {
|
|
105
|
+
bubbles: true,
|
|
106
|
+
cancelable: true,
|
|
107
|
+
clientX,
|
|
108
|
+
clientY,
|
|
109
|
+
button: 2,
|
|
110
|
+
});
|
|
111
|
+
target.dispatchEvent(contextMenuEvent);
|
|
112
|
+
}
|