@topconsultnpm/sdkui-react 6.20.0-dev1.56 → 6.20.0-dev1.57

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.
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
4
4
  import * as S from './styles';
5
5
  import { useIsMobile, useMenuPosition, useIsIOS } from './hooks';
6
6
  import { IconArrowLeft } from '../../../helper';
7
- const TMContextMenu = ({ items, trigger = 'right', children, externalControl, keepOpenOnClick = false }) => {
7
+ const TMContextMenu = ({ items, trigger = 'right', children, target, externalControl, keepOpenOnClick = false }) => {
8
8
  const [menuState, setMenuState] = useState({
9
9
  visible: false,
10
10
  position: { x: 0, y: 0 },
@@ -46,6 +46,91 @@ const TMContextMenu = ({ items, trigger = 'right', children, externalControl, ke
46
46
  }));
47
47
  }
48
48
  }, [externalControl, items]);
49
+ // iOS long-press support: attach touch listeners to target elements
50
+ // On long-press, dispatch synthetic contextmenu event to trigger existing handlers
51
+ useEffect(() => {
52
+ if (!target || !isIOS)
53
+ return;
54
+ const elements = document.querySelectorAll(target);
55
+ if (elements.length === 0)
56
+ return;
57
+ const touchStateMap = new WeakMap();
58
+ const handleTouchStart = (e) => {
59
+ const touchEvent = e;
60
+ const element = e.currentTarget;
61
+ const touch = touchEvent.touches[0];
62
+ let state = touchStateMap.get(element);
63
+ if (!state) {
64
+ state = { timeout: null, startX: 0, startY: 0 };
65
+ touchStateMap.set(element, state);
66
+ }
67
+ state.startX = touch.clientX;
68
+ state.startY = touch.clientY;
69
+ if (state.timeout)
70
+ clearTimeout(state.timeout);
71
+ state.timeout = setTimeout(() => {
72
+ // Haptic feedback
73
+ if ('vibrate' in navigator)
74
+ navigator.vibrate(50);
75
+ // Dispatch synthetic contextmenu event to trigger existing onContextMenu handlers
76
+ const syntheticEvent = new MouseEvent('contextmenu', {
77
+ bubbles: true,
78
+ cancelable: true,
79
+ clientX: touch.clientX,
80
+ clientY: touch.clientY,
81
+ });
82
+ element.dispatchEvent(syntheticEvent);
83
+ if (state)
84
+ state.timeout = null;
85
+ }, 500);
86
+ };
87
+ const handleTouchMove = (e) => {
88
+ const touchEvent = e;
89
+ const element = e.currentTarget;
90
+ const state = touchStateMap.get(element);
91
+ if (!state?.timeout)
92
+ return;
93
+ const touch = touchEvent.touches[0];
94
+ const dx = Math.abs(touch.clientX - state.startX);
95
+ const dy = Math.abs(touch.clientY - state.startY);
96
+ if (dx > 10 || dy > 10) {
97
+ clearTimeout(state.timeout);
98
+ state.timeout = null;
99
+ }
100
+ };
101
+ const handleTouchEnd = (e) => {
102
+ const element = e.currentTarget;
103
+ const state = touchStateMap.get(element);
104
+ if (state?.timeout) {
105
+ clearTimeout(state.timeout);
106
+ state.timeout = null;
107
+ }
108
+ };
109
+ // Attach listeners to all matching elements
110
+ elements.forEach(element => {
111
+ const el = element;
112
+ // Prevent iOS native callout
113
+ const style = el.style;
114
+ style.webkitTouchCallout = 'none';
115
+ style.webkitUserSelect = 'none';
116
+ el.addEventListener('touchstart', handleTouchStart, { passive: true });
117
+ el.addEventListener('touchmove', handleTouchMove, { passive: true });
118
+ el.addEventListener('touchend', handleTouchEnd);
119
+ el.addEventListener('touchcancel', handleTouchEnd);
120
+ });
121
+ return () => {
122
+ elements.forEach(element => {
123
+ const el = element;
124
+ const style = el.style;
125
+ style.webkitTouchCallout = '';
126
+ style.webkitUserSelect = '';
127
+ el.removeEventListener('touchstart', handleTouchStart);
128
+ el.removeEventListener('touchmove', handleTouchMove);
129
+ el.removeEventListener('touchend', handleTouchEnd);
130
+ el.removeEventListener('touchcancel', handleTouchEnd);
131
+ });
132
+ };
133
+ }, [target, isIOS]);
49
134
  // Update items when they change while menu is visible (for keepOpenOnClick behavior)
50
135
  useEffect(() => {
51
136
  if (!keepOpenOnClick)
@@ -16,6 +16,7 @@ export interface TMContextMenuProps {
16
16
  items: TMContextMenuItemProps[];
17
17
  trigger?: 'right' | 'left';
18
18
  children?: React.ReactNode;
19
+ target?: string;
19
20
  externalControl?: {
20
21
  visible: boolean;
21
22
  position: {
@@ -180,7 +180,7 @@ const TMSavedQuerySelector = React.memo(({ items, selectedId, allowShowSearch =
180
180
  overflow: 'auto'
181
181
  }, children: dataSource.slice(0, showAllRoot || searchText.length > 0 ? dataSource.length : initialSQDsMaxItems).filter(o => searchText.length <= 0 || (searchText.length > 0 && o.name?.toLocaleLowerCase().includes(searchText.toLocaleLowerCase())) || o.description?.toLocaleLowerCase().includes(searchText.toLocaleLowerCase())).map((sqd, index) => {
182
182
  const isCurrent = selectedItem?.id == sqd.id;
183
- return (_jsx(StyledSqdItem, { "$isMobile": isMobile, onClick: () => {
183
+ return (_jsx(StyledSqdItem, { id: `sqd-item-${sqd.id}`, "$isMobile": isMobile, onClick: () => {
184
184
  setSelectedItem(sqd);
185
185
  onItemClick?.(sqd);
186
186
  }, onContextMenu: (e) => {
@@ -222,10 +222,10 @@ const TMSavedQuerySelector = React.memo(({ items, selectedId, allowShowSearch =
222
222
  visibility: isCurrent ? 'visible' : 'hidden'
223
223
  }, children: _jsx(IconApply, { fontSize: 24, color: 'green' }) })] }) }, sqd.id));
224
224
  }) }), dataSource.length > initialSQDsMaxItems && searchText.length <= 0 &&
225
- _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end', padding: '10px', position: 'relative' }, children: _jsx(TMShowAllOrMaxItemsButton, { showAll: showAllRoot, dataSourceLength: dataSource.length, onClick: () => { setShowAllRoot(!showAllRoot); } }) }), contextMenuState.sqd && (_jsx(TMContextMenu, { items: getContextMenuItems(contextMenuState.sqd, manageDefault, isMobile, deleteSQDAsync, setDefaultSQDAsync, favManageSQDAsync, setInfoSQD), externalControl: {
225
+ _jsx("div", { style: { display: 'flex', justifyContent: 'flex-end', padding: '10px', position: 'relative' }, children: _jsx(TMShowAllOrMaxItemsButton, { showAll: showAllRoot, dataSourceLength: dataSource.length, onClick: () => { setShowAllRoot(!showAllRoot); } }) }), _jsx(TMContextMenu, { target: "[id^='sqd-item-']", items: contextMenuState.sqd ? getContextMenuItems(contextMenuState.sqd, manageDefault, isMobile, deleteSQDAsync, setDefaultSQDAsync, favManageSQDAsync, setInfoSQD) : [], externalControl: {
226
226
  visible: contextMenuState.visible,
227
227
  position: contextMenuState.position,
228
228
  onClose: () => setContextMenuState(prev => ({ ...prev, visible: false, sqd: null }))
229
- } })), _jsxs(StyledOffCanvasPanel, { ref: panelRef, "$isOpen": isMobile && infoSQD !== undefined, children: [_jsxs(StyledDivHorizontal, { style: { gap: 10, padding: '10px 8px', width: '100%', alignItems: 'center' }, children: [_jsx("p", { style: { fontSize: '1.1rem', fontWeight: 'bold' }, children: `${SDK_Localizator.SavedQuery} - ${SDKUI_Localizator.About}` }), _jsx(IconCloseOutline, { style: { marginLeft: 'auto', cursor: 'pointer' }, onClick: () => setInfoSQD(undefined) })] }), getTooltipBySqd(infoSQD)] })] }));
229
+ } }), _jsxs(StyledOffCanvasPanel, { ref: panelRef, "$isOpen": isMobile && infoSQD !== undefined, children: [_jsxs(StyledDivHorizontal, { style: { gap: 10, padding: '10px 8px', width: '100%', alignItems: 'center' }, children: [_jsx("p", { style: { fontSize: '1.1rem', fontWeight: 'bold' }, children: `${SDK_Localizator.SavedQuery} - ${SDKUI_Localizator.About}` }), _jsx(IconCloseOutline, { style: { marginLeft: 'auto', cursor: 'pointer' }, onClick: () => setInfoSQD(undefined) })] }), getTooltipBySqd(infoSQD)] })] }));
230
230
  });
231
231
  export default TMSavedQuerySelector;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.20.0-dev1.56",
3
+ "version": "6.20.0-dev1.57",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",