react-core-ts 2.0.20 → 2.0.22
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/AutoCompleteWithSelectedList.js +48 -22
- package/dist/AutoCompleteWithSelectedList.js.map +1 -1
- package/dist/AutoCompleteWithTabFilter.js +77 -50
- package/dist/AutoCompleteWithTabFilter.js.map +1 -1
- package/dist/ExpandableAutoComplete.js +1 -1
- package/dist/ExpandableAutoComplete.js.map +1 -1
- package/dist/commontypes.d.ts +1 -0
- package/dist/dropdownFilterTabs.js +52 -23
- package/dist/dropdownFilterTabs.js.map +1 -1
- package/dist/styles/components/selectedautocomplete.css +39 -0
- package/dist/utilities/measureTabListNaturalWidth.d.ts +30 -0
- package/dist/utilities/measureTabListNaturalWidth.js +92 -0
- package/dist/utilities/measureTabListNaturalWidth.js.map +1 -0
- package/package.json +1 -1
- package/src/AutoCompleteWithSelectedList.tsx +49 -13
- package/src/AutoCompleteWithTabFilter.tsx +55 -18
- package/src/ExpandableAutoComplete.tsx +1 -0
- package/src/commontypes.ts +1 -0
- package/src/dropdownFilterTabs.tsx +52 -14
- package/src/styles/components/selectedautocomplete.css +39 -0
- package/src/utilities/measureTabListNaturalWidth.ts +117 -0
- package/dist/styles/styles/components/autocomplete.css +0 -744
- package/dist/styles/styles/components/selectedautocomplete.css +0 -179
- package/dist/styles/styles/components/tooltip.css +0 -497
- package/dist/styles/styles/components/treedropdown.css +0 -97
- package/dist/styles/styles/global.css +0 -8
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clampDropdownToViewportPreferRightAlign = exports.clampDropdownToViewport = exports.measureTabListNaturalWidth = exports.MAX_DROPDOWN_WIDTH_WITH_TABS_PX = exports.MIN_DROPDOWN_WIDTH_PX = void 0;
|
|
4
|
+
var DROPDOWN_HORIZONTAL_PADDING_PX = 16; // .qbs-autocomplete-suggestions has padding: 8px
|
|
5
|
+
var VIEWPORT_RIGHT_GUTTER_PX = 8;
|
|
6
|
+
var VIEWPORT_LEFT_GUTTER_PX = 8;
|
|
7
|
+
exports.MIN_DROPDOWN_WIDTH_PX = 300;
|
|
8
|
+
// When tabs exist, cap dropdown width and let the tab strip scroll horizontally if needed.
|
|
9
|
+
exports.MAX_DROPDOWN_WIDTH_WITH_TABS_PX = 600;
|
|
10
|
+
/**
|
|
11
|
+
* Measures the "natural" (nowrap) width of a rendered tabs <ul>, including the dropdown's horizontal padding.
|
|
12
|
+
* This avoids tabs wrapping/squishing when the trigger/input is narrower than the tab labels.
|
|
13
|
+
*/
|
|
14
|
+
function measureTabListNaturalWidth(tabListEl) {
|
|
15
|
+
if (!tabListEl || typeof document === 'undefined')
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
var container = document.createElement('div');
|
|
19
|
+
container.style.position = 'absolute';
|
|
20
|
+
container.style.top = '-99999px';
|
|
21
|
+
container.style.left = '-99999px';
|
|
22
|
+
container.style.visibility = 'hidden';
|
|
23
|
+
container.style.pointerEvents = 'none';
|
|
24
|
+
container.style.whiteSpace = 'nowrap';
|
|
25
|
+
var clone = tabListEl.cloneNode(true);
|
|
26
|
+
// Force a single row with natural width.
|
|
27
|
+
clone.style.display = 'inline-flex';
|
|
28
|
+
clone.style.flexWrap = 'nowrap';
|
|
29
|
+
clone.style.width = 'max-content';
|
|
30
|
+
clone.style.whiteSpace = 'nowrap';
|
|
31
|
+
// Remove width constraints from items so content can dictate width.
|
|
32
|
+
clone.querySelectorAll('li').forEach(function (li) {
|
|
33
|
+
li.style.flex = '0 0 auto';
|
|
34
|
+
li.style.width = 'auto';
|
|
35
|
+
});
|
|
36
|
+
clone.querySelectorAll('span').forEach(function (span) {
|
|
37
|
+
span.style.width = 'max-content';
|
|
38
|
+
span.style.whiteSpace = 'nowrap';
|
|
39
|
+
});
|
|
40
|
+
container.appendChild(clone);
|
|
41
|
+
document.body.appendChild(container);
|
|
42
|
+
var tabRowWidth = Math.ceil(clone.getBoundingClientRect().width);
|
|
43
|
+
document.body.removeChild(container);
|
|
44
|
+
// Include dropdown padding so the tabs still fit within the content box.
|
|
45
|
+
return tabRowWidth + DROPDOWN_HORIZONTAL_PADDING_PX;
|
|
46
|
+
}
|
|
47
|
+
catch (_a) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
exports.measureTabListNaturalWidth = measureTabListNaturalWidth;
|
|
52
|
+
/**
|
|
53
|
+
* Keeps a dropdown fully visible within the viewport by clamping width and shifting left/right as needed.
|
|
54
|
+
* All coordinates are page coordinates (i.e., include window.scrollX).
|
|
55
|
+
*/
|
|
56
|
+
function clampDropdownToViewport(opts) {
|
|
57
|
+
if (typeof window === 'undefined')
|
|
58
|
+
return { width: opts.desiredWidth, left: opts.desiredLeft };
|
|
59
|
+
var viewportLeft = window.scrollX;
|
|
60
|
+
var viewportRight = window.scrollX + window.innerWidth;
|
|
61
|
+
var maxWidth = Math.max(0, Math.floor(viewportRight - viewportLeft - VIEWPORT_LEFT_GUTTER_PX - VIEWPORT_RIGHT_GUTTER_PX));
|
|
62
|
+
var width = maxWidth > 0 ? Math.min(opts.desiredWidth, maxWidth) : opts.desiredWidth;
|
|
63
|
+
var minLeft = viewportLeft + VIEWPORT_LEFT_GUTTER_PX;
|
|
64
|
+
var maxLeft = viewportRight - VIEWPORT_RIGHT_GUTTER_PX - width;
|
|
65
|
+
var left = Math.min(Math.max(opts.desiredLeft, minLeft), maxLeft);
|
|
66
|
+
return { width: width, left: left };
|
|
67
|
+
}
|
|
68
|
+
exports.clampDropdownToViewport = clampDropdownToViewport;
|
|
69
|
+
/**
|
|
70
|
+
* Same as clampDropdownToViewport, but if the dropdown would overflow the right edge, it will prefer
|
|
71
|
+
* right-aligning the dropdown to the provided anchor (typically the input's right edge).
|
|
72
|
+
*/
|
|
73
|
+
function clampDropdownToViewportPreferRightAlign(opts) {
|
|
74
|
+
if (typeof window === 'undefined')
|
|
75
|
+
return { width: opts.desiredWidth, left: opts.desiredLeft };
|
|
76
|
+
var viewportLeft = window.scrollX;
|
|
77
|
+
var viewportRight = window.scrollX + window.innerWidth;
|
|
78
|
+
var maxWidth = Math.max(0, Math.floor(viewportRight - viewportLeft - VIEWPORT_LEFT_GUTTER_PX - VIEWPORT_RIGHT_GUTTER_PX));
|
|
79
|
+
var width = maxWidth > 0 ? Math.min(opts.desiredWidth, maxWidth) : opts.desiredWidth;
|
|
80
|
+
var minLeft = viewportLeft + VIEWPORT_LEFT_GUTTER_PX;
|
|
81
|
+
var maxLeft = viewportRight - VIEWPORT_RIGHT_GUTTER_PX - width;
|
|
82
|
+
// Default: left-align to the trigger.
|
|
83
|
+
var left = opts.desiredLeft;
|
|
84
|
+
// If it would overflow right, prefer right-align to the trigger's right edge.
|
|
85
|
+
if (left + width > viewportRight - VIEWPORT_RIGHT_GUTTER_PX) {
|
|
86
|
+
left = opts.anchorRight - width;
|
|
87
|
+
}
|
|
88
|
+
left = Math.min(Math.max(left, minLeft), maxLeft);
|
|
89
|
+
return { width: width, left: left };
|
|
90
|
+
}
|
|
91
|
+
exports.clampDropdownToViewportPreferRightAlign = clampDropdownToViewportPreferRightAlign;
|
|
92
|
+
//# sourceMappingURL=measureTabListNaturalWidth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measureTabListNaturalWidth.js","sourceRoot":"","sources":["../../src/utilities/measureTabListNaturalWidth.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -18,6 +18,12 @@ import { filterSuggestions } from './utilities/filterSuggestions';
|
|
|
18
18
|
import { AllDropArrow, DropArrow, Search, Spinner } from './utilities/icons';
|
|
19
19
|
import { default as Tooltip } from './utilities/NewTooltip';
|
|
20
20
|
import { getKeyValue, safeToLowerString } from './utilities/getKeyValue';
|
|
21
|
+
import {
|
|
22
|
+
clampDropdownToViewportPreferRightAlign,
|
|
23
|
+
MAX_DROPDOWN_WIDTH_WITH_TABS_PX,
|
|
24
|
+
MIN_DROPDOWN_WIDTH_PX,
|
|
25
|
+
measureTabListNaturalWidth,
|
|
26
|
+
} from './utilities/measureTabListNaturalWidth';
|
|
21
27
|
|
|
22
28
|
const AutoCompleteWithSelectedList = forwardRef<
|
|
23
29
|
HTMLInputElement,
|
|
@@ -119,6 +125,7 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
119
125
|
const [refetchData, setRefetchData] = useState(false);
|
|
120
126
|
const [allDataLoaded, setAllDataLoaded] = useState(false);
|
|
121
127
|
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
|
|
128
|
+
const [tabsScrollable, setTabsScrollable] = useState(false);
|
|
122
129
|
|
|
123
130
|
let originalOverflow = '';
|
|
124
131
|
let hasScrollbar = false;
|
|
@@ -131,6 +138,23 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
131
138
|
width: inputRect.width,
|
|
132
139
|
top: 0,
|
|
133
140
|
};
|
|
141
|
+
const tabsNaturalWidth = tabRef.current
|
|
142
|
+
? measureTabListNaturalWidth(tabRef.current)
|
|
143
|
+
: null;
|
|
144
|
+
const widthBeforeClamp = Math.min(
|
|
145
|
+
Math.max(dropdownPosition.width, tabsNaturalWidth ?? 0, MIN_DROPDOWN_WIDTH_PX),
|
|
146
|
+
tab.length > 0 ? MAX_DROPDOWN_WIDTH_WITH_TABS_PX : Number.POSITIVE_INFINITY
|
|
147
|
+
);
|
|
148
|
+
const { width: desiredWidth, left: desiredLeft } =
|
|
149
|
+
clampDropdownToViewportPreferRightAlign({
|
|
150
|
+
desiredWidth: widthBeforeClamp,
|
|
151
|
+
desiredLeft: dropdownPosition.left,
|
|
152
|
+
anchorRight: inputRect.right + window.scrollX,
|
|
153
|
+
});
|
|
154
|
+
setTabsScrollable((prev) => {
|
|
155
|
+
const next = tab.length > 0 && (tabsNaturalWidth ?? 0) > desiredWidth;
|
|
156
|
+
return prev === next ? prev : next;
|
|
157
|
+
});
|
|
134
158
|
// Check if there's enough space below the input for the dropdown
|
|
135
159
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
136
160
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -184,8 +208,8 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
184
208
|
setDropdownStyle((prevStyle) => ({
|
|
185
209
|
...prevStyle,
|
|
186
210
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
187
|
-
left:
|
|
188
|
-
width:
|
|
211
|
+
left: desiredLeft,
|
|
212
|
+
width: desiredWidth,
|
|
189
213
|
}));
|
|
190
214
|
}
|
|
191
215
|
};
|
|
@@ -383,6 +407,7 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
383
407
|
|
|
384
408
|
const uniqueDropArrowId = `${name}-drop-arrow-selected-list-icon`;
|
|
385
409
|
const isKeyboardOpenRef = useRef(false);
|
|
410
|
+
const closeOnScrollTimeoutRef = useRef<number | null>(null);
|
|
386
411
|
|
|
387
412
|
// Keep ref in sync with state for use in event handlers
|
|
388
413
|
useEffect(() => {
|
|
@@ -391,7 +416,13 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
391
416
|
|
|
392
417
|
useEffect(() => {
|
|
393
418
|
const handleScroll = (event: any) => {
|
|
394
|
-
//
|
|
419
|
+
// Don't close if the user is scrolling inside the dropdown itself.
|
|
420
|
+
const scrollTarget = event.target;
|
|
421
|
+
if (dropRef.current && scrollTarget instanceof Node && dropRef.current.contains(scrollTarget)) {
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// On mobile: Don't close dropdown on scroll when keyboard is open (typing scenario)
|
|
395
426
|
// On desktop: Always allow closing on scroll
|
|
396
427
|
if (isKeyboardOpenRef.current) {
|
|
397
428
|
// Only prevent closing if keyboard is actually open (mobile scenario)
|
|
@@ -406,15 +437,12 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
406
437
|
}
|
|
407
438
|
}
|
|
408
439
|
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
// On desktop (keyboard not open), close on scroll
|
|
416
|
-
if (dropRef.current && dropOpen) {
|
|
417
|
-
setTimeout(() => {
|
|
440
|
+
// Close on any outside scroll (including scrolls on nested containers).
|
|
441
|
+
if (dropOpen) {
|
|
442
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
443
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
444
|
+
}
|
|
445
|
+
closeOnScrollTimeoutRef.current = window.setTimeout(() => {
|
|
418
446
|
setDropOpen(false);
|
|
419
447
|
if (!typeOnlyFetch) setSearchValue('');
|
|
420
448
|
}, 200);
|
|
@@ -460,6 +488,8 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
460
488
|
|
|
461
489
|
document.addEventListener('mousedown', handleClickOutside as any);
|
|
462
490
|
window.addEventListener('scroll', handleScroll as any);
|
|
491
|
+
// Capture-phase scroll catches scrolls from any scroll container (scroll doesn't bubble).
|
|
492
|
+
document.addEventListener('scroll', handleScroll as any, true);
|
|
463
493
|
|
|
464
494
|
const scrollableDivs = document.querySelectorAll(
|
|
465
495
|
'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
@@ -473,9 +503,14 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
473
503
|
return () => {
|
|
474
504
|
document.removeEventListener('mousedown', handleClickOutside as any);
|
|
475
505
|
window.removeEventListener('scroll', handleScroll as any);
|
|
506
|
+
document.removeEventListener('scroll', handleScroll as any, true);
|
|
476
507
|
scrollableDivs.forEach((div) =>
|
|
477
508
|
div.removeEventListener('scroll', handleScroll as any)
|
|
478
509
|
);
|
|
510
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
511
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
512
|
+
closeOnScrollTimeoutRef.current = null;
|
|
513
|
+
}
|
|
479
514
|
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
480
515
|
scrollRef.current.removeEventListener(
|
|
481
516
|
'scroll',
|
|
@@ -774,7 +809,7 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
774
809
|
const getTabItems = () => {
|
|
775
810
|
return (
|
|
776
811
|
<ul
|
|
777
|
-
className=
|
|
812
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
778
813
|
ref={tabRef}
|
|
779
814
|
>
|
|
780
815
|
{tab.map((item: TabPops, idx: number) => (
|
|
@@ -1126,6 +1161,7 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
1126
1161
|
<input
|
|
1127
1162
|
type="checkbox"
|
|
1128
1163
|
checked={selectAll}
|
|
1164
|
+
onChange={handleSelectAll}
|
|
1129
1165
|
id={`qbs-checkbox-all`}
|
|
1130
1166
|
/>
|
|
1131
1167
|
<label htmlFor={`qbs-checkbox-all`}>
|
|
@@ -17,6 +17,12 @@ import { filterSuggestions } from './utilities/filterSuggestions';
|
|
|
17
17
|
import { AllDropArrow, DropArrow, Search, Spinner, Tools } from './utilities/icons';
|
|
18
18
|
import { default as Tooltip } from './utilities/NewTooltip';
|
|
19
19
|
import { getKeyValue, safeToLowerString } from './utilities/getKeyValue';
|
|
20
|
+
import {
|
|
21
|
+
clampDropdownToViewportPreferRightAlign,
|
|
22
|
+
MAX_DROPDOWN_WIDTH_WITH_TABS_PX,
|
|
23
|
+
MIN_DROPDOWN_WIDTH_PX,
|
|
24
|
+
measureTabListNaturalWidth,
|
|
25
|
+
} from './utilities/measureTabListNaturalWidth';
|
|
20
26
|
import DropdownFilterTabs from './dropdownFilterTabs';
|
|
21
27
|
|
|
22
28
|
const AutoCompleteWithTabFilter = forwardRef<
|
|
@@ -93,7 +99,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
93
99
|
moreOptionTab,
|
|
94
100
|
moreOptionTabLabel,
|
|
95
101
|
moreOptionTabIcon,
|
|
96
|
-
onMoreOptionChange
|
|
102
|
+
onMoreOptionChange,
|
|
103
|
+
resetAllSearch = true,
|
|
97
104
|
},
|
|
98
105
|
ref
|
|
99
106
|
) => {
|
|
@@ -139,6 +146,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
139
146
|
const [refetchData, setRefetchData] = useState(false);
|
|
140
147
|
const [allDataLoaded, setAllDataLoaded] = useState(false);
|
|
141
148
|
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
|
|
149
|
+
const [tabsScrollable, setTabsScrollable] = useState(false);
|
|
142
150
|
const toolsBtnRef = useRef<HTMLButtonElement>(null);
|
|
143
151
|
const [showToolsTab, setShowToolsTab] = useState(false);
|
|
144
152
|
// Track previous tabSelectedItems to detect new items
|
|
@@ -295,12 +303,13 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
295
303
|
setSelectedItems([]);
|
|
296
304
|
setShowAllSelected(false);
|
|
297
305
|
setShowToolsTab(false);
|
|
298
|
-
if (searchValue && type === 'auto_suggestion' && tabInlineSearch) {
|
|
306
|
+
if (searchValue && type === 'auto_suggestion' && (tabInlineSearch || resetAllSearch)) {
|
|
299
307
|
setSearchValue('');
|
|
300
308
|
}
|
|
309
|
+
console.log('handleClearSelected', searchValue, type, tabInlineSearch);
|
|
301
310
|
if (isMultiple) onChange([]);
|
|
302
311
|
else onChange({ [descId]: '', [desc]: '' });
|
|
303
|
-
if (async && type === 'auto_suggestion' && tabInlineSearch) resetSuggections?.();
|
|
312
|
+
if (async && type === 'auto_suggestion' && (tabInlineSearch || resetAllSearch)) resetSuggections?.();
|
|
304
313
|
}, [searchValue, type, tabInlineSearch, isMultiple, onChange, descId, desc, async, resetSuggections]);
|
|
305
314
|
|
|
306
315
|
const generateClassName = useCallback(() => {
|
|
@@ -315,6 +324,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
315
324
|
|
|
316
325
|
const uniqueDropArrowId = `${name}-drop-arrow-selected-list-icon`;
|
|
317
326
|
const isKeyboardOpenRef = useRef(false);
|
|
327
|
+
const closeOnScrollTimeoutRef = useRef<number | null>(null);
|
|
318
328
|
|
|
319
329
|
// Keep ref in sync with state for use in event handlers
|
|
320
330
|
useEffect(() => {
|
|
@@ -323,7 +333,13 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
323
333
|
|
|
324
334
|
useEffect(() => {
|
|
325
335
|
const handleScroll = (event: any) => {
|
|
326
|
-
//
|
|
336
|
+
// Don't close if the user is scrolling inside the dropdown itself.
|
|
337
|
+
const scrollTarget = event.target;
|
|
338
|
+
if (dropRef.current && scrollTarget instanceof Node && dropRef.current.contains(scrollTarget)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// On mobile: Don't close dropdown on scroll when keyboard is open (typing scenario)
|
|
327
343
|
// On desktop: Always allow closing on scroll
|
|
328
344
|
if (isKeyboardOpenRef.current) {
|
|
329
345
|
// Only prevent closing if keyboard is actually open (mobile scenario)
|
|
@@ -338,15 +354,12 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
338
354
|
}
|
|
339
355
|
}
|
|
340
356
|
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// On desktop (keyboard not open), close on scroll
|
|
348
|
-
if (dropRef.current && dropOpen) {
|
|
349
|
-
setTimeout(() => {
|
|
357
|
+
// Close on any outside scroll (including scrolls on nested containers).
|
|
358
|
+
if (dropOpen) {
|
|
359
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
360
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
361
|
+
}
|
|
362
|
+
closeOnScrollTimeoutRef.current = window.setTimeout(() => {
|
|
350
363
|
setDropOpen(false);
|
|
351
364
|
setShowToolsTab(false);
|
|
352
365
|
setAllDataLoaded(false);
|
|
@@ -408,6 +421,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
408
421
|
|
|
409
422
|
document.addEventListener('mousedown', handleClickOutside as any);
|
|
410
423
|
window.addEventListener('scroll', handleScroll as any);
|
|
424
|
+
// Capture-phase scroll catches scrolls from any scroll container (scroll doesn't bubble).
|
|
425
|
+
document.addEventListener('scroll', handleScroll as any, true);
|
|
411
426
|
|
|
412
427
|
const scrollableDivs = document.querySelectorAll(
|
|
413
428
|
'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
@@ -421,9 +436,14 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
421
436
|
return () => {
|
|
422
437
|
document.removeEventListener('mousedown', handleClickOutside as any);
|
|
423
438
|
window.removeEventListener('scroll', handleScroll as any);
|
|
439
|
+
document.removeEventListener('scroll', handleScroll as any, true);
|
|
424
440
|
scrollableDivs.forEach((div) =>
|
|
425
441
|
div.removeEventListener('scroll', handleScroll as any)
|
|
426
442
|
);
|
|
443
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
444
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
445
|
+
closeOnScrollTimeoutRef.current = null;
|
|
446
|
+
}
|
|
427
447
|
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
428
448
|
scrollRef.current.removeEventListener(
|
|
429
449
|
'scroll',
|
|
@@ -455,6 +475,22 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
455
475
|
width: inputRect.width,
|
|
456
476
|
top: 0,
|
|
457
477
|
};
|
|
478
|
+
const tabsNaturalWidth = tabRef.current
|
|
479
|
+
? measureTabListNaturalWidth(tabRef.current)
|
|
480
|
+
: null;
|
|
481
|
+
const widthBeforeClamp = Math.min(
|
|
482
|
+
Math.max(dropdownPosition.width, tabsNaturalWidth ?? 0, MIN_DROPDOWN_WIDTH_PX),
|
|
483
|
+
tab.length > 0 ? MAX_DROPDOWN_WIDTH_WITH_TABS_PX : Number.POSITIVE_INFINITY
|
|
484
|
+
);
|
|
485
|
+
const { width: desiredWidth, left: desiredLeft } = clampDropdownToViewportPreferRightAlign({
|
|
486
|
+
desiredWidth: widthBeforeClamp,
|
|
487
|
+
desiredLeft: dropdownPosition.left,
|
|
488
|
+
anchorRight: inputRect.right + window.scrollX,
|
|
489
|
+
});
|
|
490
|
+
setTabsScrollable((prev) => {
|
|
491
|
+
const next = tab.length > 0 && (tabsNaturalWidth ?? 0) > desiredWidth;
|
|
492
|
+
return prev === next ? prev : next;
|
|
493
|
+
});
|
|
458
494
|
// Check if there's enough space below the input for the dropdown
|
|
459
495
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
460
496
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -510,8 +546,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
510
546
|
const newStyle = {
|
|
511
547
|
...prevStyle,
|
|
512
548
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
513
|
-
left:
|
|
514
|
-
width:
|
|
549
|
+
left: desiredLeft,
|
|
550
|
+
width: desiredWidth,
|
|
515
551
|
};
|
|
516
552
|
// Only update if the style actually changed
|
|
517
553
|
if (prevStyle.transform !== newStyle.transform ||
|
|
@@ -831,7 +867,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
831
867
|
const getTabItems = useCallback(() => {
|
|
832
868
|
return (
|
|
833
869
|
<ul
|
|
834
|
-
className=
|
|
870
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
835
871
|
ref={tabRef}
|
|
836
872
|
>
|
|
837
873
|
{tab.map((item: TabPops, idx: number) => (
|
|
@@ -849,7 +885,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
849
885
|
))}
|
|
850
886
|
</ul>
|
|
851
887
|
);
|
|
852
|
-
}, [tab, activeTab, handleTabClick]);
|
|
888
|
+
}, [tab, activeTab, handleTabClick, tabsScrollable]);
|
|
853
889
|
|
|
854
890
|
const handleSelectAll = useCallback(() => {
|
|
855
891
|
if (isMultiple) {
|
|
@@ -902,7 +938,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
902
938
|
setVisible(true);
|
|
903
939
|
}
|
|
904
940
|
}
|
|
905
|
-
if (!suggestions || suggestions?.length === 0 || refetchData) {
|
|
941
|
+
if (!suggestions || suggestions?.length === 0 || refetchData || resetAllSearch) {
|
|
906
942
|
if (autoDropdown && (inputValue === '' || inputValue.trim() === '')) {
|
|
907
943
|
const activeTabVal =
|
|
908
944
|
tab.length > 0 ? tab?.[activeTab].id : undefined;
|
|
@@ -1260,6 +1296,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
1260
1296
|
<input
|
|
1261
1297
|
type="checkbox"
|
|
1262
1298
|
checked={selectAll}
|
|
1299
|
+
onChange={handleSelectAll}
|
|
1263
1300
|
id={`qbs-checkbox-all`}
|
|
1264
1301
|
/>
|
|
1265
1302
|
<label htmlFor={`qbs-checkbox-all`}>
|
package/src/commontypes.ts
CHANGED
|
@@ -104,6 +104,7 @@ export interface AutoSuggestionInputProps {
|
|
|
104
104
|
moreOptionTabLabel?: string; // label for the more options tab (default: "More Options")
|
|
105
105
|
moreOptionTabIcon?: React.ReactNode; // icon to display in the more options tab
|
|
106
106
|
onMoreOptionChange?: (values?: any) => void; // separate callback for moreOptionValues
|
|
107
|
+
resetAllSearch?: boolean; // allow to reset if tabInlineSearch is false
|
|
107
108
|
}
|
|
108
109
|
export interface IconsProps {
|
|
109
110
|
name: any;
|
|
@@ -18,6 +18,12 @@ import { filterSuggestions } from './utilities/filterSuggestions';
|
|
|
18
18
|
import { AllDropArrow, DropArrow, Search, Spinner } from './utilities/icons';
|
|
19
19
|
import { default as Tooltip } from './utilities/NewTooltip';
|
|
20
20
|
import { getKeyValue, safeToLowerString } from './utilities/getKeyValue';
|
|
21
|
+
import {
|
|
22
|
+
clampDropdownToViewportPreferRightAlign,
|
|
23
|
+
MAX_DROPDOWN_WIDTH_WITH_TABS_PX,
|
|
24
|
+
MIN_DROPDOWN_WIDTH_PX,
|
|
25
|
+
measureTabListNaturalWidth,
|
|
26
|
+
} from './utilities/measureTabListNaturalWidth';
|
|
21
27
|
|
|
22
28
|
const DropdownFilterTabs = forwardRef<
|
|
23
29
|
HTMLInputElement,
|
|
@@ -137,6 +143,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
137
143
|
const [allDataLoaded, setAllDataLoaded] = useState(false);
|
|
138
144
|
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
|
|
139
145
|
const [hasMoreOptionValues, setHasMoreOptionValues] = useState(false);
|
|
146
|
+
const [tabsScrollable, setTabsScrollable] = useState(false);
|
|
140
147
|
|
|
141
148
|
// Use external reset key if provided, otherwise default to 0
|
|
142
149
|
const currentResetKey = externalResetKey !== undefined ? externalResetKey : 0;
|
|
@@ -171,6 +178,23 @@ const DropdownFilterTabs = forwardRef<
|
|
|
171
178
|
width: inputRect.width,
|
|
172
179
|
top: 0,
|
|
173
180
|
};
|
|
181
|
+
const hasTabs = tab.length > 0 || !!moreOptionTab;
|
|
182
|
+
const tabsNaturalWidth = tabRef.current
|
|
183
|
+
? measureTabListNaturalWidth(tabRef.current)
|
|
184
|
+
: null;
|
|
185
|
+
const widthBeforeClamp = Math.min(
|
|
186
|
+
Math.max(dropdownPosition.width, tabsNaturalWidth ?? 0, MIN_DROPDOWN_WIDTH_PX),
|
|
187
|
+
hasTabs ? MAX_DROPDOWN_WIDTH_WITH_TABS_PX : Number.POSITIVE_INFINITY
|
|
188
|
+
);
|
|
189
|
+
const { width: desiredWidth, left: desiredLeft } = clampDropdownToViewportPreferRightAlign({
|
|
190
|
+
desiredWidth: widthBeforeClamp,
|
|
191
|
+
desiredLeft: dropdownPosition.left,
|
|
192
|
+
anchorRight: inputRect.right + window.scrollX,
|
|
193
|
+
});
|
|
194
|
+
setTabsScrollable((prev) => {
|
|
195
|
+
const next = hasTabs && (tabsNaturalWidth ?? 0) > desiredWidth;
|
|
196
|
+
return prev === next ? prev : next;
|
|
197
|
+
});
|
|
174
198
|
// Check if there's enough space below the input for the dropdown
|
|
175
199
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
176
200
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -224,8 +248,8 @@ const DropdownFilterTabs = forwardRef<
|
|
|
224
248
|
setDropdownStyle((prevStyle) => ({
|
|
225
249
|
...prevStyle,
|
|
226
250
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
227
|
-
left:
|
|
228
|
-
width:
|
|
251
|
+
left: desiredLeft,
|
|
252
|
+
width: desiredWidth,
|
|
229
253
|
}));
|
|
230
254
|
|
|
231
255
|
}
|
|
@@ -386,6 +410,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
386
410
|
|
|
387
411
|
const uniqueDropArrowId = `${name}-drop-arrow-selected-list-icon`;
|
|
388
412
|
const isKeyboardOpenRef = useRef(false);
|
|
413
|
+
const closeOnScrollTimeoutRef = useRef<number | null>(null);
|
|
389
414
|
|
|
390
415
|
// Keep ref in sync with state for use in event handlers
|
|
391
416
|
useEffect(() => {
|
|
@@ -412,7 +437,13 @@ const DropdownFilterTabs = forwardRef<
|
|
|
412
437
|
|
|
413
438
|
useEffect(() => {
|
|
414
439
|
const handleScroll = (event: any) => {
|
|
415
|
-
//
|
|
440
|
+
// Don't close if the user is scrolling inside the dropdown itself.
|
|
441
|
+
const scrollTarget = event.target;
|
|
442
|
+
if (dropRef.current && scrollTarget instanceof Node && dropRef.current.contains(scrollTarget)) {
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// On mobile: Don't close dropdown on scroll when keyboard is open (typing scenario)
|
|
416
447
|
// On desktop: Always allow closing on scroll
|
|
417
448
|
if (isKeyboardOpenRef.current) {
|
|
418
449
|
// Only prevent closing if keyboard is actually open (mobile scenario)
|
|
@@ -427,15 +458,12 @@ const DropdownFilterTabs = forwardRef<
|
|
|
427
458
|
}
|
|
428
459
|
}
|
|
429
460
|
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// On desktop (keyboard not open), close on scroll
|
|
437
|
-
if (dropRef.current && dropOpen) {
|
|
438
|
-
setTimeout(() => {
|
|
461
|
+
// Close on any outside scroll (including scrolls on nested containers).
|
|
462
|
+
if (dropOpen) {
|
|
463
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
464
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
465
|
+
}
|
|
466
|
+
closeOnScrollTimeoutRef.current = window.setTimeout(() => {
|
|
439
467
|
setDropOpen(false);
|
|
440
468
|
onToolClose?.();
|
|
441
469
|
if (!typeOnlyFetch) setSearchValue('');
|
|
@@ -485,6 +513,8 @@ const DropdownFilterTabs = forwardRef<
|
|
|
485
513
|
|
|
486
514
|
document.addEventListener('mousedown', handleClickOutside as any);
|
|
487
515
|
window.addEventListener('scroll', handleScroll as any);
|
|
516
|
+
// Capture-phase scroll catches scrolls from any scroll container (scroll doesn't bubble).
|
|
517
|
+
document.addEventListener('scroll', handleScroll as any, true);
|
|
488
518
|
|
|
489
519
|
const scrollableDivs = document.querySelectorAll(
|
|
490
520
|
'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
@@ -498,9 +528,14 @@ const DropdownFilterTabs = forwardRef<
|
|
|
498
528
|
return () => {
|
|
499
529
|
document.removeEventListener('mousedown', handleClickOutside as any);
|
|
500
530
|
window.removeEventListener('scroll', handleScroll as any);
|
|
531
|
+
document.removeEventListener('scroll', handleScroll as any, true);
|
|
501
532
|
scrollableDivs.forEach((div) =>
|
|
502
533
|
div.removeEventListener('scroll', handleScroll as any)
|
|
503
534
|
);
|
|
535
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
536
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
537
|
+
closeOnScrollTimeoutRef.current = null;
|
|
538
|
+
}
|
|
504
539
|
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
505
540
|
scrollRef.current.removeEventListener(
|
|
506
541
|
'scroll',
|
|
@@ -758,7 +793,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
758
793
|
const allTabs = moreOptionTab ? [...tab, { id: 'more-options', label: moreOptionTabLabel }] : tab;
|
|
759
794
|
return (
|
|
760
795
|
<ul
|
|
761
|
-
className=
|
|
796
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
762
797
|
ref={tabRef}
|
|
763
798
|
>
|
|
764
799
|
{allTabs.map((item: TabPops, idx: number) => {
|
|
@@ -794,7 +829,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
794
829
|
})}
|
|
795
830
|
</ul>
|
|
796
831
|
);
|
|
797
|
-
}, [tab, hasSelectedItemsInTab, activeTab, handleTabClick, moreOptionTab, moreOptionTabLabel, moreOptionTabIcon]);
|
|
832
|
+
}, [tab, hasSelectedItemsInTab, activeTab, handleTabClick, moreOptionTab, moreOptionTabLabel, moreOptionTabIcon, tabsScrollable]);
|
|
798
833
|
|
|
799
834
|
const handleSelectAll = useCallback(() => {
|
|
800
835
|
if (isMultiple) {
|
|
@@ -866,6 +901,8 @@ const DropdownFilterTabs = forwardRef<
|
|
|
866
901
|
|
|
867
902
|
const handleApplySelected = (values?: any) => {
|
|
868
903
|
setDropOpen(false);
|
|
904
|
+
setSearchValue('');
|
|
905
|
+
resetSuggections?.();
|
|
869
906
|
applyTabFilter?.(values);
|
|
870
907
|
};
|
|
871
908
|
|
|
@@ -996,6 +1033,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
996
1033
|
<input
|
|
997
1034
|
type="checkbox"
|
|
998
1035
|
checked={selectAll}
|
|
1036
|
+
onChange={handleSelectAll}
|
|
999
1037
|
id={`qbs-checkbox-all`}
|
|
1000
1038
|
/>
|
|
1001
1039
|
<label htmlFor={`qbs-checkbox-all`}>
|
|
@@ -37,6 +37,15 @@
|
|
|
37
37
|
.qbs-tab-item {
|
|
38
38
|
color: #4b5563;
|
|
39
39
|
border-color: #d1d5db;
|
|
40
|
+
/* Prevent mid-word breaking from any inherited/global rules */
|
|
41
|
+
word-break: normal;
|
|
42
|
+
overflow-wrap: normal;
|
|
43
|
+
hyphens: manual;
|
|
44
|
+
/* Prevent breaking at spaces too (single-line label) */
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
/* In non-scroll layout, keep text tidy if it's forced into a narrow cell */
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
text-overflow: ellipsis;
|
|
40
49
|
}
|
|
41
50
|
.qbs-tab-item:hover {
|
|
42
51
|
color: #2563eb;
|
|
@@ -45,6 +54,36 @@
|
|
|
45
54
|
color: #2563eb;
|
|
46
55
|
border-color: #2563eb;
|
|
47
56
|
}
|
|
57
|
+
|
|
58
|
+
/* When tabs overflow, keep them in a single row and allow horizontal scroll */
|
|
59
|
+
.qbs-tab.qbs-tab-scroll {
|
|
60
|
+
flex-wrap: nowrap !important; /* override .qbs-flex-wrap */
|
|
61
|
+
overflow-x: auto;
|
|
62
|
+
overflow-y: hidden;
|
|
63
|
+
-webkit-overflow-scrolling: touch;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.qbs-tab.qbs-tab-scroll .qbs-tab-items {
|
|
67
|
+
flex: 0 0 auto !important; /* override .qbs-flex-1 */
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.qbs-tab.qbs-tab-scroll .qbs-tab-item {
|
|
71
|
+
width: max-content !important; /* override .qbs-w-full */
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
word-break: normal;
|
|
74
|
+
overflow-wrap: normal;
|
|
75
|
+
hyphens: manual;
|
|
76
|
+
overflow: visible;
|
|
77
|
+
text-overflow: clip;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* If the tab label has nested spans (icons + text), ensure they inherit the non-breaking behavior */
|
|
81
|
+
.qbs-tab-item * {
|
|
82
|
+
word-break: inherit;
|
|
83
|
+
overflow-wrap: inherit;
|
|
84
|
+
hyphens: inherit;
|
|
85
|
+
white-space: inherit;
|
|
86
|
+
}
|
|
48
87
|
.qbs-relative {
|
|
49
88
|
position: relative;
|
|
50
89
|
}
|