@harborclient/sdk 0.6.3 → 0.6.6

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 (86) hide show
  1. package/dist/components/BusyIndicator/index.d.ts +3 -2
  2. package/dist/components/BusyIndicator/index.d.ts.map +1 -1
  3. package/dist/components/BusyIndicator/index.js +11 -3
  4. package/dist/components/Button/index.d.ts +23 -8
  5. package/dist/components/Button/index.d.ts.map +1 -1
  6. package/dist/components/Button/index.js +6 -4
  7. package/dist/components/CodeEditor/index.d.ts +9 -1
  8. package/dist/components/CodeEditor/index.d.ts.map +1 -1
  9. package/dist/components/CodeEditor/index.js +186 -62
  10. package/dist/components/FieldError/index.d.ts.map +1 -1
  11. package/dist/components/FieldError/index.js +1 -1
  12. package/dist/components/FormDataEditor/index.d.ts.map +1 -1
  13. package/dist/components/FormDataEditor/index.js +10 -38
  14. package/dist/components/FormDataEditor/utils.d.ts +19 -0
  15. package/dist/components/FormDataEditor/utils.d.ts.map +1 -0
  16. package/dist/components/FormDataEditor/utils.js +28 -0
  17. package/dist/components/FormGroup/index.d.ts +6 -1
  18. package/dist/components/FormGroup/index.d.ts.map +1 -1
  19. package/dist/components/FormGroup/index.js +25 -31
  20. package/dist/components/KeyValueEditor/index.d.ts.map +1 -1
  21. package/dist/components/KeyValueEditor/index.js +2 -3
  22. package/dist/components/Modal/ModalFormLayout.d.ts.map +1 -1
  23. package/dist/components/Modal/ModalFormLayout.js +2 -1
  24. package/dist/components/Modal/ModalHeader.d.ts +5 -1
  25. package/dist/components/Modal/ModalHeader.d.ts.map +1 -1
  26. package/dist/components/Modal/ModalHeader.js +2 -2
  27. package/dist/components/Modal/index.d.ts.map +1 -1
  28. package/dist/components/Modal/index.js +4 -2
  29. package/dist/components/OverlayCloseButton/index.d.ts.map +1 -1
  30. package/dist/components/OverlayCloseButton/index.js +1 -2
  31. package/dist/components/Page/index.d.ts +46 -0
  32. package/dist/components/Page/index.d.ts.map +1 -0
  33. package/dist/components/Page/index.js +18 -0
  34. package/dist/components/PageSidebar/index.d.ts +45 -0
  35. package/dist/components/PageSidebar/index.d.ts.map +1 -0
  36. package/dist/components/PageSidebar/index.js +24 -0
  37. package/dist/components/Resizable/Resizable.d.ts.map +1 -1
  38. package/dist/components/Resizable/Resizable.js +1 -1
  39. package/dist/components/Resizable/ResizeHandle.js +2 -2
  40. package/dist/components/ResourceList/index.d.ts +11 -1
  41. package/dist/components/ResourceList/index.d.ts.map +1 -1
  42. package/dist/components/ResourceList/index.js +4 -2
  43. package/dist/components/RowActionsMenu/index.d.ts.map +1 -1
  44. package/dist/components/RowActionsMenu/index.js +150 -11
  45. package/dist/components/SegmentedTabs/index.d.ts.map +1 -1
  46. package/dist/components/SegmentedTabs/index.js +2 -2
  47. package/dist/components/SidebarLayout/index.d.ts +22 -0
  48. package/dist/components/SidebarLayout/index.d.ts.map +1 -0
  49. package/dist/components/SidebarLayout/index.js +11 -0
  50. package/dist/components/TabCloseButton/index.d.ts +1 -1
  51. package/dist/components/TabCloseButton/index.js +2 -2
  52. package/dist/components/Table/index.d.ts +103 -0
  53. package/dist/components/Table/index.d.ts.map +1 -0
  54. package/dist/components/Table/index.js +99 -0
  55. package/dist/components/VariableInput/index.d.ts.map +1 -1
  56. package/dist/components/VariableInput/index.js +84 -10
  57. package/dist/components/VariableTable/index.d.ts.map +1 -1
  58. package/dist/components/VariableTable/index.js +2 -2
  59. package/dist/components/enhanceControl.d.ts +28 -0
  60. package/dist/components/enhanceControl.d.ts.map +1 -0
  61. package/dist/components/enhanceControl.js +84 -0
  62. package/dist/components/enhanceControl.test.d.ts +2 -0
  63. package/dist/components/enhanceControl.test.d.ts.map +1 -0
  64. package/dist/components/enhanceControl.test.js +83 -0
  65. package/dist/components/index.d.ts +6 -1
  66. package/dist/components/index.d.ts.map +1 -1
  67. package/dist/components/index.js +4 -1
  68. package/dist/components/useDialogFocus.d.ts +3 -2
  69. package/dist/components/useDialogFocus.d.ts.map +1 -1
  70. package/dist/components/useDialogFocus.js +61 -4
  71. package/dist/components/utils.d.ts +25 -0
  72. package/dist/components/utils.d.ts.map +1 -1
  73. package/dist/components/utils.js +24 -0
  74. package/dist/components/utils.test.js +18 -1
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/variables/index.d.ts +1 -1
  77. package/dist/variables/index.d.ts.map +1 -1
  78. package/dist/variables/index.js +1 -1
  79. package/dist/variables/tokens.d.ts +31 -0
  80. package/dist/variables/tokens.d.ts.map +1 -1
  81. package/dist/variables/tokens.js +38 -1
  82. package/dist/variables/tokens.test.js +46 -1
  83. package/package.json +1 -1
  84. package/dist/components/OverlayPage/index.d.ts +0 -40
  85. package/dist/components/OverlayPage/index.d.ts.map +0 -1
  86. package/dist/components/OverlayPage/index.js +0 -18
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
- import { useEffect, useRef } from '@harborclient/sdk/react';
2
+ import { useCallback, useEffect, useMemo, useRef, useState } from '@harborclient/sdk/react';
3
3
  import { Button } from '../Button/index.js';
4
4
  import { FaIcon } from '../FaIcon/index.js';
5
5
  import { faBars } from '@fortawesome/free-solid-svg-icons';
6
+ import { resolveMenuTypeahead, resolveTabListKeyAction } from '../utils.js';
7
+ const TYPEAHEAD_TIMEOUT_MS = 500;
6
8
  /**
7
9
  * Tailwind classes for a single menu item button.
8
10
  *
@@ -19,7 +21,79 @@ function menuItemClass(variant) {
19
21
  */
20
22
  export function RowActionsMenu({ groups, menuId, openMenuId, onOpenChange }) {
21
23
  const isOpen = openMenuId === menuId;
24
+ const menuElementId = `${menuId}-menu`;
22
25
  const rootRef = useRef(null);
26
+ const triggerRef = useRef(null);
27
+ const itemRefs = useRef([]);
28
+ const typeaheadBuffer = useRef('');
29
+ const typeaheadTimer = useRef(null);
30
+ const wasOpenRef = useRef(isOpen);
31
+ const [focusedIndex, setFocusedIndex] = useState(0);
32
+ const flatItems = useMemo(() => groups.flat(), [groups]);
33
+ const itemLabels = useMemo(() => flatItems.map((item) => item.label), [flatItems]);
34
+ /**
35
+ * Clears the accumulated typeahead buffer.
36
+ */
37
+ const clearTypeahead = useCallback(() => {
38
+ typeaheadBuffer.current = '';
39
+ if (typeaheadTimer.current != null) {
40
+ window.clearTimeout(typeaheadTimer.current);
41
+ typeaheadTimer.current = null;
42
+ }
43
+ }, []);
44
+ /**
45
+ * Closes the menu and returns focus to the trigger button.
46
+ */
47
+ const closeMenu = useCallback(() => {
48
+ clearTypeahead();
49
+ onOpenChange(null);
50
+ requestAnimationFrame(() => {
51
+ triggerRef.current?.focus();
52
+ });
53
+ }, [clearTypeahead, onOpenChange]);
54
+ /**
55
+ * Focuses a menu item by index and updates roving tabindex state.
56
+ *
57
+ * @param index - Flat index of the menu item to focus.
58
+ */
59
+ const focusItem = useCallback((index) => {
60
+ setFocusedIndex(index);
61
+ requestAnimationFrame(() => {
62
+ itemRefs.current[index]?.focus();
63
+ });
64
+ }, []);
65
+ /**
66
+ * Opens the menu and focuses the first or last item.
67
+ *
68
+ * @param focusLast - When true, focus the last item instead of the first.
69
+ */
70
+ const openMenu = useCallback((focusLast = false) => {
71
+ if (flatItems.length === 0)
72
+ return;
73
+ setFocusedIndex(focusLast ? flatItems.length - 1 : 0);
74
+ onOpenChange(menuId);
75
+ }, [flatItems.length, menuId, onOpenChange]);
76
+ /**
77
+ * Moves focus into the menu after it opens and item refs are mounted.
78
+ */
79
+ useEffect(() => {
80
+ if (isOpen && !wasOpenRef.current) {
81
+ requestAnimationFrame(() => {
82
+ itemRefs.current[focusedIndex]?.focus();
83
+ });
84
+ }
85
+ wasOpenRef.current = isOpen;
86
+ }, [focusedIndex, isOpen]);
87
+ /**
88
+ * Resets item refs when the menu closes.
89
+ */
90
+ useEffect(() => {
91
+ if (!isOpen) {
92
+ itemRefs.current = [];
93
+ setFocusedIndex(0);
94
+ clearTypeahead();
95
+ }
96
+ }, [clearTypeahead, isOpen]);
23
97
  /**
24
98
  * Closes the menu on outside click or Escape while it is open.
25
99
  */
@@ -28,12 +102,13 @@ export function RowActionsMenu({ groups, menuId, openMenuId, onOpenChange }) {
28
102
  return;
29
103
  const handleMouseDown = (e) => {
30
104
  if (rootRef.current && !rootRef.current.contains(e.target)) {
31
- onOpenChange(null);
105
+ closeMenu();
32
106
  }
33
107
  };
34
108
  const handleKeyDown = (e) => {
35
109
  if (e.key === 'Escape') {
36
- onOpenChange(null);
110
+ e.preventDefault();
111
+ closeMenu();
37
112
  }
38
113
  };
39
114
  document.addEventListener('mousedown', handleMouseDown);
@@ -42,13 +117,77 @@ export function RowActionsMenu({ groups, menuId, openMenuId, onOpenChange }) {
42
117
  document.removeEventListener('mousedown', handleMouseDown);
43
118
  document.removeEventListener('keydown', handleKeyDown);
44
119
  };
45
- }, [isOpen, onOpenChange]);
46
- return (_jsxs("div", { ref: rootRef, className: "relative shrink-0", children: [_jsx(Button, { type: "button", variant: "icon", className: isOpen ? 'opacity-100' : undefined, title: "Actions", "aria-label": "Row actions", "aria-haspopup": "menu", "aria-expanded": isOpen, onClick: (e) => {
120
+ }, [closeMenu, isOpen]);
121
+ /**
122
+ * Handles keyboard interaction on the menu trigger when closed.
123
+ *
124
+ * @param event - Keyboard event from the trigger button.
125
+ */
126
+ const handleTriggerKeyDown = (event) => {
127
+ if (isOpen)
128
+ return;
129
+ if (event.key === 'ArrowDown' || event.key === 'Enter' || event.key === ' ') {
130
+ event.preventDefault();
131
+ openMenu(false);
132
+ return;
133
+ }
134
+ if (event.key === 'ArrowUp') {
135
+ event.preventDefault();
136
+ openMenu(true);
137
+ }
138
+ };
139
+ /**
140
+ * Handles keyboard navigation within the open menu.
141
+ *
142
+ * @param event - Keyboard event from the menu container.
143
+ */
144
+ const handleMenuKeyDown = (event) => {
145
+ if (flatItems.length === 0)
146
+ return;
147
+ if (event.key === 'Tab') {
148
+ closeMenu();
149
+ return;
150
+ }
151
+ const arrowIndex = resolveTabListKeyAction(event.key, focusedIndex, flatItems.length);
152
+ if (arrowIndex !== null) {
153
+ event.preventDefault();
154
+ clearTypeahead();
155
+ focusItem(arrowIndex);
156
+ return;
157
+ }
158
+ const typeahead = resolveMenuTypeahead(itemLabels, focusedIndex, event.key, typeaheadBuffer.current);
159
+ if (typeahead) {
160
+ event.preventDefault();
161
+ typeaheadBuffer.current = typeahead.buffer;
162
+ if (typeaheadTimer.current != null) {
163
+ window.clearTimeout(typeaheadTimer.current);
164
+ }
165
+ typeaheadTimer.current = window.setTimeout(() => {
166
+ typeaheadBuffer.current = '';
167
+ typeaheadTimer.current = null;
168
+ }, TYPEAHEAD_TIMEOUT_MS);
169
+ focusItem(typeahead.index);
170
+ }
171
+ };
172
+ return (_jsxs("div", { ref: rootRef, className: "relative shrink-0", children: [_jsx(Button, { innerRef: triggerRef, type: "button", variant: "icon", title: "Actions", "aria-label": "Row actions", "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": isOpen ? menuElementId : undefined, onClick: (e) => {
47
173
  e.stopPropagation();
48
- onOpenChange(isOpen ? null : menuId);
49
- }, children: _jsx(FaIcon, { icon: faBars, className: "h-3.5 w-3.5" }) }), isOpen && (_jsx("div", { role: "menu", className: "absolute right-0 top-full z-10 mt-0.5 min-w-[120px] rounded-md border border-separator bg-surface py-1 shadow-md app-no-drag", children: groups.map((group, groupIndex) => (_jsx("div", { role: "group", className: groupIndex > 0 ? 'border-t border-separator' : undefined, children: group.map((item) => (_jsx("button", { type: "button", role: "menuitem", className: menuItemClass(item.variant), onClick: (e) => {
50
- e.stopPropagation();
51
- onOpenChange(null);
52
- item.onSelect();
53
- }, children: item.label }, item.label))) }, groupIndex))) }))] }));
174
+ if (isOpen) {
175
+ closeMenu();
176
+ }
177
+ else {
178
+ openMenu(false);
179
+ }
180
+ }, onKeyDown: handleTriggerKeyDown, children: _jsx(FaIcon, { icon: faBars, className: "h-3.5 w-3.5" }) }), isOpen && (_jsx("div", { id: menuElementId, role: "menu", className: "absolute right-0 top-full z-10 mt-0.5 min-w-[120px] rounded-md border border-separator bg-surface py-1 shadow-md app-no-drag", onKeyDown: handleMenuKeyDown, children: groups.map((group, groupIndex) => {
181
+ let flatIndex = groups.slice(0, groupIndex).reduce((count, g) => count + g.length, 0);
182
+ return (_jsx("div", { className: groupIndex > 0 ? 'border-t border-separator' : undefined, children: group.map((item) => {
183
+ const itemIndex = flatIndex++;
184
+ return (_jsx("button", { ref: (el) => {
185
+ itemRefs.current[itemIndex] = el;
186
+ }, type: "button", role: "menuitem", tabIndex: itemIndex === focusedIndex ? 0 : -1, className: menuItemClass(item.variant), onClick: (e) => {
187
+ e.stopPropagation();
188
+ closeMenu();
189
+ item.onSelect();
190
+ }, children: item.label }, item.label));
191
+ }) }, groupIndex));
192
+ }) }))] }));
54
193
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAC;AAI3D,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM;IACvC;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC;IAET;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA+BD,UAAU,UAAU,CAAC,CAAC,SAAS,MAAM;IACnC;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC;IAET;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE7B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EAAE,EACnD,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACT,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAmB7B;AAED,UAAU,UAAU,CAAC,CAAC,SAAS,MAAM;IACnC;;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,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAkBpC;AAED,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,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,SAAiB,EACjB,SAAS,EACT,OAAgB,EAChB,SAAS,EAAE,aAAa,EACzB,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CA8GxB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SegmentedTabs/index.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,GAAG,EAAiB,SAAS,EAAE,MAAM,OAAO,CAAC;AAI3D,MAAM,WAAW,OAAO,CAAC,CAAC,SAAS,MAAM;IACvC;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC;IAET;;OAEG;IACH,KAAK,EAAE,SAAS,CAAC;IAEjB;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA+BD,UAAU,UAAU,CAAC,CAAC,SAAS,MAAM;IACnC;;OAEG;IACH,KAAK,EAAE,CAAC,CAAC;IAET;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IAE7B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,MAAM,EAAE,EACnD,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,EACT,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CAmB7B;AAED,UAAU,UAAU,CAAC,CAAC,SAAS,MAAM;IACnC;;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,UAAU,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAkBpC;AAED,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,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,SAAiB,EACjB,SAAS,EACT,OAAgB,EAChB,SAAS,EAAE,aAAa,EACzB,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,OAAO,CA+GxB"}
@@ -91,7 +91,7 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
91
91
  });
92
92
  }, [visibleTabs, value, onChange]);
93
93
  const isRadiogroup = pattern === 'radiogroup';
94
- return (_jsx("div", { className: groupClassName, role: isRadiogroup ? 'radiogroup' : 'tablist', "aria-label": ariaLabel, onKeyDown: handleKeyDown, children: visibleTabs.map((tab) => {
94
+ return (_jsx("div", { className: groupClassName, role: isRadiogroup ? 'radiogroup' : 'tablist', "aria-label": ariaLabel, ...(!isRadiogroup ? { 'aria-orientation': 'horizontal' } : {}), onKeyDown: handleKeyDown, children: visibleTabs.map((tab) => {
95
95
  const selected = value === tab.value;
96
96
  const tabClassName = `${segment(selected)}${fullWidth ? ' flex-1' : ''}`;
97
97
  return (_jsx("button", { ref: (element) => {
@@ -105,7 +105,7 @@ export function SegmentedTabs({ tabs, value: valueProp, onChange: onChangeProp,
105
105
  role: 'tab',
106
106
  id: getTabId(tab.value),
107
107
  'aria-selected': selected,
108
- 'aria-controls': getPanelId(tab.value)
108
+ ...(context ? { 'aria-controls': getPanelId(tab.value) } : {})
109
109
  }), children: _jsxs("span", { className: "inline-flex items-center gap-1.5", children: [tab.label, tab.indicator && _jsx("span", { className: "h-1.5 w-1.5 rounded-full bg-accent", "aria-hidden": true })] }) }, tab.value));
110
110
  }) }));
111
111
  }
@@ -0,0 +1,22 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ interface Props {
3
+ /**
4
+ * Sidebar navigation rendered in the left column.
5
+ */
6
+ sidebar: ReactNode;
7
+ /**
8
+ * Scrollable main content rendered in the right column.
9
+ */
10
+ children: ReactNode;
11
+ /**
12
+ * Additional Tailwind classes merged onto the outer flex column wrapper.
13
+ */
14
+ className?: string;
15
+ }
16
+ /**
17
+ * Two-pane layout shell: a fixed sidebar column and a scrollable content area
18
+ * with standard page padding.
19
+ */
20
+ export declare function SidebarLayout({ sidebar, children, className }: Props): JSX.Element;
21
+ export {};
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SidebarLayout/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5C,UAAU,KAAK;IACb;;OAEG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAalF"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
+ /**
3
+ * Two-pane layout shell: a fixed sidebar column and a scrollable content area
4
+ * with standard page padding.
5
+ */
6
+ export function SidebarLayout({ sidebar, children, className }) {
7
+ const outer = className
8
+ ? `flex min-h-0 flex-1 flex-col ${className}`
9
+ : 'flex min-h-0 flex-1 flex-col';
10
+ return (_jsx("div", { className: outer, children: _jsxs("div", { className: "flex min-h-0 flex-1", children: [sidebar, _jsx("div", { className: "flex-1 overflow-y-auto p-6", children: children })] }) }));
11
+ }
@@ -14,7 +14,7 @@ interface Props {
14
14
  title?: string;
15
15
  }
16
16
  /**
17
- * Hover-reveal close control for document-style tabs in the request editor and AI chat.
17
+ * Close control for document-style tabs in the request editor and AI chat.
18
18
  *
19
19
  * @param ariaLabel - Accessible name describing which tab closes.
20
20
  * @param onClick - Click handler; callers should stop propagation when needed.
@@ -2,12 +2,12 @@ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
2
  import { faXmark } from '@fortawesome/free-solid-svg-icons';
3
3
  import { FaIcon } from '../FaIcon/index.js';
4
4
  /**
5
- * Hover-reveal close control for document-style tabs in the request editor and AI chat.
5
+ * Close control for document-style tabs in the request editor and AI chat.
6
6
  *
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
10
  */
11
11
  export function TabCloseButton({ ariaLabel, onClick, title = 'Close tab' }) {
12
- return (_jsx("button", { type: "button", className: "inline-flex aspect-square shrink-0 cursor-pointer items-center justify-center self-stretch rounded-md border-none bg-transparent text-[14px] text-muted opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100 focus:opacity-100 focus-visible:opacity-100 hover:bg-selection hover: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
+ return (_jsx("button", { type: "button", className: "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" }) }));
13
13
  }
@@ -0,0 +1,103 @@
1
+ import type { HTMLAttributes, JSX, ReactNode, TdHTMLAttributes, ThHTMLAttributes } from 'react';
2
+ /**
3
+ * Layout preset for {@link Table} and its subcomponents.
4
+ */
5
+ export type TableVariant = 'bordered' | 'loose';
6
+ /**
7
+ * Default header cell classes for bordered editor tables.
8
+ */
9
+ export declare const tableHeadClass = "border-r border-b border-separator p-3 text-left text-[14px] font-medium uppercase tracking-wide text-muted last:border-r-0";
10
+ /**
11
+ * Default body cell classes for bordered editor tables.
12
+ */
13
+ export declare const tableCellClass = "border-r border-b border-separator p-3 last:border-r-0";
14
+ /**
15
+ * Header cell classes for loose tables with separate cell spacing.
16
+ */
17
+ export declare const tableHeadClassLoose = "pb-1 text-left text-[14px] font-medium uppercase tracking-wide text-muted";
18
+ /**
19
+ * Body cell classes for loose tables with separate cell spacing.
20
+ */
21
+ export declare const tableCellClassLoose = "";
22
+ interface TableProps {
23
+ /**
24
+ * Table header and body sections.
25
+ */
26
+ children: ReactNode;
27
+ /**
28
+ * Layout preset for the table shell and cell styling.
29
+ */
30
+ variant?: TableVariant;
31
+ /**
32
+ * Additional Tailwind classes merged onto the outer wrapper or table element.
33
+ */
34
+ className?: string;
35
+ }
36
+ /**
37
+ * Table shell for editable row layouts.
38
+ *
39
+ * @param children - {@link TableHeader} and {@link TableBody} sections.
40
+ * @param variant - Bordered collapsed layout or loose separate-cell spacing.
41
+ * @param className - Extra classes on the wrapper (bordered) or table element (loose).
42
+ */
43
+ export declare function Table({ children, variant, className }: TableProps): JSX.Element;
44
+ interface TableSectionProps extends HTMLAttributes<HTMLTableSectionElement> {
45
+ /**
46
+ * Header or body row content.
47
+ */
48
+ children: ReactNode;
49
+ /**
50
+ * Additional Tailwind classes merged onto the section element.
51
+ */
52
+ className?: string;
53
+ }
54
+ /**
55
+ * Table header section wrapper.
56
+ *
57
+ * @param children - Header row elements, typically native `tr` nodes.
58
+ * @param className - Extra classes appended after any section preset.
59
+ */
60
+ export declare function TableHeader({ children, className, ...rest }: TableSectionProps): JSX.Element;
61
+ /**
62
+ * Table body section wrapper.
63
+ *
64
+ * @param children - Body row elements, typically native `tr` nodes.
65
+ * @param className - Extra classes appended after the layout preset.
66
+ */
67
+ export declare function TableBody({ children, className, ...rest }: TableSectionProps): JSX.Element;
68
+ interface TableHeadProps extends ThHTMLAttributes<HTMLTableCellElement> {
69
+ /**
70
+ * Header cell content.
71
+ */
72
+ children?: ReactNode;
73
+ /**
74
+ * Additional Tailwind classes merged after the header cell preset.
75
+ */
76
+ className?: string;
77
+ }
78
+ /**
79
+ * Header cell with table styling for the active variant.
80
+ *
81
+ * @param children - Header label content.
82
+ * @param className - Extra classes appended after the layout preset.
83
+ */
84
+ export declare function TableHead({ children, className, scope, ...rest }: TableHeadProps): JSX.Element;
85
+ interface TableCellProps extends TdHTMLAttributes<HTMLTableCellElement> {
86
+ /**
87
+ * Body cell content.
88
+ */
89
+ children?: ReactNode;
90
+ /**
91
+ * Additional Tailwind classes merged after the body cell preset.
92
+ */
93
+ className?: string;
94
+ }
95
+ /**
96
+ * Body cell with table styling for the active variant.
97
+ *
98
+ * @param children - Cell content.
99
+ * @param className - Extra classes appended after the layout preset.
100
+ */
101
+ export declare function TableCell({ children, className, ...rest }: TableCellProps): JSX.Element;
102
+ export {};
103
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Table/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,SAAS,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAEhG;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,OAAO,CAAC;AAEhD;;GAEG;AACH,eAAO,MAAM,cAAc,gIACoG,CAAC;AAEhI;;GAEG;AACH,eAAO,MAAM,cAAc,2DAA2D,CAAC;AAEvF;;GAEG;AACH,eAAO,MAAM,mBAAmB,8EAC6C,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAuBtC,UAAU,UAAU;IAClB;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IAEvB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,EAAE,QAAQ,EAAE,OAAoB,EAAE,SAAS,EAAE,EAAE,UAAU,GAAG,GAAG,CAAC,OAAO,CAwB5F;AAED,UAAU,iBAAkB,SAAQ,cAAc,CAAC,uBAAuB,CAAC;IACzE;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CAM5F;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,iBAAiB,GAAG,GAAG,CAAC,OAAO,CAU1F;AAED,UAAU,cAAe,SAAQ,gBAAgB,CAAC,oBAAoB,CAAC;IACrE;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAS,EACT,KAAa,EACb,GAAG,IAAI,EACR,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CAU9B;AAED,UAAU,cAAe,SAAQ,gBAAgB,CAAC,oBAAoB,CAAC;IACrE;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EAAE,cAAc,GAAG,GAAG,CAAC,OAAO,CAUvF"}
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
+ import { createContext, createElement, useContext } from '@harborclient/sdk/react';
3
+ /**
4
+ * Default header cell classes for bordered editor tables.
5
+ */
6
+ export const tableHeadClass = 'border-r border-b border-separator p-3 text-left text-[14px] font-medium uppercase tracking-wide text-muted last:border-r-0';
7
+ /**
8
+ * Default body cell classes for bordered editor tables.
9
+ */
10
+ export const tableCellClass = 'border-r border-b border-separator p-3 last:border-r-0';
11
+ /**
12
+ * Header cell classes for loose tables with separate cell spacing.
13
+ */
14
+ export const tableHeadClassLoose = 'pb-1 text-left text-[14px] font-medium uppercase tracking-wide text-muted';
15
+ /**
16
+ * Body cell classes for loose tables with separate cell spacing.
17
+ */
18
+ export const tableCellClassLoose = '';
19
+ const TableVariantContext = createContext('bordered');
20
+ /**
21
+ * Returns preset classes merged with optional overrides.
22
+ *
23
+ * @param base - Variant preset classes.
24
+ * @param className - Additional classes appended after the preset.
25
+ */
26
+ function mergeClasses(base, className) {
27
+ if (base && className) {
28
+ return `${base} ${className}`;
29
+ }
30
+ if (base) {
31
+ return base;
32
+ }
33
+ if (className) {
34
+ return className;
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * Table shell for editable row layouts.
40
+ *
41
+ * @param children - {@link TableHeader} and {@link TableBody} sections.
42
+ * @param variant - Bordered collapsed layout or loose separate-cell spacing.
43
+ * @param className - Extra classes on the wrapper (bordered) or table element (loose).
44
+ */
45
+ export function Table({ children, variant = 'bordered', className }) {
46
+ if (variant === 'loose') {
47
+ const tableBase = 'w-full border-separate border-spacing-x-1.5 border-spacing-y-1.5';
48
+ const tableClasses = className ? `${tableBase} ${className}` : tableBase;
49
+ return createElement(TableVariantContext.Provider, { value: variant }, createElement('table', { className: tableClasses }, children));
50
+ }
51
+ const wrapperBase = 'overflow-hidden rounded-md border border-separator';
52
+ const wrapperClasses = className ? `${wrapperBase} ${className}` : wrapperBase;
53
+ return createElement(TableVariantContext.Provider, { value: variant }, createElement('div', { className: wrapperClasses }, createElement('table', { className: 'w-full border-collapse' }, children)));
54
+ }
55
+ /**
56
+ * Table header section wrapper.
57
+ *
58
+ * @param children - Header row elements, typically native `tr` nodes.
59
+ * @param className - Extra classes appended after any section preset.
60
+ */
61
+ export function TableHeader({ children, className, ...rest }) {
62
+ return (_jsx("thead", { className: className, ...rest, children: children }));
63
+ }
64
+ /**
65
+ * Table body section wrapper.
66
+ *
67
+ * @param children - Body row elements, typically native `tr` nodes.
68
+ * @param className - Extra classes appended after the layout preset.
69
+ */
70
+ export function TableBody({ children, className, ...rest }) {
71
+ const variant = useContext(TableVariantContext);
72
+ const base = variant === 'bordered' ? '[&_tr:last-child_td]:border-b-0' : '';
73
+ const classes = mergeClasses(base, className);
74
+ return (_jsx("tbody", { className: classes, ...rest, children: children }));
75
+ }
76
+ /**
77
+ * Header cell with table styling for the active variant.
78
+ *
79
+ * @param children - Header label content.
80
+ * @param className - Extra classes appended after the layout preset.
81
+ */
82
+ export function TableHead({ children, className, scope = 'col', ...rest }) {
83
+ const variant = useContext(TableVariantContext);
84
+ const base = variant === 'loose' ? tableHeadClassLoose : tableHeadClass;
85
+ const classes = mergeClasses(base, className);
86
+ return (_jsx("th", { scope: scope, className: classes, ...rest, children: children }));
87
+ }
88
+ /**
89
+ * Body cell with table styling for the active variant.
90
+ *
91
+ * @param children - Cell content.
92
+ * @param className - Extra classes appended after the layout preset.
93
+ */
94
+ export function TableCell({ children, className, ...rest }) {
95
+ const variant = useContext(TableVariantContext);
96
+ const base = variant === 'loose' ? tableCellClassLoose : tableCellClass;
97
+ const classes = mergeClasses(base, className);
98
+ return (_jsx("td", { className: classes, ...rest, children: children }));
99
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VariableInput/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,aAAa,EAAc,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAgB/C,MAAM,WAAW,KAAK;IACpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;OAEG;IACH,SAAS,EAAE,QAAQ,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,SAAS,EACT,WAAW,EACX,SAAS,EACT,SAAc,EACd,gBAAgB,EAChB,cAAc,EACd,EAAE,EACF,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EAClC,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAiKrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VariableInput/index.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,aAAa,EAAc,MAAM,OAAO,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAkB/C,MAAM,WAAW,KAAK;IACpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;;OAIG;IACH,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC;;OAEG;IACH,SAAS,EAAE,QAAQ,EAAE,CAAC;IAEtB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,EACR,SAAS,EACT,WAAW,EACX,SAAS,EACT,SAAc,EACd,gBAAgB,EAChB,cAAc,EACd,EAAE,EACF,YAAY,EAAE,SAAS,EACvB,iBAAiB,EAAE,cAAc,EAClC,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CA0PrB"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from '@harborclient/sdk/react';
3
- import { getDynamicVariableDescription, resolveVariable, tokenizeVariables } from '../../variables/index.js';
2
+ import { useEffect, useId, useMemo, useRef, useState } from '@harborclient/sdk/react';
3
+ import { getDynamicVariableDescription, getVariableTokenAtOffset, getVariableTooltipContent, resolveVariable, tokenizeVariables } from '../../variables/index.js';
4
4
  import { Input } from '../forms/index.js';
5
5
  /**
6
6
  * Text input that highlights {{variable}} tokens and shows resolved values on hover.
@@ -14,6 +14,7 @@ export function VariableInput({ value, onChange, variables, placeholder, onKeyDo
14
14
  const spanRefs = useRef(new Map());
15
15
  const hideTimer = useRef(null);
16
16
  const [tooltip, setTooltip] = useState(null);
17
+ const tooltipId = useId();
17
18
  /**
18
19
  * Splits the input value into plain text and {{variable}} token spans for highlighting.
19
20
  */
@@ -48,6 +49,53 @@ export function VariableInput({ value, onChange, variables, placeholder, onKeyDo
48
49
  backdrop.scrollLeft = input.scrollLeft;
49
50
  }
50
51
  };
52
+ /**
53
+ * Shows a tooltip for a variable token at the given screen position.
54
+ *
55
+ * @param key - Variable name from the token.
56
+ * @param top - Top coordinate for tooltip placement.
57
+ * @param left - Horizontal center coordinate for tooltip placement.
58
+ */
59
+ const showTooltipForKey = (key, top, left) => {
60
+ setTooltip({
61
+ key,
62
+ value: resolveVariable(key, variables),
63
+ dynamicDescription: getDynamicVariableDescription(key),
64
+ top,
65
+ left
66
+ });
67
+ };
68
+ /**
69
+ * Updates the tooltip based on the current text caret position.
70
+ */
71
+ const updateTooltipFromCaret = () => {
72
+ const input = inputRef.current;
73
+ if (!input)
74
+ return;
75
+ const offset = input.selectionStart ?? 0;
76
+ const match = getVariableTokenAtOffset(value, offset);
77
+ if (!match) {
78
+ setTooltip(null);
79
+ return;
80
+ }
81
+ let tokenIndex = -1;
82
+ let position = 0;
83
+ for (const [index, token] of tokens.entries()) {
84
+ if (token.key && position === match.start) {
85
+ tokenIndex = index;
86
+ break;
87
+ }
88
+ position += token.text.length;
89
+ }
90
+ const span = tokenIndex >= 0 ? spanRefs.current.get(tokenIndex) : undefined;
91
+ if (span) {
92
+ const rect = span.getBoundingClientRect();
93
+ showTooltipForKey(match.key, rect.top, rect.left + rect.width / 2);
94
+ return;
95
+ }
96
+ const rect = input.getBoundingClientRect();
97
+ showTooltipForKey(match.key, rect.top, rect.left + rect.width / 2);
98
+ };
51
99
  /**
52
100
  * Shows a tooltip when the pointer is over a variable token span.
53
101
  *
@@ -66,24 +114,50 @@ export function VariableInput({ value, onChange, variables, placeholder, onKeyDo
66
114
  e.clientX <= rect.right &&
67
115
  e.clientY >= rect.top &&
68
116
  e.clientY <= rect.bottom) {
69
- setTooltip({
70
- key: token.key,
71
- value: resolveVariable(token.key, variables),
72
- dynamicDescription: getDynamicVariableDescription(token.key),
73
- top: rect.top,
74
- left: rect.left + rect.width / 2
75
- });
117
+ showTooltipForKey(token.key, rect.top, rect.left + rect.width / 2);
76
118
  return;
77
119
  }
78
120
  }
79
121
  setTooltip(null);
80
122
  };
123
+ /**
124
+ * Handles keyboard events on the input, including tooltip dismissal.
125
+ *
126
+ * @param event - Keyboard event from the input.
127
+ */
128
+ const handleKeyDown = (event) => {
129
+ if (event.key === 'Escape' && tooltip) {
130
+ event.preventDefault();
131
+ setTooltip(null);
132
+ return;
133
+ }
134
+ onKeyDown?.(event);
135
+ };
136
+ /**
137
+ * Updates the tooltip after keyboard navigation moves the caret.
138
+ *
139
+ * @param event - Keyboard event from the input.
140
+ */
141
+ const handleKeyUp = (event) => {
142
+ if (event.key === 'ArrowLeft' ||
143
+ event.key === 'ArrowRight' ||
144
+ event.key === 'ArrowUp' ||
145
+ event.key === 'ArrowDown' ||
146
+ event.key === 'Home' ||
147
+ event.key === 'End') {
148
+ updateTooltipFromCaret();
149
+ }
150
+ };
151
+ const tooltipContent = tooltip ? getVariableTooltipContent(tooltip.key, variables) : null;
81
152
  return (_jsxs("div", { className: wrapperClassName ? `relative min-w-0 ${wrapperClassName}` : 'relative min-w-0 flex-1', children: [_jsx("div", { ref: backdropRef, "aria-hidden": true, className: "pointer-events-none absolute inset-0 overflow-hidden whitespace-nowrap px-2 py-1.5 text-[14px] text-inherit", children: value ? (tokens.map((token, index) => token.key ? (_jsx("span", { ref: (el) => {
82
153
  if (el)
83
154
  spanRefs.current.set(index, el);
84
155
  else
85
156
  spanRefs.current.delete(index);
86
- }, className: "text-[#32D2E2]", children: token.text }, index)) : (_jsx("span", { children: token.text }, index)))) : (_jsx("span", { className: "text-muted", children: placeholder })) }), _jsx(Input, { ref: inputRef, id: id, variant: "plain", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, className: `relative w-full min-w-0 border-none bg-transparent px-2 py-1.5 text-[14px] text-transparent caret-text focus-visible:shadow-none ${className}`, type: "text", placeholder: placeholder, value: value, onChange: (e) => onChange(e.target.value), onKeyDown: onKeyDown, onScroll: syncScroll, onMouseMove: handleMouseMove, onMouseLeave: scheduleHide }), tooltip && (_jsxs("div", { className: "pointer-events-auto fixed z-50 flex max-w-sm -translate-x-1/2 -translate-y-full flex-col gap-1.5 rounded-md border border-separator bg-surface px-3 py-2 text-[14px] text-text shadow-md", style: { top: tooltip.top - 4, left: tooltip.left }, onMouseEnter: cancelHide, onMouseLeave: () => setTooltip(null), children: [tooltip.value !== undefined ? (tooltip.value) : tooltip.dynamicDescription ? (_jsxs("span", { className: "text-muted", children: ["Dynamic: ", tooltip.dynamicDescription] })) : (_jsx("span", { className: "text-muted", children: "Not defined" })), onEditVariable && (_jsx("button", { type: "button", className: "self-start text-[14px] text-accent hover:underline app-no-drag", onClick: () => {
157
+ }, className: "text-[#32D2E2]", children: token.text }, index)) : (_jsx("span", { children: token.text }, index)))) : (_jsx("span", { className: "text-muted", children: placeholder })) }), _jsx(Input, { ref: inputRef, id: id, variant: "plain", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": tooltip ? tooltipId : undefined, className: `relative w-full min-w-0 border-none bg-transparent px-2 py-1.5 text-[14px] text-transparent caret-text focus-visible:shadow-none ${className}`, type: "text", placeholder: placeholder, value: value, onChange: (e) => {
158
+ onChange(e.target.value);
159
+ queueMicrotask(updateTooltipFromCaret);
160
+ }, onFocus: updateTooltipFromCaret, onKeyDown: handleKeyDown, onKeyUp: handleKeyUp, onSelect: updateTooltipFromCaret, onClick: updateTooltipFromCaret, onScroll: syncScroll, onMouseMove: handleMouseMove, onMouseLeave: scheduleHide }), tooltip && tooltipContent && (_jsxs("div", { id: tooltipId, role: "tooltip", className: "pointer-events-auto fixed z-50 flex max-w-sm -translate-x-1/2 -translate-y-full flex-col gap-1.5 rounded-md border border-separator bg-surface px-3 py-2 text-[14px] text-text shadow-md", style: { top: tooltip.top - 4, left: tooltip.left }, onMouseEnter: cancelHide, onMouseLeave: () => setTooltip(null), children: [_jsx("span", { className: tooltipContent.muted ? 'text-muted' : undefined, children: tooltipContent.text }), onEditVariable && (_jsx("button", { type: "button", className: "self-start text-[14px] text-accent hover:underline app-no-drag", "aria-label": `Edit value for ${tooltip.key}`, onClick: () => {
87
161
  onEditVariable();
88
162
  setTooltip(null);
89
163
  }, children: "Edit value" }))] }))] }));
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VariableTable/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAa/C,UAAU,KAAK;IACb;;OAEG;IACH,SAAS,EAAE,QAAQ,EAAE,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAE1C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAkHtF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VariableTable/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAY/C,UAAU,KAAK;IACb;;OAEG;IACH,SAAS,EAAE,QAAQ,EAAE,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAE1C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAmHtF"}