kelt-ui-kit-react 2.1.2 → 2.1.3

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kelt-ui-kit-react",
3
3
  "type": "module",
4
- "version": "2.1.2",
4
+ "version": "2.1.3",
5
5
  "private": false,
6
6
  "repository": {
7
7
  "type": "git",
@@ -5,6 +5,9 @@ import { OverlayPanel, PositionOverlay } from "../overlayPanel/OverlayPanel";
5
5
  import "./select.css";
6
6
  import { SelectOptionInterface } from "./selectOption.interface";
7
7
 
8
+ // Nombre d'options rendues à l'ouverture puis à chaque fin de scroll
9
+ const OPTIONS_PAGE_SIZE = 50;
10
+
8
11
  export interface SelectProps {
9
12
  id?: string;
10
13
  label?: string;
@@ -55,6 +58,7 @@ export const Select = ({
55
58
  const hasAutoOpened = useRef(false);
56
59
  const optionsListRef = useRef<HTMLDivElement | null>(null);
57
60
  const [search, setSearch] = useState("");
61
+ const [visibleCount, setVisibleCount] = useState(OPTIONS_PAGE_SIZE);
58
62
 
59
63
  useEffect(() => {
60
64
  if (!showOverlay) setSearch("");
@@ -68,6 +72,11 @@ export const Select = ({
68
72
  return options.filter((option) => normalize(option.label).includes(term));
69
73
  }, [options, search, searchable]);
70
74
 
75
+ const optionsByValue = useMemo(
76
+ () => new Map(options.map((option) => [option.value, option])),
77
+ [options],
78
+ );
79
+
71
80
  useEffect(() => {
72
81
  if (!showOverlay) return;
73
82
  const timer = setTimeout(() => {
@@ -95,15 +104,38 @@ export const Select = ({
95
104
  : [];
96
105
  const hasSelection = selectedValues.some((v) => v !== "");
97
106
 
107
+ // Rendu progressif : on repart d'une page à chaque ouverture ou recherche,
108
+ // en allant au moins jusqu'à l'option sélectionnée en sélection simple
109
+ useEffect(() => {
110
+ if (!showOverlay) return;
111
+ const selectedIndex = multiple
112
+ ? -1
113
+ : filteredOptions.findIndex((option) => option.value === selectedValues[0]);
114
+ setVisibleCount(Math.max(OPTIONS_PAGE_SIZE, selectedIndex + 1));
115
+ optionsListRef.current?.scrollTo({ top: 0 });
116
+ }, [showOverlay, search]);
117
+
118
+ const visibleOptions = filteredOptions.slice(0, visibleCount);
119
+
120
+ const handleOptionsScroll = (e: React.UIEvent<HTMLDivElement>) => {
121
+ const { scrollTop, scrollHeight, clientHeight } = e.currentTarget;
122
+ if (
123
+ visibleCount < filteredOptions.length &&
124
+ scrollHeight - scrollTop - clientHeight < 100
125
+ ) {
126
+ setVisibleCount((count) => count + OPTIONS_PAGE_SIZE);
127
+ }
128
+ };
129
+
98
130
  const getDisplayValue = () => {
99
131
  if (selectedValues.length === 0) return placeholder || "";
100
132
  if (multiple) {
101
133
  const labels = selectedValues
102
- .map((v) => options.find((opt) => opt.value === v)?.label)
134
+ .map((v) => optionsByValue.get(v)?.label)
103
135
  .filter((l): l is string => !!l);
104
136
  return labels.join(", ");
105
137
  }
106
- const selected = options.find((opt) => opt.value === selectedValues[0]);
138
+ const selected = optionsByValue.get(selectedValues[0]);
107
139
  return selected?.label || "";
108
140
  };
109
141
 
@@ -200,11 +232,15 @@ export const Select = ({
200
232
  />
201
233
  </div>
202
234
  )}
203
- <div className="select-overlay-options" ref={optionsListRef}>
235
+ <div
236
+ className="select-overlay-options"
237
+ ref={optionsListRef}
238
+ onScroll={handleOptionsScroll}
239
+ >
204
240
  {searchable && filteredOptions.length === 0 && (
205
241
  <div className="select-overlay-empty">Aucun résultat</div>
206
242
  )}
207
- {filteredOptions.map((option) => (
243
+ {visibleOptions.map((option) => (
208
244
  <div
209
245
  key={option.value}
210
246
  aria-valuetext={option.label}