@topconsultnpm/sdkui-react 6.19.0-dev2.36 → 6.19.0-dev2.38

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 (36) hide show
  1. package/lib/components/base/TMCustomButton.js +1 -1
  2. package/lib/components/base/TMWaitPanel.js +8 -2
  3. package/lib/components/features/documents/TMDcmtPreview.js +17 -1
  4. package/lib/helper/SDKUI_Localizator.d.ts +1 -0
  5. package/lib/helper/SDKUI_Localizator.js +10 -0
  6. package/lib/helper/dcmtsHelper.d.ts +2 -2
  7. package/lib/helper/dcmtsHelper.js +14 -17
  8. package/package.json +2 -2
  9. package/lib/components/NewComponents/ContextMenu/TMContextMenu.d.ts +0 -4
  10. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +0 -187
  11. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +0 -11
  12. package/lib/components/NewComponents/ContextMenu/hooks.js +0 -48
  13. package/lib/components/NewComponents/ContextMenu/index.d.ts +0 -2
  14. package/lib/components/NewComponents/ContextMenu/index.js +0 -1
  15. package/lib/components/NewComponents/ContextMenu/styles.d.ts +0 -27
  16. package/lib/components/NewComponents/ContextMenu/styles.js +0 -308
  17. package/lib/components/NewComponents/ContextMenu/types.d.ts +0 -26
  18. package/lib/components/NewComponents/ContextMenu/types.js +0 -1
  19. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.d.ts +0 -4
  20. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +0 -370
  21. package/lib/components/NewComponents/FloatingMenuBar/index.d.ts +0 -2
  22. package/lib/components/NewComponents/FloatingMenuBar/index.js +0 -2
  23. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +0 -38
  24. package/lib/components/NewComponents/FloatingMenuBar/styles.js +0 -267
  25. package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +0 -30
  26. package/lib/components/NewComponents/FloatingMenuBar/types.js +0 -1
  27. package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
  28. package/lib/components/NewComponents/Notification/Notification.js +0 -60
  29. package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
  30. package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
  31. package/lib/components/NewComponents/Notification/index.d.ts +0 -2
  32. package/lib/components/NewComponents/Notification/index.js +0 -2
  33. package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
  34. package/lib/components/NewComponents/Notification/styles.js +0 -180
  35. package/lib/components/NewComponents/Notification/types.d.ts +0 -18
  36. package/lib/components/NewComponents/Notification/types.js +0 -1
@@ -20,7 +20,7 @@ const TMCustomButton = (props) => {
20
20
  const { appName: scriptUrl, arguments: args } = button;
21
21
  const iframeRef = useRef(null);
22
22
  const attributes = useMemo(() => processButtonAttributes(args, formData), [args, formData]);
23
- const selectedItemsProcessed = useMemo(() => processSelectedItems(selectedItems), [selectedItems]);
23
+ const selectedItemsProcessed = useMemo(() => processSelectedItems(args, formData, selectedItems), [args, formData, selectedItems]);
24
24
  const RunOnce = button.mode === "RunOnce";
25
25
  const [loading, setLoading] = useState(true);
26
26
  const [error, setError] = useState(false);
@@ -33,8 +33,14 @@ const StyledProgressText = styled.p ` font-weight: bold; color: #333; margin: 0;
33
33
  const StyledMessage = styled.p ` color: #666; font-size: 0.9em; margin-top: 10px; `;
34
34
  const StyledAbortButton = styled.button ` background: #ff4d4d; color: white; border: none; border-radius: 5px; padding: 10px 20px; font-size: 1em; cursor: pointer; margin-top: 20px; &:hover { background: #ff6666; } `;
35
35
  export const TMWaitPanel = (props) => {
36
- let progressValue1 = 100 * (((props.valuePrimary ?? 0) / (props.maxValuePrimary ?? 0)) || 0);
37
- let progressValue2 = 100 * (((props.valueSecondary ?? 0) / (props.maxValueSecondary ?? 0)) || 0);
36
+ const calculateProgress = (value = 0, maxValue = 0) => {
37
+ if (maxValue === 0)
38
+ return 0;
39
+ const progress = (value / maxValue) * 100;
40
+ return Number.isFinite(progress) ? progress : 0;
41
+ };
42
+ let progressValue1 = calculateProgress(props.valuePrimary, props.maxValuePrimary);
43
+ let progressValue2 = calculateProgress(props.valueSecondary, props.maxValueSecondary);
38
44
  return (_jsx(StyledWaitPanelOverlay, { children: _jsxs(StyledWaitPanel, { "$height": (props.showPrimary && props.showSecondary) ? '350px' : '250px', children: [_jsx(StyledTitle, { children: props.title }), props.showPrimary &&
39
45
  _jsxs("div", { style: { width: '100%', height: '100px' }, children: [_jsx(StyledProgressBarContainer, { children: _jsx(StyledProgressBar, { style: { width: `${progressValue1.toFixed(2)}%` } }) }), _jsxs(StyledProgressText, { children: [progressValue1.toFixed(2), "%"] }), _jsx(StyledMessage, { children: props.textPrimary })] }), props.showSecondary &&
40
46
  _jsxs("div", { style: { width: '100%', height: '100px' }, children: [_jsx(StyledProgressBarContainer, { children: _jsx(StyledProgressBar, { style: { width: `${progressValue2.toFixed(2)}%` } }) }), _jsxs(StyledProgressText, { children: [progressValue2.toFixed(2), "%"] }), _jsx(StyledMessage, { children: props.textSecondary })] }), props.isCancelable && _jsx(StyledAbortButton, { onClick: () => props.onAbortClick?.(props.abortController), children: SDKUI_Localizator.Abort })] }) }));
@@ -15,11 +15,18 @@ import { TMSaveFormButtonPrevious, TMSaveFormButtonNext } from '../../forms/TMSa
15
15
  import { StyledAnimatedComponentOpacity } from '../../base/Styled';
16
16
  import TMPanel from '../../base/TMPanel';
17
17
  import TMTooltip from '../../base/TMTooltip';
18
+ const ErrorContent = ({ error, isAbortError, onRetry }) => {
19
+ if (isAbortError) {
20
+ return (_jsx(StyledAnimatedComponentOpacity, { style: { width: '100%', height: '100%' }, children: _jsxs(StyledPanelStatusContainer, { children: [_jsx(IconCloseOutline, { fontSize: 92, color: TMColors.error }), _jsxs(StyledPreviewNotAvailable, { children: [_jsx("div", { children: error }), _jsx("div", { children: SDKUI_Localizator.PreviewNotAvailable })] }), _jsx(TMButton, { caption: SDKUI_Localizator.TryAgain, onClick: onRetry, showTooltip: false })] }) }));
21
+ }
22
+ return _jsx(TMNothingToShow, { icon: _jsx(IconCloseOutline, { fontSize: 92, color: TMColors.error }), text: error });
23
+ };
18
24
  const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev, onClosePanel, onNext, onPrev, allowMaximize = true, onMaximizePanel }) => {
19
25
  const [dcmtBlob, setDcmtBlob] = useState(undefined);
20
26
  const [showPreview, setShowPreview] = useState(false);
21
27
  const [isFromCache, setIsFromCache] = useState(false);
22
28
  const [error, setError] = useState('');
29
+ const [isAbortError, setIsAbortError] = useState(false);
23
30
  const { abortController, showWaitPanel, waitPanelTitle, showPrimary, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, getDcmtFileAsync, clearDcmtsFileCache, removeDcmtsFileCache, isDcmtFileInCache } = useDcmtOperations();
24
31
  const cacheKey = dcmtData ? `${dcmtData.tid}-${dcmtData.did}` : '00';
25
32
  const [hasLoadedDataOnce, setHasLoadedDataOnce] = useState(false);
@@ -29,6 +36,7 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
29
36
  setLastLoadedDid(undefined); // Reset
30
37
  setDcmtBlob(undefined);
31
38
  setError('');
39
+ setIsAbortError(false);
32
40
  setShowPreview(false);
33
41
  return;
34
42
  }
@@ -42,6 +50,7 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
42
50
  if (shouldFetch) {
43
51
  setDcmtBlob(undefined);
44
52
  setError('');
53
+ setIsAbortError(false);
45
54
  if ((extensionHandler(dcmtData.fileExt) !== FileExtensionHandler.NONE) && ((dcmtData.fileSize ?? 0) <= (SDKUI_Globals.userSettings.searchSettings.previewThreshold * 1024))) {
46
55
  loadDocumentWithCache();
47
56
  setShowPreview(true);
@@ -65,15 +74,19 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
65
74
  let dcmtFile = await getDcmtFileAsync({ TID: dcmtData?.tid, DID: dcmtData?.did, FILEEXT: dcmtData?.fileExt }, rfo, 'Anteprima', false);
66
75
  setDcmtBlob(dcmtFile?.file);
67
76
  setIsFromCache(!!dcmtFile?.isFromCache);
77
+ setError('');
78
+ setIsAbortError(false);
68
79
  }
69
80
  catch (ex) {
70
81
  const err = ex;
71
82
  if (err.name === 'CanceledError') {
72
83
  setError('Operazione annullata.');
84
+ setIsAbortError(true);
73
85
  ShowAlert({ message: err.message, mode: 'warning', duration: 3000, title: 'Abort' });
74
86
  }
75
87
  else {
76
88
  setError(getExceptionMessage(ex));
89
+ setIsAbortError(false);
77
90
  TMExceptionBoxManager.show({ exception: ex });
78
91
  }
79
92
  }
@@ -101,6 +114,9 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
101
114
  const reOpenDcmt = async () => {
102
115
  removeDcmtsFileCache(cacheKey);
103
116
  setIsFromCache(false);
117
+ setError('');
118
+ setIsAbortError(false);
119
+ setDcmtBlob(undefined);
104
120
  try {
105
121
  await loadDocumentWithCache();
106
122
  }
@@ -112,7 +128,7 @@ const TMDcmtPreview = ({ dcmtData, isResizingActive, isVisible, canNext, canPrev
112
128
  { icon: _jsx(IconCloseCircle, {}), text: SDKUI_Localizator.RemoveFromCache, onClick: () => { removeDcmtsFileCache(cacheKey); setIsFromCache(false); } },
113
129
  { icon: _jsx(IconClear, {}), text: SDKUI_Localizator.ClearCache, onClick: () => { clearDcmtsFileCache(); setIsFromCache(false); } },
114
130
  ] }, "btn13") }), _jsx(StyledHeaderIcon, { onClick: reOpenDcmt, "$color": TMColors.primaryColor, children: _jsx(TMTooltip, { content: SDKUI_Localizator.ReopenDocument, children: _jsx(IconRefresh, {}) }) })] }), children: error
115
- ? _jsx(TMNothingToShow, { icon: _jsx(IconCloseOutline, { fontSize: 92, color: TMColors.error }), text: error })
131
+ ? _jsx(ErrorContent, { error: error, isAbortError: isAbortError, onRetry: reOpenDcmt })
116
132
  : renderedPreview(dcmtData?.tid, dcmtData?.did, dcmtData?.fileExt, dcmtData?.fileSize, dcmtData?.fileCount, extensionHandler(dcmtData?.fileExt), showPreview, isResizingActive, () => { loadDocumentWithCache(); setShowPreview(true); }, dcmtBlob) }) }));
117
133
  };
118
134
  export default TMDcmtPreview;
@@ -505,6 +505,7 @@ export declare class SDKUI_Localizator {
505
505
  static get RemovingFromList(): string;
506
506
  static get RememberCredentials(): "Anmeldedaten merken" | "Remember credentials" | "Recordar credenciales" | "Se souvenir des identifiants" | "Lembrar credenciais" | "Ricorda credenziali";
507
507
  static get ReopenDocument(): string;
508
+ static get TryAgain(): string;
508
509
  static get ReplaceDocument(): "Dokument ersetzen" | "Replace Document" | "Reemplazar Documento" | "Remplacer le Document" | "Substituir Documento" | "Sostituisci Documento";
509
510
  static get Request(): string;
510
511
  static get RequestTo(): string;
@@ -4978,6 +4978,16 @@ export class SDKUI_Localizator {
4978
4978
  default: return "Riapri documento";
4979
4979
  }
4980
4980
  }
4981
+ static get TryAgain() {
4982
+ switch (this._cultureID) {
4983
+ case CultureIDs.De_DE: return "Erneut versuchen";
4984
+ case CultureIDs.En_US: return "Try Again";
4985
+ case CultureIDs.Es_ES: return "Intentar de nuevo";
4986
+ case CultureIDs.Fr_FR: return "Réessayer";
4987
+ case CultureIDs.Pt_PT: return "Tentar novamente";
4988
+ default: return "Riprova";
4989
+ }
4990
+ }
4981
4991
  static get ReplaceDocument() {
4982
4992
  switch (this._cultureID) {
4983
4993
  case CultureIDs.De_DE: return "Dokument ersetzen";
@@ -4,5 +4,5 @@ export declare const hasDetailRelations: (mTID: number | undefined) => Promise<b
4
4
  /** Check if dcmtType (mTID) has configured Master or Many-to-Many relations */
5
5
  export declare const hasMasterRelations: (mTID: number | undefined) => Promise<boolean>;
6
6
  export declare const isXMLFileExt: (fileExt: string | undefined) => boolean;
7
- export declare const processButtonAttributes: (args: string | undefined, formData: MetadataValueDescriptorEx[] | undefined) => string[] | undefined;
8
- export declare const processSelectedItems: (selectedItems: Array<any> | undefined) => any[] | undefined;
7
+ export declare const processButtonAttributes: (args: string | undefined, formData: MetadataValueDescriptorEx[] | undefined) => (string | null)[] | undefined;
8
+ export declare const processSelectedItems: (args: string | undefined, formData: MetadataValueDescriptorEx[] | undefined, selectedItems: Array<any> | undefined) => any[][];
@@ -25,29 +25,26 @@ export const isXMLFileExt = (fileExt) => {
25
25
  };
26
26
  /*utility functions for TMCustomButton*/
27
27
  export const processButtonAttributes = (args, formData) => args && formData ? formDataMap(formData, args) : undefined;
28
- export const processSelectedItems = (selectedItems) => selectedItems?.map(item => {
29
- const tid = item.TID;
30
- if (!tid)
31
- return item;
32
- const prefix = `${tid}_`;
33
- const prefixLength = prefix.length;
34
- return Object.keys(item).reduce((acc, key) => {
35
- acc[key.startsWith(prefix) ? key.substring(prefixLength) : key] = item[key];
36
- return acc;
37
- }, {});
38
- });
39
- const formDataMap = (data, args) => {
28
+ export const processSelectedItems = (args, formData, selectedItems) => {
29
+ if (!args || !selectedItems)
30
+ return [];
31
+ const MidList = formData ? formDataMap(formData, args, true) : [];
32
+ return selectedItems.map(item =>
33
+ //salvo il did come primo elemento dell'array
34
+ [item["DID"], ...MidList.map(key => key && item[key])]);
35
+ };
36
+ const formDataMap = (data, args, returnMid = false) => {
40
37
  const tokens = args.match(/\{@?[^}]+\}/g) || [];
41
38
  return tokens.map(token => {
42
39
  if (token.startsWith('{@')) {
43
40
  // Campo dinamico: {@campo} -> cerca in formData
44
- const fieldName = token.slice(2, -1); // Rimuove {@...}
41
+ const fieldName = token.slice(2, -1);
45
42
  const md = data.find(md => md.md?.name === fieldName);
46
- return md?.value;
43
+ return returnMid ? (md ? `${md.tid}_${md.mid}` : null) : (md?.value ?? null);
47
44
  }
48
45
  else {
49
- // Campo statico: {valore} -> ritorna il valore direttamente
50
- return token.slice(1, -1); // Rimuove {...}
46
+ // Campo statico: {valore} -> ritorna il valore o null
47
+ return returnMid ? null : token.slice(1, -1);
51
48
  }
52
- }).filter((value) => value !== undefined && value !== null);
49
+ });
53
50
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react",
3
- "version": "6.19.0-dev2.36",
3
+ "version": "6.19.0-dev2.38",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -39,7 +39,7 @@
39
39
  "lib"
40
40
  ],
41
41
  "dependencies": {
42
- "@topconsultnpm/sdk-ts": "6.19.0-dev2.4",
42
+ "@topconsultnpm/sdk-ts": "6.19.0-dev2.5",
43
43
  "buffer": "^6.0.3",
44
44
  "devextreme": "25.1.7",
45
45
  "devextreme-react": "25.1.7",
@@ -1,4 +0,0 @@
1
- import React from 'react';
2
- import type { TMContextMenuProps } from './types';
3
- declare const TMContextMenu: React.FC<TMContextMenuProps>;
4
- export default TMContextMenu;
@@ -1,187 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState, useRef, useEffect } from 'react';
3
- import * as S from './styles';
4
- import { useIsMobile, useMenuPosition } from './hooks';
5
- const TMContextMenu = ({ items, trigger = 'right', children }) => {
6
- const [menuState, setMenuState] = useState({
7
- visible: false,
8
- position: { x: 0, y: 0 },
9
- submenuStack: [items],
10
- parentNames: [],
11
- });
12
- const [hoveredSubmenus, setHoveredSubmenus] = useState([]);
13
- const isMobile = useIsMobile();
14
- const menuRef = useRef(null);
15
- const triggerRef = useRef(null);
16
- const submenuTimeoutRef = useRef(null);
17
- const { openLeft, openUp } = useMenuPosition(menuRef, menuState.position);
18
- const handleClose = () => {
19
- setMenuState(prev => ({
20
- ...prev,
21
- visible: false,
22
- submenuStack: [items],
23
- parentNames: [],
24
- }));
25
- setHoveredSubmenus([]);
26
- };
27
- useEffect(() => {
28
- if (!menuState.visible)
29
- return;
30
- const handleClickOutside = (event) => {
31
- const target = event.target;
32
- // Check if click is inside main menu
33
- if (menuRef.current?.contains(target)) {
34
- return;
35
- }
36
- // Check if click is inside any submenu
37
- const submenus = document.querySelectorAll('[data-submenu="true"]');
38
- for (const submenu of Array.from(submenus)) {
39
- if (submenu.contains(target)) {
40
- return;
41
- }
42
- }
43
- // Click is outside all menus, close them
44
- handleClose();
45
- };
46
- document.addEventListener('mousedown', handleClickOutside);
47
- document.addEventListener('touchstart', handleClickOutside);
48
- return () => {
49
- document.removeEventListener('mousedown', handleClickOutside);
50
- document.removeEventListener('touchstart', handleClickOutside);
51
- };
52
- }, [menuState.visible]);
53
- const handleContextMenu = (e) => {
54
- if (trigger === 'right') {
55
- e.preventDefault();
56
- setMenuState({
57
- visible: true,
58
- position: { x: e.clientX, y: e.clientY },
59
- submenuStack: [items],
60
- parentNames: [],
61
- });
62
- }
63
- };
64
- const handleClick = (e) => {
65
- if (trigger === 'left') {
66
- e.preventDefault();
67
- setMenuState({
68
- visible: true,
69
- position: { x: e.clientX, y: e.clientY },
70
- submenuStack: [items],
71
- parentNames: [],
72
- });
73
- }
74
- };
75
- const handleItemClick = (item) => {
76
- if (item.disabled)
77
- return;
78
- // Always execute onClick if present
79
- if (item.onClick) {
80
- item.onClick();
81
- }
82
- if (item.submenu && item.submenu.length > 0) {
83
- if (isMobile) {
84
- // Mobile: Push submenu to stack
85
- setMenuState(prev => ({
86
- ...prev,
87
- submenuStack: [...prev.submenuStack, item.submenu],
88
- parentNames: [...prev.parentNames, item.name],
89
- }));
90
- }
91
- // Desktop: Submenus are handled by hover, don't close menu
92
- }
93
- else {
94
- // No submenu: close menu after executing action
95
- handleClose();
96
- }
97
- };
98
- const handleBack = () => {
99
- setMenuState(prev => ({
100
- ...prev,
101
- submenuStack: prev.submenuStack.slice(0, -1),
102
- parentNames: prev.parentNames.slice(0, -1),
103
- }));
104
- };
105
- const handleMouseEnter = (item, event, depth = 0) => {
106
- if (isMobile || !item.submenu || item.submenu.length === 0)
107
- return;
108
- if (submenuTimeoutRef.current) {
109
- clearTimeout(submenuTimeoutRef.current);
110
- submenuTimeoutRef.current = null;
111
- }
112
- const rect = event.currentTarget.getBoundingClientRect();
113
- // Calculate if submenu should open upward based on available space
114
- // Estimate submenu height: ~35px per item (accounting for smaller padding) + container padding
115
- const estimatedSubmenuHeight = (item.submenu.length * 35) + 8;
116
- const spaceBelow = window.innerHeight - rect.top;
117
- const spaceAbove = rect.bottom;
118
- // Open upward only if there's not enough space below AND there's more space above
119
- const shouldOpenUp = spaceBelow < estimatedSubmenuHeight && spaceAbove > spaceBelow;
120
- // Remove all submenus at this depth and deeper
121
- setHoveredSubmenus(prev => {
122
- const filtered = prev.filter(sub => sub.depth < depth);
123
- if (!item.submenu)
124
- return filtered;
125
- return [
126
- ...filtered,
127
- {
128
- items: item.submenu,
129
- parentRect: rect,
130
- depth: depth,
131
- openUp: shouldOpenUp,
132
- }
133
- ];
134
- });
135
- };
136
- const handleMouseLeave = (depth = 0) => {
137
- if (isMobile)
138
- return;
139
- if (submenuTimeoutRef.current) {
140
- clearTimeout(submenuTimeoutRef.current);
141
- }
142
- const targetDepth = depth;
143
- submenuTimeoutRef.current = setTimeout(() => {
144
- setHoveredSubmenus(prev => prev.filter(sub => sub.depth < targetDepth));
145
- }, 300);
146
- };
147
- const handleSubmenuMouseEnter = () => {
148
- if (submenuTimeoutRef.current) {
149
- clearTimeout(submenuTimeoutRef.current);
150
- submenuTimeoutRef.current = null;
151
- }
152
- };
153
- useEffect(() => {
154
- return () => {
155
- if (submenuTimeoutRef.current) {
156
- clearTimeout(submenuTimeoutRef.current);
157
- }
158
- };
159
- }, []);
160
- const renderMenuItems = (menuItems, depth = 0) => {
161
- return menuItems
162
- .filter(item => item.visible !== false)
163
- .map((item, idx) => {
164
- const itemKey = `${item.name}-${idx}`.replaceAll(/\s+/g, '-');
165
- const handleClick = (e) => {
166
- if (item.disabled)
167
- return;
168
- e.stopPropagation();
169
- handleItemClick(item);
170
- };
171
- const handleRightIconClick = (e) => {
172
- e.stopPropagation();
173
- // if (item.disabled) return;
174
- item.onRightIconClick?.();
175
- };
176
- return (_jsxs(S.MenuItem, { "$disabled": item.disabled, "$hasSubmenu": !!item.submenu && item.submenu.length > 0, "$beginGroup": item.beginGroup, onMouseDown: handleClick, onMouseEnter: (e) => !isMobile && handleMouseEnter(item, e, depth + 1), onMouseLeave: () => !isMobile && handleMouseLeave(depth + 1), children: [_jsxs(S.MenuItemContent, { children: [item.icon && _jsx(S.IconWrapper, { children: item.icon }), _jsx(S.MenuItemName, { children: item.name })] }), item.rightIcon && item.onRightIconClick && (_jsx(S.RightIconButton, { onClick: handleRightIconClick, onMouseDown: (e) => e.stopPropagation(), "aria-label": `Action for ${item.name}`, children: item.rightIcon })), item.submenu && item.submenu.length > 0 && (_jsx(S.SubmenuIndicator, { "$isMobile": isMobile, children: isMobile ? '›' : '▸' }))] }, itemKey));
177
- });
178
- };
179
- const currentMenu = menuState.submenuStack.at(-1) || items;
180
- const currentParentName = menuState.parentNames.at(-1) || '';
181
- return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
182
- if (e.key === 'Enter' || e.key === ' ') {
183
- handleClick(e);
184
- }
185
- }, role: "button", tabIndex: 0, style: { display: 'inline-block' }, children: children }), menuState.visible && (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { onClick: handleClose }), _jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children: "\u2190" }), _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}`)))] }))] }));
186
- };
187
- export default TMContextMenu;
@@ -1,11 +0,0 @@
1
- export declare const useIsMobile: () => boolean;
2
- export declare const useClickOutside: (callback: () => void) => import("react").RefObject<HTMLDivElement>;
3
- interface Position {
4
- x: number;
5
- y: number;
6
- }
7
- export declare const useMenuPosition: (menuRef: React.RefObject<HTMLDivElement | null>, position: Position) => {
8
- openLeft: boolean;
9
- openUp: boolean;
10
- };
11
- export {};
@@ -1,48 +0,0 @@
1
- import { useState, useEffect, useRef } from 'react';
2
- export const useIsMobile = () => {
3
- const [isMobile, setIsMobile] = useState(false);
4
- useEffect(() => {
5
- const checkMobile = () => {
6
- const mobile = globalThis.innerWidth <= 768 || 'ontouchstart' in globalThis;
7
- setIsMobile(mobile);
8
- };
9
- checkMobile();
10
- window.addEventListener('resize', checkMobile);
11
- return () => window.removeEventListener('resize', checkMobile);
12
- }, []);
13
- return isMobile;
14
- };
15
- export const useClickOutside = (callback) => {
16
- const ref = useRef(null);
17
- useEffect(() => {
18
- const handleClick = (event) => {
19
- if (ref.current && !ref.current.contains(event.target)) {
20
- callback();
21
- }
22
- };
23
- document.addEventListener('mousedown', handleClick);
24
- document.addEventListener('touchstart', handleClick);
25
- return () => {
26
- document.removeEventListener('mousedown', handleClick);
27
- document.removeEventListener('touchstart', handleClick);
28
- };
29
- }, [callback]);
30
- return ref;
31
- };
32
- export const useMenuPosition = (menuRef, position) => {
33
- const [adjustedPosition, setAdjustedPosition] = useState({ openLeft: false, openUp: false });
34
- useEffect(() => {
35
- if (!menuRef.current)
36
- return;
37
- const menuRect = menuRef.current.getBoundingClientRect();
38
- const viewportWidth = window.innerWidth;
39
- const viewportHeight = window.innerHeight;
40
- const spaceRight = viewportWidth - position.x;
41
- const spaceBottom = viewportHeight - position.y;
42
- setAdjustedPosition({
43
- openLeft: spaceRight < menuRect.width + 20,
44
- openUp: spaceBottom < menuRect.height + 20,
45
- });
46
- }, [position, menuRef]);
47
- return adjustedPosition;
48
- };
@@ -1,2 +0,0 @@
1
- export { default as ContextMenu } from './TMContextMenu';
2
- export type { TMContextMenuItemProps, TMContextMenuProps } from './types';
@@ -1 +0,0 @@
1
- export { default as ContextMenu } from './TMContextMenu';
@@ -1,27 +0,0 @@
1
- export declare const MenuContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
2
- $x: number;
3
- $y: number;
4
- $openLeft: boolean;
5
- $openUp: boolean;
6
- }>> & string;
7
- 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
- $disabled?: boolean;
9
- $hasSubmenu?: boolean;
10
- $beginGroup?: boolean;
11
- }>> & string;
12
- 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
- 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
- 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>, never>> & string;
16
- 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
- $isMobile?: boolean;
18
- }>> & string;
19
- 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
- $parentRect: DOMRect;
21
- $openUp?: boolean;
22
- }>> & string;
23
- 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
- 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
- export declare const HeaderTitle: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, never>> & string;
26
- export declare const MenuDivider: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
27
- export declare const Overlay: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;