@harborclient/sdk 1.0.19 → 1.0.21
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/dist/components/MethodSelect/index.js +1 -1
- package/dist/components/SegmentedTabs/SegmentedTabPanel.d.ts.map +1 -1
- package/dist/components/SegmentedTabs/SegmentedTabPanel.js +23 -2
- package/dist/components/SegmentedTabs/index.d.ts.map +1 -1
- package/dist/components/SegmentedTabs/index.js +71 -4
- package/dist/components/TabCloseButton/index.d.ts +7 -1
- package/dist/components/TabCloseButton/index.d.ts.map +1 -1
- package/dist/components/TabCloseButton/index.js +3 -2
- package/dist/components/classes.d.ts.map +1 -1
- package/dist/components/classes.js +3 -2
- package/package.json +3 -1
|
@@ -6,5 +6,5 @@ const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'];
|
|
|
6
6
|
* Dropdown for selecting an HTTP request method.
|
|
7
7
|
*/
|
|
8
8
|
export function MethodSelect({ value, onChange }) {
|
|
9
|
-
return (_jsx(Select, { variant: "plain", className: `hc-method-select w-[100px] shrink-0 cursor-pointer appearance-none border-none bg-transparent px-2 py-1.5 text-[14px] leading-none font-normal focus:outline-
|
|
9
|
+
return (_jsx(Select, { variant: "plain", className: `hc-method-select w-[100px] shrink-0 cursor-pointer appearance-none border-none bg-transparent px-2 py-1.5 text-[14px] leading-none font-normal focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent app-no-drag ${methodColorClass(value)}`, value: value, "aria-label": "HTTP method", onChange: (e) => onChange(e.target.value), children: METHODS.map((method) => (_jsx("option", { value: method, className: methodColorClass(method), children: method }, method))) }));
|
|
10
10
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedTabPanel.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/SegmentedTabPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"SegmentedTabPanel.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/SegmentedTabPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAC;AAG3D,UAAU,KAAK,CAAC,CAAC,SAAS,MAAM;IAC9B;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC;IAET;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,EAAE,EAClD,KAAK,EACL,QAAQ,EACR,SAAS,EACV,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CA4C/B"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
|
|
2
|
-
import { useContext } from '@harborclient/sdk/react';
|
|
2
|
+
import { useCallback, useContext } from '@harborclient/sdk/react';
|
|
3
3
|
import { SegmentedTabsContext } from './SegmentedTabsContext.js';
|
|
4
4
|
/**
|
|
5
5
|
* Renders a WAI-ARIA tab panel linked to the matching tab in the parent group.
|
|
@@ -9,7 +9,28 @@ export function SegmentedTabPanel({ value, children, className }) {
|
|
|
9
9
|
if (!context) {
|
|
10
10
|
throw new Error('SegmentedTabPanel must be used within SegmentedTabsGroup');
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Moves focus back to the owning tab when ArrowUp is pressed inside the panel,
|
|
14
|
+
* except inside CodeMirror editors where Up should edit text.
|
|
15
|
+
*
|
|
16
|
+
* @param event - Keyboard event from the tab panel container.
|
|
17
|
+
*/
|
|
18
|
+
const handleKeyDown = useCallback((event) => {
|
|
19
|
+
if (event.key !== 'ArrowUp' || event.defaultPrevented)
|
|
20
|
+
return;
|
|
21
|
+
const panel = event.currentTarget;
|
|
22
|
+
if (!panel.contains(document.activeElement))
|
|
23
|
+
return;
|
|
24
|
+
const active = document.activeElement;
|
|
25
|
+
if (active instanceof HTMLElement && active.closest('.cm-editor'))
|
|
26
|
+
return;
|
|
27
|
+
const tab = document.getElementById(context.getTabId(value));
|
|
28
|
+
if (tab instanceof HTMLElement) {
|
|
29
|
+
event.preventDefault();
|
|
30
|
+
tab.focus();
|
|
31
|
+
}
|
|
32
|
+
}, [context, value]);
|
|
12
33
|
if (context.value !== value)
|
|
13
34
|
return null;
|
|
14
|
-
return (_jsx("div", { role: "tabpanel", id: context.getPanelId(value), "aria-labelledby": context.getTabId(value), className: className, children: children }));
|
|
35
|
+
return (_jsx("div", { role: "tabpanel", id: context.getPanelId(value), "aria-labelledby": context.getTabId(value), className: className, onKeyDown: handleKeyDown, children: children }));
|
|
15
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;AAMhD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAkD1C,UAAU,KAAK,CAAC,CAAC,SAAS,MAAM;IAC9B;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAEnB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,CAAC;IAEV;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,EAAE,CAAC;IAEvB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,CAAC,EAAE,CAAC;IAE9B;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,gBAAgB,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAE3D;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAEhC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,EAC9C,IAAI,EACJ,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,YAAY,EACtB,QAAe,EACf,gBAAgB,EAAE,oBAAoB,EACtC,uBAAuB,EACvB,wBAAwB,EACxB,SAAiB,EACjB,SAAS,EACT,OAAgB,EAChB,SAAS,EAAE,aAAa,EACzB,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAoPxB"}
|
|
@@ -1,11 +1,52 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
|
|
2
2
|
import { useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from '@harborclient/sdk/react';
|
|
3
|
+
import { getFocusableElements } from '../useDialogFocus.js';
|
|
3
4
|
import { resolveTabListKeyAction } from '../utils.js';
|
|
4
5
|
import { segment, segmentGroup } from '../classes.js';
|
|
5
6
|
import { SegmentedTabsContext } from './SegmentedTabsContext.js';
|
|
6
7
|
import { SegmentedTabsVisibilityMenu } from './SegmentedTabsVisibilityMenu.js';
|
|
7
8
|
export { SegmentedTabsGroup } from './SegmentedTabsGroup.js';
|
|
8
9
|
export { SegmentedTabPanel } from './SegmentedTabPanel.js';
|
|
10
|
+
/**
|
|
11
|
+
* Returns the tab value whose button currently has keyboard focus.
|
|
12
|
+
*
|
|
13
|
+
* @param tabRefs - Map of tab values to their button elements.
|
|
14
|
+
* @returns Focused tab value, or undefined when focus is outside the tab buttons.
|
|
15
|
+
*/
|
|
16
|
+
function getFocusedTabValue(tabRefs) {
|
|
17
|
+
const active = document.activeElement;
|
|
18
|
+
if (active == null)
|
|
19
|
+
return undefined;
|
|
20
|
+
for (const [tabValue, button] of tabRefs.entries()) {
|
|
21
|
+
if (button === active)
|
|
22
|
+
return tabValue;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Moves focus to the first visible focusable element inside a tab panel.
|
|
28
|
+
*
|
|
29
|
+
* Uses {@link getFocusableElements} first, then falls back to a direct query
|
|
30
|
+
* when layout APIs report no visible descendants (for example in jsdom).
|
|
31
|
+
*
|
|
32
|
+
* @param panel - Tab panel element linked from the active tab.
|
|
33
|
+
* @returns True when focus moved into the panel.
|
|
34
|
+
*/
|
|
35
|
+
function focusFirstFocusableInPanel(panel) {
|
|
36
|
+
if (panel == null)
|
|
37
|
+
return false;
|
|
38
|
+
let firstFocusable = getFocusableElements(panel)[0];
|
|
39
|
+
if (firstFocusable == null) {
|
|
40
|
+
const selector = 'button:not([disabled]), a[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
41
|
+
firstFocusable =
|
|
42
|
+
Array.from(panel.querySelectorAll(selector)).find((element) => !element.closest('[aria-hidden="true"]')) ?? undefined;
|
|
43
|
+
}
|
|
44
|
+
if (firstFocusable == null || typeof firstFocusable.focus !== 'function') {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
firstFocusable.focus();
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
9
50
|
/**
|
|
10
51
|
* macOS-style segmented tab control with WAI-ARIA tabs or radiogroup semantics.
|
|
11
52
|
*/
|
|
@@ -113,13 +154,40 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
|
|
|
113
154
|
const tabListClassName = ['inline-flex min-w-0 flex-1 items-center', fullWidth ? 'w-full' : '']
|
|
114
155
|
.filter(Boolean)
|
|
115
156
|
.join(' ');
|
|
157
|
+
const isRadiogroup = pattern === 'radiogroup';
|
|
116
158
|
/**
|
|
117
159
|
* Moves selection with arrow, Home, and End keys and focuses the newly
|
|
118
|
-
* selected tab or radio control.
|
|
160
|
+
* selected tab or radio control. ArrowDown on a focused tab moves focus into
|
|
161
|
+
* the linked tab panel when used inside `SegmentedTabsGroup`.
|
|
119
162
|
*
|
|
120
163
|
* @param event - Keyboard event from the tab list or radio group container.
|
|
121
164
|
*/
|
|
122
165
|
const handleKeyDown = useCallback((event) => {
|
|
166
|
+
if (event.key === 'ArrowDown' && !isRadiogroup && context) {
|
|
167
|
+
const focusedTabValue = getFocusedTabValue(tabRefs.current);
|
|
168
|
+
if (focusedTabValue !== undefined) {
|
|
169
|
+
event.preventDefault();
|
|
170
|
+
/**
|
|
171
|
+
* Focuses the first focusable control in the panel for the focused tab.
|
|
172
|
+
*/
|
|
173
|
+
const focusPanel = () => {
|
|
174
|
+
const panel = document.getElementById(getPanelId(focusedTabValue));
|
|
175
|
+
if (panel instanceof HTMLElement) {
|
|
176
|
+
focusFirstFocusableInPanel(panel);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
if (focusedTabValue !== value) {
|
|
180
|
+
onChange(focusedTabValue);
|
|
181
|
+
requestAnimationFrame(() => {
|
|
182
|
+
requestAnimationFrame(focusPanel);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
requestAnimationFrame(focusPanel);
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
123
191
|
const currentIndex = visibleTabs.findIndex((tab) => tab.value === value);
|
|
124
192
|
const disabledIndices = visibleTabs
|
|
125
193
|
.map((tab, index) => (tab.disabled ? index : -1))
|
|
@@ -137,8 +205,7 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
|
|
|
137
205
|
requestAnimationFrame(() => {
|
|
138
206
|
tabRefs.current.get(nextTab.value)?.focus();
|
|
139
207
|
});
|
|
140
|
-
}, [visibleTabs, value, onChange]);
|
|
141
|
-
const isRadiogroup = pattern === 'radiogroup';
|
|
208
|
+
}, [visibleTabs, value, onChange, isRadiogroup, context, getPanelId]);
|
|
142
209
|
return (_jsxs("div", { className: outerClassName, children: [_jsx("div", { className: tabListClassName, role: isRadiogroup ? 'radiogroup' : 'tablist', "aria-label": ariaLabel, ...(!isRadiogroup ? { 'aria-orientation': 'horizontal' } : {}), onKeyDown: handleKeyDown, children: visibleTabs.map((tab) => {
|
|
143
210
|
const selected = value === tab.value;
|
|
144
211
|
const tabClassName = `${segment(selected)}${fullWidth ? ' flex-1' : ''}`;
|
|
@@ -147,7 +214,7 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
|
|
|
147
214
|
tabRefs.current.set(tab.value, element);
|
|
148
215
|
else
|
|
149
216
|
tabRefs.current.delete(tab.value);
|
|
150
|
-
}, type: "button", className: tabClassName, disabled: tab.disabled, tabIndex:
|
|
217
|
+
}, type: "button", className: tabClassName, disabled: tab.disabled, tabIndex: tab.disabled ? -1 : 0, onClick: () => onChange(tab.value), ...(isRadiogroup
|
|
151
218
|
? { role: 'radio', 'aria-checked': selected }
|
|
152
219
|
: {
|
|
153
220
|
role: 'tab',
|
|
@@ -12,6 +12,11 @@ interface Props {
|
|
|
12
12
|
* Optional tooltip label; defaults to "Close tab".
|
|
13
13
|
*/
|
|
14
14
|
title?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Tab order index for roving-tablist layouts. Pass `0` on the active tab and
|
|
17
|
+
* `-1` on inactive tabs so the tab label receives focus before the close control.
|
|
18
|
+
*/
|
|
19
|
+
tabIndex?: number;
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
22
|
* Close control for document-style tabs in the request editor and AI chat.
|
|
@@ -19,7 +24,8 @@ interface Props {
|
|
|
19
24
|
* @param ariaLabel - Accessible name describing which tab closes.
|
|
20
25
|
* @param onClick - Click handler; callers should stop propagation when needed.
|
|
21
26
|
* @param title - Optional native tooltip text.
|
|
27
|
+
* @param tabIndex - Tab order index; use with roving tabindex on the tab label.
|
|
22
28
|
*/
|
|
23
|
-
export declare function TabCloseButton({ ariaLabel, onClick, title }: Props): JSX.Element;
|
|
29
|
+
export declare function TabCloseButton({ ariaLabel, onClick, title, tabIndex }: Props): JSX.Element;
|
|
24
30
|
export {};
|
|
25
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TabCloseButton/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAIjC,UAAU,KAAK;IACb;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAE9D;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TabCloseButton/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAIjC,UAAU,KAAK;IACb;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAC;IAE9D;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAC7B,SAAS,EACT,OAAO,EACP,KAAmB,EACnB,QAAQ,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAarB"}
|
|
@@ -7,7 +7,8 @@ import { FaIcon } from '../FaIcon/index.js';
|
|
|
7
7
|
* @param ariaLabel - Accessible name describing which tab closes.
|
|
8
8
|
* @param onClick - Click handler; callers should stop propagation when needed.
|
|
9
9
|
* @param title - Optional native tooltip text.
|
|
10
|
+
* @param tabIndex - Tab order index; use with roving tabindex on the tab label.
|
|
10
11
|
*/
|
|
11
|
-
export function TabCloseButton({ ariaLabel, onClick, title = 'Close tab' }) {
|
|
12
|
-
return (_jsx("button", { type: "button", className: "hc-tab-close-button inline-flex aspect-square shrink-0 cursor-pointer items-center justify-center self-stretch rounded-md border-none bg-transparent text-[14px] text-muted hover:bg-selection hover:text-text focus-visible:bg-selection focus-visible:text-text app-no-drag", title: title, "aria-label": ariaLabel, onClick: onClick, children: _jsx(FaIcon, { icon: faXmark, className: "h-3.5 w-3.5" }) }));
|
|
12
|
+
export function TabCloseButton({ ariaLabel, onClick, title = 'Close tab', tabIndex }) {
|
|
13
|
+
return (_jsx("button", { type: "button", className: "hc-tab-close-button inline-flex aspect-square shrink-0 cursor-pointer items-center justify-center self-stretch rounded-md border-none bg-transparent text-[14px] text-muted hover:bg-selection hover:text-text focus-visible:bg-selection focus-visible:text-text app-no-drag", title: title, "aria-label": ariaLabel, tabIndex: tabIndex, onClick: onClick, children: _jsx(FaIcon, { icon: faXmark, className: "h-3.5 w-3.5" }) }));
|
|
13
14
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../src/components/classes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,6GACmF,CAAC;
|
|
1
|
+
{"version":3,"file":"classes.d.ts","sourceRoot":"","sources":["../../src/components/classes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,6GACmF,CAAC;AAK7G;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAI/C"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Shared macOS-style Tailwind class strings for SDK UI components.
|
|
3
3
|
*/
|
|
4
4
|
export const segmentGroup = 'inline-flex p-3 border-b border-separator w-full shadow-[inset_0_0.5px_1px_rgba(0,0,0,0.06)] app-no-drag';
|
|
5
|
+
const segmentFocusVisible = 'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent';
|
|
5
6
|
/**
|
|
6
7
|
* Tailwind classes for a segmented control button.
|
|
7
8
|
*
|
|
@@ -9,6 +10,6 @@ export const segmentGroup = 'inline-flex p-3 border-b border-separator w-full sh
|
|
|
9
10
|
*/
|
|
10
11
|
export function segment(active) {
|
|
11
12
|
return active
|
|
12
|
-
?
|
|
13
|
-
:
|
|
13
|
+
? `cursor-pointer rounded-[2.5px] border-none bg-field px-3 py-1 text-[15px] text-text shadow-sm app-no-drag ${segmentFocusVisible}`
|
|
14
|
+
: `cursor-pointer rounded-[2.5px] border-none bg-transparent px-3 py-1 text-[15px] text-muted hover:text-text app-no-drag ${segmentFocusVisible}`;
|
|
14
15
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@harborclient/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "TypeScript SDK for HarborClient plugin development.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"harborclient",
|
|
@@ -181,6 +181,7 @@
|
|
|
181
181
|
"@tailwindcss/vite": "^4.3.1",
|
|
182
182
|
"@types/node": "^26.0.1",
|
|
183
183
|
"@types/react": "^19.1.2",
|
|
184
|
+
"@types/react-dom": "^19.1.2",
|
|
184
185
|
"concurrently": "^8.2.2",
|
|
185
186
|
"cytoscape": "^3.34.0",
|
|
186
187
|
"cytoscape-cose-bilkent": "^4.1.0",
|
|
@@ -188,6 +189,7 @@
|
|
|
188
189
|
"debug": "^4.4.3",
|
|
189
190
|
"eslint": "^10.5.0",
|
|
190
191
|
"jest": "^30.4.2",
|
|
192
|
+
"jest-environment-jsdom": "^30.4.1",
|
|
191
193
|
"mermaid": "^11.15.0",
|
|
192
194
|
"prettier": "^3.8.4",
|
|
193
195
|
"react": "^19.2.7",
|