@topconsultnpm/sdkui-react 6.20.0-dev1.67 → 6.20.0-dev1.68

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.
@@ -59,6 +59,8 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
59
59
  const touchEvent = e;
60
60
  const element = e.currentTarget;
61
61
  const touch = touchEvent.touches[0];
62
+ // Prevent text selection during long press
63
+ e.preventDefault();
62
64
  let state = touchStateMap.get(element);
63
65
  if (!state) {
64
66
  state = { timeout: null, startX: 0, startY: 0, longPressTriggered: false };
@@ -75,16 +77,27 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
75
77
  // Haptic feedback
76
78
  if ('vibrate' in navigator)
77
79
  navigator.vibrate(50);
78
- const syntheticEvent = new MouseEvent('contextmenu', {
80
+ // First, trigger a click to select/focus the item
81
+ const clickEvent = new MouseEvent('click', {
79
82
  bubbles: true,
80
83
  cancelable: true,
81
84
  clientX: touch.clientX,
82
85
  clientY: touch.clientY,
83
86
  });
84
- element.dispatchEvent(syntheticEvent);
87
+ element.dispatchEvent(clickEvent);
88
+ // Small delay to ensure click is processed before opening context menu
89
+ setTimeout(() => {
90
+ const syntheticEvent = new MouseEvent('contextmenu', {
91
+ bubbles: true,
92
+ cancelable: true,
93
+ clientX: touch.clientX,
94
+ clientY: touch.clientY,
95
+ });
96
+ element.dispatchEvent(syntheticEvent);
97
+ }, 10);
85
98
  if (state)
86
99
  state.timeout = null;
87
- }, 500);
100
+ }, 400);
88
101
  };
89
102
  const handleTouchMove = (e) => {
90
103
  const touchEvent = e;
@@ -122,14 +135,16 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
122
135
  // Attach listeners to all matching elements
123
136
  elements.forEach(element => {
124
137
  const el = element;
125
- // Prevent iOS native callout
138
+ // Prevent iOS native callout and text selection
126
139
  const style = el.style;
127
140
  style.webkitTouchCallout = 'none';
128
141
  style.webkitUserSelect = 'none';
129
- el.addEventListener('touchstart', handleTouchStart, { passive: true });
142
+ style.userSelect = 'none';
143
+ el.addEventListener('touchstart', handleTouchStart, { passive: false });
130
144
  el.addEventListener('touchmove', handleTouchMove, { passive: true });
131
145
  el.addEventListener('touchend', handleTouchEnd);
132
146
  el.addEventListener('touchcancel', handleTouchEnd);
147
+ el.addEventListener('contextmenu', (e) => e.preventDefault(), { capture: true });
133
148
  el.addEventListener('click', handleClick, { capture: true });
134
149
  });
135
150
  return () => {
@@ -138,6 +153,7 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
138
153
  const style = el.style;
139
154
  style.webkitTouchCallout = '';
140
155
  style.webkitUserSelect = '';
156
+ style.userSelect = '';
141
157
  el.removeEventListener('touchstart', handleTouchStart);
142
158
  el.removeEventListener('touchmove', handleTouchMove);
143
159
  el.removeEventListener('touchend', handleTouchEnd);
@@ -268,7 +284,7 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
268
284
  parentNames: [],
269
285
  });
270
286
  }
271
- }, 500);
287
+ }, 400);
272
288
  };
273
289
  const handleTouchMove = (e) => {
274
290
  if (!isIOS || trigger !== 'right' || !touchStartPos.current)
@@ -411,6 +427,7 @@ const TMContextMenu = ({ items, trigger = 'right', children, target, externalCon
411
427
  display: 'inline-block',
412
428
  WebkitTouchCallout: isIOS ? 'none' : undefined,
413
429
  WebkitUserSelect: isIOS ? 'none' : undefined,
430
+ userSelect: isIOS ? 'none' : undefined,
414
431
  }, children: children })), menuState.visible && createPortal(_jsxs(_Fragment, { children: [_jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, "$isPositioned": isCalculated, "$externalControl": !!externalControl, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children: _jsx(IconArrowLeft, {}) }), _jsx(S.HeaderTitle, { children: currentParentName })] })), renderMenuItems(currentMenu, 0)] }), !isMobile && hoveredSubmenus.map((submenu, idx) => (_jsx(S.Submenu, { "$parentRect": submenu.parentRect, "$openUp": submenu.openUp, "data-submenu": "true", onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: () => handleMouseLeave(submenu.depth), children: renderMenuItems(submenu.items, submenu.depth) }, `submenu-${submenu.depth}-${idx}`)))] }), document.body)] }));
415
432
  };
416
433
  export default TMContextMenu;
@@ -4,7 +4,8 @@ import { ContextMenu } from '../ContextMenu';
4
4
  import ShowAlert from '../../base/TMAlert';
5
5
  import TMTooltip from '../../base/TMTooltip';
6
6
  import * as S from './styles';
7
- import { IconAdd, IconApply, IconMenuVertical, IconPin, IconSeparator, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
7
+ import { IconAdd, IconCloseOutline, IconMenuVertical, IconPin, IconSave, IconSeparator, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
8
+ import { ButtonNames, TMMessageBoxManager } from '../../base/TMPopUp';
8
9
  const Separator = (props) => (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M12 2v20", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round" }) }));
9
10
  const IconDraggableDots = (props) => (_jsx("svg", { fontSize: 18, viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M9 3a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm10-18a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0z" }) }));
10
11
  const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 100, }) => {
@@ -603,6 +604,45 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
603
604
  const snapshotIds = stateSnapshot.current.items.map(i => i.id).join(',');
604
605
  return currentIds !== snapshotIds;
605
606
  };
607
+ const handleClose = () => {
608
+ // If all items removed, exit without asking and restore last items
609
+ if (state.items.length === 0 && stateSnapshot.current) {
610
+ setState(s => ({
611
+ ...s,
612
+ items: [...stateSnapshot.current.items],
613
+ orientation: stateSnapshot.current.orientation,
614
+ position: { ...stateSnapshot.current.position },
615
+ isConfigMode: false,
616
+ }));
617
+ stateSnapshot.current = null;
618
+ return;
619
+ }
620
+ // If no changes, simply exit config mode
621
+ if (!hasChanges()) {
622
+ stateSnapshot.current = null;
623
+ const cleanedItems = removeTrailingSeparators(state.items);
624
+ setState(s => ({ ...s, isConfigMode: false, items: cleanedItems }));
625
+ return;
626
+ }
627
+ // If there are changes, ask for confirmation
628
+ TMMessageBoxManager.show({
629
+ message: 'Perderai le tue modifiche, sei sicuro?',
630
+ buttons: [ButtonNames.YES, ButtonNames.NO],
631
+ onButtonClick: (buttonName) => {
632
+ if (buttonName === ButtonNames.YES && stateSnapshot.current) {
633
+ // Restore snapshot and exit config mode
634
+ setState(s => ({
635
+ ...s,
636
+ items: [...stateSnapshot.current.items],
637
+ orientation: stateSnapshot.current.orientation,
638
+ position: { ...stateSnapshot.current.position },
639
+ isConfigMode: false,
640
+ }));
641
+ stateSnapshot.current = null;
642
+ }
643
+ },
644
+ });
645
+ };
606
646
  const toggleOrientation = () => {
607
647
  const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
608
648
  if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
@@ -740,6 +780,8 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained
740
780
  currentOnClick();
741
781
  }
742
782
  }, disabled: state.isConfigMode ? isDisabled && !state.isConfigMode : isDisabled, children: item.icon }) }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
743
- }), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: true, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0 ? 'Devi aggiungere almeno un item' : SDKUI_Localizator.ApplyAndClose, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.ApplyButton, { onClick: toggleConfigMode, disabled: state.items.length === 0, children: _jsx(IconApply, { fontSize: 20 }) }) })] })] }))] })] }));
783
+ }), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: true, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0
784
+ ? 'Devi aggiungere almeno un item'
785
+ : SDKUI_Localizator.Save, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.ApplyButton, { onClick: toggleConfigMode, disabled: state.items.length === 0 || !hasChanges(), children: _jsx(IconSave, { fontSize: 20 }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Close, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.CloseButton, { onClick: handleClose, children: _jsx(IconCloseOutline, { fontSize: 20 }) }) })] })] }))] })] }));
744
786
  };
745
787
  export default TMFloatingMenuBar;
@@ -33,6 +33,7 @@ export declare const MenuButton: import("styled-components/dist/types").IStyledC
33
33
  }>> & string;
34
34
  export declare const ConfigButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
35
35
  export declare const ApplyButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
36
+ export declare const CloseButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, never>> & string;
36
37
  export declare const ContextMenuButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<Omit<import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "$isActive"> & {
37
38
  $isActive?: boolean;
38
39
  }, "ref"> & {
@@ -74,7 +74,7 @@ export const GripHandle = styled.div `
74
74
  display: flex;
75
75
  align-items: center;
76
76
  justify-content: center;
77
- padding: ${props => props.$orientation === 'horizontal' ? '10px 6px' : '6px 10px'};
77
+ padding: ${props => props.$orientation === 'horizontal' ? '6px 4px' : '4px 6px'};
78
78
  cursor: grab;
79
79
  color: rgba(255, 255, 255, 0.7);
80
80
  transition: all 0.2s ease;
@@ -98,7 +98,7 @@ export const GripHandle = styled.div `
98
98
  export const Separator = styled.div `
99
99
  background: rgba(255, 255, 255, 0.25);
100
100
  width: ${props => props.$orientation === 'horizontal' ? '1px' : '100%'};
101
- height: ${props => props.$orientation === 'horizontal' ? '24px' : '1px'};
101
+ height: ${props => props.$orientation === 'horizontal' ? '20px' : '1px'};
102
102
  margin: ${props => props.$orientation === 'horizontal' ? '0 4px' : '4px 0'};
103
103
  flex-shrink: 0;
104
104
  `;
@@ -111,8 +111,8 @@ export const ItemSeparator = styled.div `
111
111
  display: flex;
112
112
  align-items: center;
113
113
  justify-content: center;
114
- width: 34px;
115
- height: 34px;
114
+ width: 30px;
115
+ height: 30px;
116
116
  background: transparent;
117
117
  border-radius: 8px;
118
118
  cursor: grab;
@@ -122,7 +122,7 @@ export const ItemSeparator = styled.div `
122
122
  content: '';
123
123
  background: rgba(255, 255, 255, 0.25);
124
124
  width: ${props.$orientation === 'horizontal' ? '2px' : '100%'};
125
- height: ${props.$orientation === 'horizontal' ? '20px' : '2px'};
125
+ height: ${props.$orientation === 'horizontal' ? '18px' : '2px'};
126
126
  border-radius: 1px;
127
127
  }
128
128
 
@@ -137,7 +137,7 @@ export const ItemSeparator = styled.div `
137
137
  /* Normal mode: simple line with tight spacing */
138
138
  background: rgba(255, 255, 255, 0.25);
139
139
  width: ${props.$orientation === 'horizontal' ? '1px' : '100%'};
140
- height: ${props.$orientation === 'horizontal' ? '24px' : '1px'};
140
+ height: ${props.$orientation === 'horizontal' ? '20px' : '1px'};
141
141
  margin: ${props.$orientation === 'horizontal' ? '0 2px' : '2px 0'};
142
142
  `}
143
143
  `;
@@ -145,8 +145,8 @@ export const MenuButton = styled.button `
145
145
  display: flex;
146
146
  align-items: center;
147
147
  justify-content: center;
148
- width: 34px;
149
- height: 34px;
148
+ width: 30px;
149
+ height: 30px;
150
150
  background: transparent;
151
151
  border: none;
152
152
  border-radius: 8px;
@@ -243,6 +243,40 @@ export const ApplyButton = styled.button `
243
243
  height: 20px;
244
244
  }
245
245
  `;
246
+ export const CloseButton = styled.button `
247
+ display: flex;
248
+ align-items: center;
249
+ justify-content: center;
250
+ width: 24px;
251
+ height: 24px;
252
+ background: transparent;
253
+ border: none;
254
+ border-radius: 4px;
255
+ color: rgba(239, 68, 68, 1);
256
+ font-size: 10px;
257
+ cursor: pointer;
258
+ transition: all 0.2s ease;
259
+ padding: 3px;
260
+
261
+ &:hover:not(:disabled) {
262
+ background: rgba(255, 255, 255, 0.1);
263
+ color: rgba(239, 68, 68, 1);
264
+ }
265
+
266
+ &:active:not(:disabled) {
267
+ transform: scale(0.9);
268
+ }
269
+
270
+ &:disabled {
271
+ opacity: 0.3;
272
+ cursor: default;
273
+ }
274
+
275
+ svg {
276
+ width: 20px;
277
+ height: 20px;
278
+ }
279
+ `;
246
280
  export const ContextMenuButton = styled(MenuButton) `
247
281
  svg {
248
282
  transform: translateY(0);
@@ -252,13 +286,13 @@ export const AddButton = styled.button `
252
286
  display: flex;
253
287
  align-items: center;
254
288
  justify-content: center;
255
- width: 28px;
256
- height: 28px;
289
+ width: 26px;
290
+ height: 26px;
257
291
  background: rgba(255, 255, 255, 0.15);
258
292
  border: 1px dashed rgba(255, 255, 255, 0.4);
259
293
  border-radius: 8px;
260
294
  color: white;
261
- font-size: 20px;
295
+ font-size: 18px;
262
296
  font-weight: bold;
263
297
  line-height: 0;
264
298
  cursor: pointer;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.20.0-dev1.67",
3
+ "version": "6.20.0-dev1.68",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",