react-core-ts 2.0.21 → 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 +47 -21
- package/dist/AutoCompleteWithSelectedList.js.map +1 -1
- package/dist/AutoCompleteWithTabFilter.js +51 -25
- package/dist/AutoCompleteWithTabFilter.js.map +1 -1
- package/dist/dropdownFilterTabs.js +51 -22
- 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 +48 -13
- package/src/AutoCompleteWithTabFilter.tsx +48 -14
- package/src/dropdownFilterTabs.tsx +51 -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,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,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) => (
|
|
@@ -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,22 @@ 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 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
|
+
});
|
|
460
494
|
// Check if there's enough space below the input for the dropdown
|
|
461
495
|
const spaceBelow = window.innerHeight - inputRect.bottom;
|
|
462
496
|
const spaceAbove = inputRect.top + window.scrollY;
|
|
@@ -512,8 +546,8 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
512
546
|
const newStyle = {
|
|
513
547
|
...prevStyle,
|
|
514
548
|
transform: `translateY(${dropdownPosition.top}px)`,
|
|
515
|
-
left:
|
|
516
|
-
width:
|
|
549
|
+
left: desiredLeft,
|
|
550
|
+
width: desiredWidth,
|
|
517
551
|
};
|
|
518
552
|
// Only update if the style actually changed
|
|
519
553
|
if (prevStyle.transform !== newStyle.transform ||
|
|
@@ -833,7 +867,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
833
867
|
const getTabItems = useCallback(() => {
|
|
834
868
|
return (
|
|
835
869
|
<ul
|
|
836
|
-
className=
|
|
870
|
+
className={`qbs-flex qbs-flex-wrap qbs-w-full qbs-tab qbs-mb-2 -qbs-mt-2 ${tabsScrollable ? 'qbs-tab-scroll' : ''}`}
|
|
837
871
|
ref={tabRef}
|
|
838
872
|
>
|
|
839
873
|
{tab.map((item: TabPops, idx: number) => (
|
|
@@ -851,7 +885,7 @@ const AutoCompleteWithTabFilter = forwardRef<
|
|
|
851
885
|
))}
|
|
852
886
|
</ul>
|
|
853
887
|
);
|
|
854
|
-
}, [tab, activeTab, handleTabClick]);
|
|
888
|
+
}, [tab, activeTab, handleTabClick, tabsScrollable]);
|
|
855
889
|
|
|
856
890
|
const handleSelectAll = useCallback(() => {
|
|
857
891
|
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,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
|
|
|
@@ -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
|
}
|