react-core-ts 2.0.21 → 2.0.23
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 +50 -21
- package/dist/AutoCompleteWithSelectedList.js.map +1 -1
- package/dist/AutoCompleteWithTabFilter.js +54 -25
- package/dist/AutoCompleteWithTabFilter.js.map +1 -1
- package/dist/dropdownFilterTabs.js +54 -22
- package/dist/dropdownFilterTabs.js.map +1 -1
- package/dist/styles/components/selectedautocomplete.css +42 -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 +51 -13
- package/src/AutoCompleteWithTabFilter.tsx +51 -14
- package/src/dropdownFilterTabs.tsx +54 -14
- package/src/styles/components/selectedautocomplete.css +42 -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,30 @@
|
|
|
1
|
+
export declare const MIN_DROPDOWN_WIDTH_PX = 300;
|
|
2
|
+
export declare const MAX_DROPDOWN_WIDTH_WITH_TABS_PX = 600;
|
|
3
|
+
/**
|
|
4
|
+
* Measures the "natural" (nowrap) width of a rendered tabs <ul>, including the dropdown's horizontal padding.
|
|
5
|
+
* This avoids tabs wrapping/squishing when the trigger/input is narrower than the tab labels.
|
|
6
|
+
*/
|
|
7
|
+
export declare function measureTabListNaturalWidth(tabListEl: HTMLUListElement): number | null;
|
|
8
|
+
/**
|
|
9
|
+
* Keeps a dropdown fully visible within the viewport by clamping width and shifting left/right as needed.
|
|
10
|
+
* All coordinates are page coordinates (i.e., include window.scrollX).
|
|
11
|
+
*/
|
|
12
|
+
export declare function clampDropdownToViewport(opts: {
|
|
13
|
+
desiredWidth: number;
|
|
14
|
+
desiredLeft: number;
|
|
15
|
+
}): {
|
|
16
|
+
width: number;
|
|
17
|
+
left: number;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Same as clampDropdownToViewport, but if the dropdown would overflow the right edge, it will prefer
|
|
21
|
+
* right-aligning the dropdown to the provided anchor (typically the input's right edge).
|
|
22
|
+
*/
|
|
23
|
+
export declare function clampDropdownToViewportPreferRightAlign(opts: {
|
|
24
|
+
desiredWidth: number;
|
|
25
|
+
desiredLeft: number;
|
|
26
|
+
anchorRight: number;
|
|
27
|
+
}): {
|
|
28
|
+
width: number;
|
|
29
|
+
left: number;
|
|
30
|
+
};
|
|
@@ -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,26 @@ 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 maxWidthCap = tab.length > 0
|
|
145
|
+
? Math.max(MAX_DROPDOWN_WIDTH_WITH_TABS_PX, dropdownPosition.width)
|
|
146
|
+
: Number.POSITIVE_INFINITY;
|
|
147
|
+
const widthBeforeClamp = Math.min(
|
|
148
|
+
Math.max(dropdownPosition.width, tabsNaturalWidth ?? 0, MIN_DROPDOWN_WIDTH_PX),
|
|
149
|
+
maxWidthCap
|
|
150
|
+
);
|
|
151
|
+
const { width: desiredWidth, left: desiredLeft } =
|
|
152
|
+
clampDropdownToViewportPreferRightAlign({
|
|
153
|
+
desiredWidth: widthBeforeClamp,
|
|
154
|
+
desiredLeft: dropdownPosition.left,
|
|
155
|
+
anchorRight: inputRect.right + window.scrollX,
|
|
156
|
+
});
|
|
157
|
+
setTabsScrollable((prev) => {
|
|
158
|
+
const next = tab.length > 0 && (tabsNaturalWidth ?? 0) > desiredWidth;
|
|
159
|
+
return prev === next ? prev : next;
|
|
160
|
+
});
|
|
134
161
|
// Check if there's enough space below the input for the dropdown
|
|
135
162
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
136
163
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -184,8 +211,8 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
184
211
|
setDropdownStyle((prevStyle) => ({
|
|
185
212
|
...prevStyle,
|
|
186
213
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
187
|
-
left:
|
|
188
|
-
width:
|
|
214
|
+
left: desiredLeft,
|
|
215
|
+
width: desiredWidth,
|
|
189
216
|
}));
|
|
190
217
|
}
|
|
191
218
|
};
|
|
@@ -383,6 +410,7 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
383
410
|
|
|
384
411
|
const uniqueDropArrowId = `${name}-drop-arrow-selected-list-icon`;
|
|
385
412
|
const isKeyboardOpenRef = useRef(false);
|
|
413
|
+
const closeOnScrollTimeoutRef = useRef<number | null>(null);
|
|
386
414
|
|
|
387
415
|
// Keep ref in sync with state for use in event handlers
|
|
388
416
|
useEffect(() => {
|
|
@@ -391,7 +419,13 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
391
419
|
|
|
392
420
|
useEffect(() => {
|
|
393
421
|
const handleScroll = (event: any) => {
|
|
394
|
-
//
|
|
422
|
+
// Don't close if the user is scrolling inside the dropdown itself.
|
|
423
|
+
const scrollTarget = event.target;
|
|
424
|
+
if (dropRef.current && scrollTarget instanceof Node && dropRef.current.contains(scrollTarget)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// On mobile: Don't close dropdown on scroll when keyboard is open (typing scenario)
|
|
395
429
|
// On desktop: Always allow closing on scroll
|
|
396
430
|
if (isKeyboardOpenRef.current) {
|
|
397
431
|
// Only prevent closing if keyboard is actually open (mobile scenario)
|
|
@@ -406,15 +440,12 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
406
440
|
}
|
|
407
441
|
}
|
|
408
442
|
|
|
409
|
-
//
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
// On desktop (keyboard not open), close on scroll
|
|
416
|
-
if (dropRef.current && dropOpen) {
|
|
417
|
-
setTimeout(() => {
|
|
443
|
+
// Close on any outside scroll (including scrolls on nested containers).
|
|
444
|
+
if (dropOpen) {
|
|
445
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
446
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
447
|
+
}
|
|
448
|
+
closeOnScrollTimeoutRef.current = window.setTimeout(() => {
|
|
418
449
|
setDropOpen(false);
|
|
419
450
|
if (!typeOnlyFetch) setSearchValue('');
|
|
420
451
|
}, 200);
|
|
@@ -460,6 +491,8 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
460
491
|
|
|
461
492
|
document.addEventListener('mousedown', handleClickOutside as any);
|
|
462
493
|
window.addEventListener('scroll', handleScroll as any);
|
|
494
|
+
// Capture-phase scroll catches scrolls from any scroll container (scroll doesn't bubble).
|
|
495
|
+
document.addEventListener('scroll', handleScroll as any, true);
|
|
463
496
|
|
|
464
497
|
const scrollableDivs = document.querySelectorAll(
|
|
465
498
|
'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
@@ -473,9 +506,14 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
473
506
|
return () => {
|
|
474
507
|
document.removeEventListener('mousedown', handleClickOutside as any);
|
|
475
508
|
window.removeEventListener('scroll', handleScroll as any);
|
|
509
|
+
document.removeEventListener('scroll', handleScroll as any, true);
|
|
476
510
|
scrollableDivs.forEach((div) =>
|
|
477
511
|
div.removeEventListener('scroll', handleScroll as any)
|
|
478
512
|
);
|
|
513
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
514
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
515
|
+
closeOnScrollTimeoutRef.current = null;
|
|
516
|
+
}
|
|
479
517
|
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
480
518
|
scrollRef.current.removeEventListener(
|
|
481
519
|
'scroll',
|
|
@@ -774,7 +812,7 @@ const AutoCompleteWithSelectedList = forwardRef<
|
|
|
774
812
|
const getTabItems = () => {
|
|
775
813
|
return (
|
|
776
814
|
<ul
|
|
777
|
-
className=
|
|
815
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
778
816
|
ref={tabRef}
|
|
779
817
|
>
|
|
780
818
|
{tab.map((item: TabPops, idx: number) => (
|
|
@@ -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<
|
|
@@ -140,6 +146,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
140
146
|
const [refetchData, setRefetchData] = useState(false);
|
|
141
147
|
const [allDataLoaded, setAllDataLoaded] = useState(false);
|
|
142
148
|
const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
|
|
149
|
+
const [tabsScrollable, setTabsScrollable] = useState(false);
|
|
143
150
|
const toolsBtnRef = useRef<HTMLButtonElement>(null);
|
|
144
151
|
const [showToolsTab, setShowToolsTab] = useState(false);
|
|
145
152
|
// Track previous tabSelectedItems to detect new items
|
|
@@ -317,6 +324,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
317
324
|
|
|
318
325
|
const uniqueDropArrowId = `${name}-drop-arrow-selected-list-icon`;
|
|
319
326
|
const isKeyboardOpenRef = useRef(false);
|
|
327
|
+
const closeOnScrollTimeoutRef = useRef<number | null>(null);
|
|
320
328
|
|
|
321
329
|
// Keep ref in sync with state for use in event handlers
|
|
322
330
|
useEffect(() => {
|
|
@@ -325,7 +333,13 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
325
333
|
|
|
326
334
|
useEffect(() => {
|
|
327
335
|
const handleScroll = (event: any) => {
|
|
328
|
-
//
|
|
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)
|
|
329
343
|
// On desktop: Always allow closing on scroll
|
|
330
344
|
if (isKeyboardOpenRef.current) {
|
|
331
345
|
// Only prevent closing if keyboard is actually open (mobile scenario)
|
|
@@ -340,15 +354,12 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
340
354
|
}
|
|
341
355
|
}
|
|
342
356
|
|
|
343
|
-
//
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// On desktop (keyboard not open), close on scroll
|
|
350
|
-
if (dropRef.current && dropOpen) {
|
|
351
|
-
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(() => {
|
|
352
363
|
setDropOpen(false);
|
|
353
364
|
setShowToolsTab(false);
|
|
354
365
|
setAllDataLoaded(false);
|
|
@@ -410,6 +421,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
410
421
|
|
|
411
422
|
document.addEventListener('mousedown', handleClickOutside as any);
|
|
412
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);
|
|
413
426
|
|
|
414
427
|
const scrollableDivs = document.querySelectorAll(
|
|
415
428
|
'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
@@ -423,9 +436,14 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
423
436
|
return () => {
|
|
424
437
|
document.removeEventListener('mousedown', handleClickOutside as any);
|
|
425
438
|
window.removeEventListener('scroll', handleScroll as any);
|
|
439
|
+
document.removeEventListener('scroll', handleScroll as any, true);
|
|
426
440
|
scrollableDivs.forEach((div) =>
|
|
427
441
|
div.removeEventListener('scroll', handleScroll as any)
|
|
428
442
|
);
|
|
443
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
444
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
445
|
+
closeOnScrollTimeoutRef.current = null;
|
|
446
|
+
}
|
|
429
447
|
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
430
448
|
scrollRef.current.removeEventListener(
|
|
431
449
|
'scroll',
|
|
@@ -457,6 +475,25 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
457
475
|
width: inputRect.width,
|
|
458
476
|
top: 0,
|
|
459
477
|
};
|
|
478
|
+
const tabsNaturalWidth = tabRef.current
|
|
479
|
+
? measureTabListNaturalWidth(tabRef.current)
|
|
480
|
+
: null;
|
|
481
|
+
const maxWidthCap = tab.length > 0
|
|
482
|
+
? Math.max(MAX_DROPDOWN_WIDTH_WITH_TABS_PX, dropdownPosition.width)
|
|
483
|
+
: Number.POSITIVE_INFINITY;
|
|
484
|
+
const widthBeforeClamp = Math.min(
|
|
485
|
+
Math.max(dropdownPosition.width, tabsNaturalWidth ?? 0, MIN_DROPDOWN_WIDTH_PX),
|
|
486
|
+
maxWidthCap
|
|
487
|
+
);
|
|
488
|
+
const { width: desiredWidth, left: desiredLeft } = clampDropdownToViewportPreferRightAlign({
|
|
489
|
+
desiredWidth: widthBeforeClamp,
|
|
490
|
+
desiredLeft: dropdownPosition.left,
|
|
491
|
+
anchorRight: inputRect.right + window.scrollX,
|
|
492
|
+
});
|
|
493
|
+
setTabsScrollable((prev) => {
|
|
494
|
+
const next = tab.length > 0 && (tabsNaturalWidth ?? 0) > desiredWidth;
|
|
495
|
+
return prev === next ? prev : next;
|
|
496
|
+
});
|
|
460
497
|
// Check if there's enough space below the input for the dropdown
|
|
461
498
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
462
499
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -512,8 +549,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
512
549
|
const newStyle = {
|
|
513
550
|
...prevStyle,
|
|
514
551
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
515
|
-
left:
|
|
516
|
-
width:
|
|
552
|
+
left: desiredLeft,
|
|
553
|
+
width: desiredWidth,
|
|
517
554
|
};
|
|
518
555
|
// Only update if the style actually changed
|
|
519
556
|
if (prevStyle.transform !== newStyle.transform ||
|
|
@@ -833,7 +870,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
833
870
|
const getTabItems = useCallback(() => {
|
|
834
871
|
return (
|
|
835
872
|
<ul
|
|
836
|
-
className=
|
|
873
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
837
874
|
ref={tabRef}
|
|
838
875
|
>
|
|
839
876
|
{tab.map((item: TabPops, idx: number) => (
|
|
@@ -851,7 +888,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
851
888
|
))}
|
|
852
889
|
</ul>
|
|
853
890
|
);
|
|
854
|
-
}, [tab, activeTab, handleTabClick]);
|
|
891
|
+
}, [tab, activeTab, handleTabClick, tabsScrollable]);
|
|
855
892
|
|
|
856
893
|
const handleSelectAll = useCallback(() => {
|
|
857
894
|
if (isMultiple) {
|
|
@@ -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,26 @@ 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 maxWidthCap = hasTabs
|
|
186
|
+
? Math.max(MAX_DROPDOWN_WIDTH_WITH_TABS_PX, dropdownPosition.width)
|
|
187
|
+
: Number.POSITIVE_INFINITY;
|
|
188
|
+
const widthBeforeClamp = Math.min(
|
|
189
|
+
Math.max(dropdownPosition.width, tabsNaturalWidth ?? 0, MIN_DROPDOWN_WIDTH_PX),
|
|
190
|
+
maxWidthCap
|
|
191
|
+
);
|
|
192
|
+
const { width: desiredWidth, left: desiredLeft } = clampDropdownToViewportPreferRightAlign({
|
|
193
|
+
desiredWidth: widthBeforeClamp,
|
|
194
|
+
desiredLeft: dropdownPosition.left,
|
|
195
|
+
anchorRight: inputRect.right + window.scrollX,
|
|
196
|
+
});
|
|
197
|
+
setTabsScrollable((prev) => {
|
|
198
|
+
const next = hasTabs && (tabsNaturalWidth ?? 0) > desiredWidth;
|
|
199
|
+
return prev === next ? prev : next;
|
|
200
|
+
});
|
|
174
201
|
// Check if there's enough space below the input for the dropdown
|
|
175
202
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
176
203
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -224,8 +251,8 @@ const DropdownFilterTabs = forwardRef<
|
|
|
224
251
|
setDropdownStyle((prevStyle) => ({
|
|
225
252
|
...prevStyle,
|
|
226
253
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
227
|
-
left:
|
|
228
|
-
width:
|
|
254
|
+
left: desiredLeft,
|
|
255
|
+
width: desiredWidth,
|
|
229
256
|
}));
|
|
230
257
|
|
|
231
258
|
}
|
|
@@ -386,6 +413,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
386
413
|
|
|
387
414
|
const uniqueDropArrowId = `${name}-drop-arrow-selected-list-icon`;
|
|
388
415
|
const isKeyboardOpenRef = useRef(false);
|
|
416
|
+
const closeOnScrollTimeoutRef = useRef<number | null>(null);
|
|
389
417
|
|
|
390
418
|
// Keep ref in sync with state for use in event handlers
|
|
391
419
|
useEffect(() => {
|
|
@@ -412,7 +440,13 @@ const DropdownFilterTabs = forwardRef<
|
|
|
412
440
|
|
|
413
441
|
useEffect(() => {
|
|
414
442
|
const handleScroll = (event: any) => {
|
|
415
|
-
//
|
|
443
|
+
// Don't close if the user is scrolling inside the dropdown itself.
|
|
444
|
+
const scrollTarget = event.target;
|
|
445
|
+
if (dropRef.current && scrollTarget instanceof Node && dropRef.current.contains(scrollTarget)) {
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// On mobile: Don't close dropdown on scroll when keyboard is open (typing scenario)
|
|
416
450
|
// On desktop: Always allow closing on scroll
|
|
417
451
|
if (isKeyboardOpenRef.current) {
|
|
418
452
|
// Only prevent closing if keyboard is actually open (mobile scenario)
|
|
@@ -427,15 +461,12 @@ const DropdownFilterTabs = forwardRef<
|
|
|
427
461
|
}
|
|
428
462
|
}
|
|
429
463
|
|
|
430
|
-
//
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
// On desktop (keyboard not open), close on scroll
|
|
437
|
-
if (dropRef.current && dropOpen) {
|
|
438
|
-
setTimeout(() => {
|
|
464
|
+
// Close on any outside scroll (including scrolls on nested containers).
|
|
465
|
+
if (dropOpen) {
|
|
466
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
467
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
468
|
+
}
|
|
469
|
+
closeOnScrollTimeoutRef.current = window.setTimeout(() => {
|
|
439
470
|
setDropOpen(false);
|
|
440
471
|
onToolClose?.();
|
|
441
472
|
if (!typeOnlyFetch) setSearchValue('');
|
|
@@ -485,6 +516,8 @@ const DropdownFilterTabs = forwardRef<
|
|
|
485
516
|
|
|
486
517
|
document.addEventListener('mousedown', handleClickOutside as any);
|
|
487
518
|
window.addEventListener('scroll', handleScroll as any);
|
|
519
|
+
// Capture-phase scroll catches scrolls from any scroll container (scroll doesn't bubble).
|
|
520
|
+
document.addEventListener('scroll', handleScroll as any, true);
|
|
488
521
|
|
|
489
522
|
const scrollableDivs = document.querySelectorAll(
|
|
490
523
|
'div[style*="overflow"], .overflow-auto, .overflow-y-auto, .overflow-x-auto'
|
|
@@ -498,9 +531,14 @@ const DropdownFilterTabs = forwardRef<
|
|
|
498
531
|
return () => {
|
|
499
532
|
document.removeEventListener('mousedown', handleClickOutside as any);
|
|
500
533
|
window.removeEventListener('scroll', handleScroll as any);
|
|
534
|
+
document.removeEventListener('scroll', handleScroll as any, true);
|
|
501
535
|
scrollableDivs.forEach((div) =>
|
|
502
536
|
div.removeEventListener('scroll', handleScroll as any)
|
|
503
537
|
);
|
|
538
|
+
if (closeOnScrollTimeoutRef.current) {
|
|
539
|
+
window.clearTimeout(closeOnScrollTimeoutRef.current);
|
|
540
|
+
closeOnScrollTimeoutRef.current = null;
|
|
541
|
+
}
|
|
504
542
|
if (scrollRef && scrollRef.current && scrollRef.current !== null) {
|
|
505
543
|
scrollRef.current.removeEventListener(
|
|
506
544
|
'scroll',
|
|
@@ -758,7 +796,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
758
796
|
const allTabs = moreOptionTab ? [...tab, { id: 'more-options', label: moreOptionTabLabel }] : tab;
|
|
759
797
|
return (
|
|
760
798
|
<ul
|
|
761
|
-
className=
|
|
799
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
762
800
|
ref={tabRef}
|
|
763
801
|
>
|
|
764
802
|
{allTabs.map((item: TabPops, idx: number) => {
|
|
@@ -794,7 +832,7 @@ const DropdownFilterTabs = forwardRef<
|
|
|
794
832
|
})}
|
|
795
833
|
</ul>
|
|
796
834
|
);
|
|
797
|
-
}, [tab, hasSelectedItemsInTab, activeTab, handleTabClick, moreOptionTab, moreOptionTabLabel, moreOptionTabIcon]);
|
|
835
|
+
}, [tab, hasSelectedItemsInTab, activeTab, handleTabClick, moreOptionTab, moreOptionTabLabel, moreOptionTabIcon, tabsScrollable]);
|
|
798
836
|
|
|
799
837
|
const handleSelectAll = useCallback(() => {
|
|
800
838
|
if (isMultiple) {
|
|
@@ -866,6 +904,8 @@ const DropdownFilterTabs = forwardRef<
|
|
|
866
904
|
|
|
867
905
|
const handleApplySelected = (values?: any) => {
|
|
868
906
|
setDropOpen(false);
|
|
907
|
+
setSearchValue('');
|
|
908
|
+
resetSuggections?.();
|
|
869
909
|
applyTabFilter?.(values);
|
|
870
910
|
};
|
|
871
911
|
|
|
@@ -37,6 +37,18 @@
|
|
|
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;
|
|
49
|
+
/* Minimum left/right padding inside each tab */
|
|
50
|
+
padding-left: 8px;
|
|
51
|
+
padding-right: 8px;
|
|
40
52
|
}
|
|
41
53
|
.qbs-tab-item:hover {
|
|
42
54
|
color: #2563eb;
|
|
@@ -45,6 +57,36 @@
|
|
|
45
57
|
color: #2563eb;
|
|
46
58
|
border-color: #2563eb;
|
|
47
59
|
}
|
|
60
|
+
|
|
61
|
+
/* When tabs overflow, keep them in a single row and allow horizontal scroll */
|
|
62
|
+
.qbs-tab.qbs-tab-scroll {
|
|
63
|
+
flex-wrap: nowrap !important; /* override .qbs-flex-wrap */
|
|
64
|
+
overflow-x: auto;
|
|
65
|
+
overflow-y: hidden;
|
|
66
|
+
-webkit-overflow-scrolling: touch;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.qbs-tab.qbs-tab-scroll .qbs-tab-items {
|
|
70
|
+
flex: 0 0 auto !important; /* override .qbs-flex-1 */
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.qbs-tab.qbs-tab-scroll .qbs-tab-item {
|
|
74
|
+
width: max-content !important; /* override .qbs-w-full */
|
|
75
|
+
white-space: nowrap;
|
|
76
|
+
word-break: normal;
|
|
77
|
+
overflow-wrap: normal;
|
|
78
|
+
hyphens: manual;
|
|
79
|
+
overflow: visible;
|
|
80
|
+
text-overflow: clip;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* If the tab label has nested spans (icons + text), ensure they inherit the non-breaking behavior */
|
|
84
|
+
.qbs-tab-item * {
|
|
85
|
+
word-break: inherit;
|
|
86
|
+
overflow-wrap: inherit;
|
|
87
|
+
hyphens: inherit;
|
|
88
|
+
white-space: inherit;
|
|
89
|
+
}
|
|
48
90
|
.qbs-relative {
|
|
49
91
|
position: relative;
|
|
50
92
|
}
|