@gp-grid/react 0.7.2

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/index.js ADDED
@@ -0,0 +1,1662 @@
1
+ import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
2
+ import { GridCore, GridCore as GridCore$1, buildCellClasses, calculateScaledColumnPositions, createClientDataSource, createClientDataSource as createClientDataSource$1, createDataSourceFromArray, createDataSourceFromArray as createDataSourceFromArray$1, createMutableClientDataSource, createServerDataSource, getTotalWidth, injectStyles, isCellActive, isCellEditing, isCellInFillPreview, isCellSelected } from "gp-grid-core";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+
5
+ //#region src/components/TextFilterContent.tsx
6
+ const MAX_VALUES_FOR_LIST = 100;
7
+ const OPERATORS$2 = [
8
+ {
9
+ value: "contains",
10
+ label: "Contains"
11
+ },
12
+ {
13
+ value: "notContains",
14
+ label: "Does not contain"
15
+ },
16
+ {
17
+ value: "equals",
18
+ label: "Equals"
19
+ },
20
+ {
21
+ value: "notEquals",
22
+ label: "Does not equal"
23
+ },
24
+ {
25
+ value: "startsWith",
26
+ label: "Starts with"
27
+ },
28
+ {
29
+ value: "endsWith",
30
+ label: "Ends with"
31
+ },
32
+ {
33
+ value: "blank",
34
+ label: "Is blank"
35
+ },
36
+ {
37
+ value: "notBlank",
38
+ label: "Is not blank"
39
+ }
40
+ ];
41
+ function TextFilterContent({ distinctValues, currentFilter, onApply, onClose }) {
42
+ const valueToString = useCallback((v) => {
43
+ if (Array.isArray(v)) return v.join(", ");
44
+ return String(v ?? "");
45
+ }, []);
46
+ const uniqueValues = useMemo(() => {
47
+ const values = distinctValues.filter((v) => v != null && v !== "" && !(Array.isArray(v) && v.length === 0)).map((v) => valueToString(v));
48
+ return Array.from(new Set(values)).sort((a, b) => {
49
+ const numA = parseFloat(a);
50
+ const numB = parseFloat(b);
51
+ if (!isNaN(numA) && !isNaN(numB)) return numA - numB;
52
+ return a.localeCompare(b, void 0, {
53
+ numeric: true,
54
+ sensitivity: "base"
55
+ });
56
+ });
57
+ }, [distinctValues, valueToString]);
58
+ const hasTooManyValues = uniqueValues.length > MAX_VALUES_FOR_LIST;
59
+ const [mode, setMode] = useState(useMemo(() => {
60
+ if (!currentFilter?.conditions[0]) return hasTooManyValues ? "condition" : "values";
61
+ const cond = currentFilter.conditions[0];
62
+ if (cond.selectedValues && cond.selectedValues.size > 0) return "values";
63
+ return "condition";
64
+ }, [currentFilter, hasTooManyValues]));
65
+ const initialSelected = useMemo(() => {
66
+ if (!currentFilter?.conditions[0]) return /* @__PURE__ */ new Set();
67
+ return currentFilter.conditions[0].selectedValues ?? /* @__PURE__ */ new Set();
68
+ }, [currentFilter]);
69
+ const initialIncludeBlanks = useMemo(() => {
70
+ if (!currentFilter?.conditions[0]) return true;
71
+ return currentFilter.conditions[0].includeBlank ?? true;
72
+ }, [currentFilter]);
73
+ const [searchText, setSearchText] = useState("");
74
+ const [selectedValues, setSelectedValues] = useState(initialSelected);
75
+ const [includeBlanks, setIncludeBlanks] = useState(initialIncludeBlanks);
76
+ const [conditions, setConditions] = useState(useMemo(() => {
77
+ if (!currentFilter?.conditions.length) return [{
78
+ operator: "contains",
79
+ value: "",
80
+ nextOperator: "and"
81
+ }];
82
+ const cond = currentFilter.conditions[0];
83
+ if (cond.selectedValues && cond.selectedValues.size > 0) return [{
84
+ operator: "contains",
85
+ value: "",
86
+ nextOperator: "and"
87
+ }];
88
+ const defaultCombination = currentFilter.combination ?? "and";
89
+ return currentFilter.conditions.map((c) => {
90
+ const tc = c;
91
+ return {
92
+ operator: tc.operator,
93
+ value: tc.value ?? "",
94
+ nextOperator: tc.nextOperator ?? defaultCombination
95
+ };
96
+ });
97
+ }, [currentFilter]));
98
+ const displayValues = useMemo(() => {
99
+ if (!searchText) return uniqueValues;
100
+ const lower = searchText.toLowerCase();
101
+ return uniqueValues.filter((v) => v.toLowerCase().includes(lower));
102
+ }, [uniqueValues, searchText]);
103
+ const hasBlanks = useMemo(() => {
104
+ return distinctValues.some((v) => v == null || v === "");
105
+ }, [distinctValues]);
106
+ const allSelected = useMemo(() => {
107
+ return displayValues.every((v) => selectedValues.has(v)) && (!hasBlanks || includeBlanks);
108
+ }, [
109
+ displayValues,
110
+ selectedValues,
111
+ hasBlanks,
112
+ includeBlanks
113
+ ]);
114
+ const handleSelectAll = useCallback(() => {
115
+ setSelectedValues(new Set(displayValues));
116
+ if (hasBlanks) setIncludeBlanks(true);
117
+ }, [displayValues, hasBlanks]);
118
+ const handleDeselectAll = useCallback(() => {
119
+ setSelectedValues(/* @__PURE__ */ new Set());
120
+ setIncludeBlanks(false);
121
+ }, []);
122
+ const handleValueToggle = useCallback((value) => {
123
+ setSelectedValues((prev) => {
124
+ const next = new Set(prev);
125
+ if (next.has(value)) next.delete(value);
126
+ else next.add(value);
127
+ return next;
128
+ });
129
+ }, []);
130
+ const updateCondition = useCallback((index, updates) => {
131
+ setConditions((prev) => {
132
+ const next = [...prev];
133
+ next[index] = {
134
+ ...next[index],
135
+ ...updates
136
+ };
137
+ return next;
138
+ });
139
+ }, []);
140
+ const addCondition = useCallback(() => {
141
+ setConditions((prev) => [...prev, {
142
+ operator: "contains",
143
+ value: "",
144
+ nextOperator: "and"
145
+ }]);
146
+ }, []);
147
+ const removeCondition = useCallback((index) => {
148
+ setConditions((prev) => prev.filter((_, i) => i !== index));
149
+ }, []);
150
+ const handleApply = useCallback(() => {
151
+ if (mode === "values") {
152
+ if (uniqueValues.every((v) => selectedValues.has(v)) && (!hasBlanks || includeBlanks)) {
153
+ onApply(null);
154
+ return;
155
+ }
156
+ onApply({
157
+ conditions: [{
158
+ type: "text",
159
+ operator: "equals",
160
+ selectedValues,
161
+ includeBlank: includeBlanks
162
+ }],
163
+ combination: "and"
164
+ });
165
+ } else {
166
+ const validConditions = conditions.filter((c) => {
167
+ if (c.operator === "blank" || c.operator === "notBlank") return true;
168
+ return c.value.trim() !== "";
169
+ });
170
+ if (validConditions.length === 0) {
171
+ onApply(null);
172
+ return;
173
+ }
174
+ onApply({
175
+ conditions: validConditions.map((c) => ({
176
+ type: "text",
177
+ operator: c.operator,
178
+ value: c.value,
179
+ nextOperator: c.nextOperator
180
+ })),
181
+ combination: "and"
182
+ });
183
+ }
184
+ }, [
185
+ mode,
186
+ uniqueValues,
187
+ selectedValues,
188
+ includeBlanks,
189
+ hasBlanks,
190
+ conditions,
191
+ onApply
192
+ ]);
193
+ const handleClear = useCallback(() => {
194
+ onApply(null);
195
+ }, [onApply]);
196
+ return /* @__PURE__ */ jsxs("div", {
197
+ className: "gp-grid-filter-content gp-grid-filter-text",
198
+ children: [
199
+ !hasTooManyValues && /* @__PURE__ */ jsxs("div", {
200
+ className: "gp-grid-filter-mode-toggle",
201
+ children: [/* @__PURE__ */ jsx("button", {
202
+ type: "button",
203
+ className: mode === "values" ? "active" : "",
204
+ onClick: () => setMode("values"),
205
+ children: "Values"
206
+ }), /* @__PURE__ */ jsx("button", {
207
+ type: "button",
208
+ className: mode === "condition" ? "active" : "",
209
+ onClick: () => setMode("condition"),
210
+ children: "Condition"
211
+ })]
212
+ }),
213
+ hasTooManyValues && mode === "condition" && /* @__PURE__ */ jsxs("div", {
214
+ className: "gp-grid-filter-info",
215
+ children: [
216
+ "Too many unique values (",
217
+ uniqueValues.length,
218
+ "). Use conditions to filter."
219
+ ]
220
+ }),
221
+ mode === "values" && /* @__PURE__ */ jsxs(Fragment, { children: [
222
+ /* @__PURE__ */ jsx("input", {
223
+ className: "gp-grid-filter-search",
224
+ type: "text",
225
+ placeholder: "Search...",
226
+ value: searchText,
227
+ onChange: (e) => setSearchText(e.target.value),
228
+ autoFocus: true
229
+ }),
230
+ /* @__PURE__ */ jsxs("div", {
231
+ className: "gp-grid-filter-actions",
232
+ children: [/* @__PURE__ */ jsx("button", {
233
+ type: "button",
234
+ onClick: handleSelectAll,
235
+ disabled: allSelected,
236
+ children: "Select All"
237
+ }), /* @__PURE__ */ jsx("button", {
238
+ type: "button",
239
+ onClick: handleDeselectAll,
240
+ children: "Deselect All"
241
+ })]
242
+ }),
243
+ /* @__PURE__ */ jsxs("div", {
244
+ className: "gp-grid-filter-list",
245
+ children: [hasBlanks && /* @__PURE__ */ jsxs("label", {
246
+ className: "gp-grid-filter-option",
247
+ children: [/* @__PURE__ */ jsx("input", {
248
+ type: "checkbox",
249
+ checked: includeBlanks,
250
+ onChange: () => setIncludeBlanks(!includeBlanks)
251
+ }), /* @__PURE__ */ jsx("span", {
252
+ className: "gp-grid-filter-blank",
253
+ children: "(Blanks)"
254
+ })]
255
+ }), displayValues.map((value) => /* @__PURE__ */ jsxs("label", {
256
+ className: "gp-grid-filter-option",
257
+ children: [/* @__PURE__ */ jsx("input", {
258
+ type: "checkbox",
259
+ checked: selectedValues.has(value),
260
+ onChange: () => handleValueToggle(value)
261
+ }), /* @__PURE__ */ jsx("span", { children: value })]
262
+ }, value))]
263
+ })
264
+ ] }),
265
+ mode === "condition" && /* @__PURE__ */ jsxs(Fragment, { children: [conditions.map((cond, index) => /* @__PURE__ */ jsxs("div", {
266
+ className: "gp-grid-filter-condition",
267
+ children: [index > 0 && /* @__PURE__ */ jsxs("div", {
268
+ className: "gp-grid-filter-combination",
269
+ children: [/* @__PURE__ */ jsx("button", {
270
+ type: "button",
271
+ className: conditions[index - 1]?.nextOperator === "and" ? "active" : "",
272
+ onClick: () => updateCondition(index - 1, { nextOperator: "and" }),
273
+ children: "AND"
274
+ }), /* @__PURE__ */ jsx("button", {
275
+ type: "button",
276
+ className: conditions[index - 1]?.nextOperator === "or" ? "active" : "",
277
+ onClick: () => updateCondition(index - 1, { nextOperator: "or" }),
278
+ children: "OR"
279
+ })]
280
+ }), /* @__PURE__ */ jsxs("div", {
281
+ className: "gp-grid-filter-row",
282
+ children: [
283
+ /* @__PURE__ */ jsx("select", {
284
+ value: cond.operator,
285
+ onChange: (e) => updateCondition(index, { operator: e.target.value }),
286
+ autoFocus: index === 0,
287
+ children: OPERATORS$2.map((op) => /* @__PURE__ */ jsx("option", {
288
+ value: op.value,
289
+ children: op.label
290
+ }, op.value))
291
+ }),
292
+ cond.operator !== "blank" && cond.operator !== "notBlank" && /* @__PURE__ */ jsx("input", {
293
+ type: "text",
294
+ value: cond.value,
295
+ onChange: (e) => updateCondition(index, { value: e.target.value }),
296
+ placeholder: "Value",
297
+ className: "gp-grid-filter-text-input"
298
+ }),
299
+ conditions.length > 1 && /* @__PURE__ */ jsx("button", {
300
+ type: "button",
301
+ className: "gp-grid-filter-remove",
302
+ onClick: () => removeCondition(index),
303
+ children: "×"
304
+ })
305
+ ]
306
+ })]
307
+ }, index)), /* @__PURE__ */ jsx("button", {
308
+ type: "button",
309
+ className: "gp-grid-filter-add",
310
+ onClick: addCondition,
311
+ children: "+ Add condition"
312
+ })] }),
313
+ /* @__PURE__ */ jsxs("div", {
314
+ className: "gp-grid-filter-buttons",
315
+ children: [/* @__PURE__ */ jsx("button", {
316
+ type: "button",
317
+ className: "gp-grid-filter-btn-clear",
318
+ onClick: handleClear,
319
+ children: "Clear"
320
+ }), /* @__PURE__ */ jsx("button", {
321
+ type: "button",
322
+ className: "gp-grid-filter-btn-apply",
323
+ onClick: handleApply,
324
+ children: "Apply"
325
+ })]
326
+ })
327
+ ]
328
+ });
329
+ }
330
+
331
+ //#endregion
332
+ //#region src/components/NumberFilterContent.tsx
333
+ const OPERATORS$1 = [
334
+ {
335
+ value: "=",
336
+ label: "="
337
+ },
338
+ {
339
+ value: "!=",
340
+ label: "≠"
341
+ },
342
+ {
343
+ value: ">",
344
+ label: ">"
345
+ },
346
+ {
347
+ value: "<",
348
+ label: "<"
349
+ },
350
+ {
351
+ value: ">=",
352
+ label: "≥"
353
+ },
354
+ {
355
+ value: "<=",
356
+ label: "≤"
357
+ },
358
+ {
359
+ value: "between",
360
+ label: "↔"
361
+ },
362
+ {
363
+ value: "blank",
364
+ label: "Is blank"
365
+ },
366
+ {
367
+ value: "notBlank",
368
+ label: "Not blank"
369
+ }
370
+ ];
371
+ function NumberFilterContent({ currentFilter, onApply, onClose }) {
372
+ const [conditions, setConditions] = useState(useMemo(() => {
373
+ if (!currentFilter?.conditions.length) return [{
374
+ operator: "=",
375
+ value: "",
376
+ valueTo: "",
377
+ nextOperator: "and"
378
+ }];
379
+ const defaultCombination = currentFilter.combination ?? "and";
380
+ return currentFilter.conditions.map((c) => {
381
+ const cond = c;
382
+ return {
383
+ operator: cond.operator,
384
+ value: cond.value != null ? String(cond.value) : "",
385
+ valueTo: cond.valueTo != null ? String(cond.valueTo) : "",
386
+ nextOperator: cond.nextOperator ?? defaultCombination
387
+ };
388
+ });
389
+ }, [currentFilter]));
390
+ const updateCondition = useCallback((index, updates) => {
391
+ setConditions((prev) => {
392
+ const next = [...prev];
393
+ next[index] = {
394
+ ...next[index],
395
+ ...updates
396
+ };
397
+ return next;
398
+ });
399
+ }, []);
400
+ const addCondition = useCallback(() => {
401
+ setConditions((prev) => [...prev, {
402
+ operator: "=",
403
+ value: "",
404
+ valueTo: "",
405
+ nextOperator: "and"
406
+ }]);
407
+ }, []);
408
+ const removeCondition = useCallback((index) => {
409
+ setConditions((prev) => prev.filter((_, i) => i !== index));
410
+ }, []);
411
+ const handleApply = useCallback(() => {
412
+ const validConditions = conditions.filter((c) => {
413
+ if (c.operator === "blank" || c.operator === "notBlank") return true;
414
+ if (c.operator === "between") return c.value !== "" && c.valueTo !== "";
415
+ return c.value !== "";
416
+ });
417
+ if (validConditions.length === 0) {
418
+ onApply(null);
419
+ return;
420
+ }
421
+ onApply({
422
+ conditions: validConditions.map((c) => ({
423
+ type: "number",
424
+ operator: c.operator,
425
+ value: c.value ? parseFloat(c.value) : void 0,
426
+ valueTo: c.valueTo ? parseFloat(c.valueTo) : void 0,
427
+ nextOperator: c.nextOperator
428
+ })),
429
+ combination: "and"
430
+ });
431
+ }, [conditions, onApply]);
432
+ const handleClear = useCallback(() => {
433
+ onApply(null);
434
+ }, [onApply]);
435
+ return /* @__PURE__ */ jsxs("div", {
436
+ className: "gp-grid-filter-content gp-grid-filter-number",
437
+ children: [
438
+ conditions.map((cond, index) => /* @__PURE__ */ jsxs("div", {
439
+ className: "gp-grid-filter-condition",
440
+ children: [index > 0 && /* @__PURE__ */ jsxs("div", {
441
+ className: "gp-grid-filter-combination",
442
+ children: [/* @__PURE__ */ jsx("button", {
443
+ type: "button",
444
+ className: conditions[index - 1]?.nextOperator === "and" ? "active" : "",
445
+ onClick: () => updateCondition(index - 1, { nextOperator: "and" }),
446
+ children: "AND"
447
+ }), /* @__PURE__ */ jsx("button", {
448
+ type: "button",
449
+ className: conditions[index - 1]?.nextOperator === "or" ? "active" : "",
450
+ onClick: () => updateCondition(index - 1, { nextOperator: "or" }),
451
+ children: "OR"
452
+ })]
453
+ }), /* @__PURE__ */ jsxs("div", {
454
+ className: "gp-grid-filter-row",
455
+ children: [
456
+ /* @__PURE__ */ jsx("select", {
457
+ value: cond.operator,
458
+ onChange: (e) => updateCondition(index, { operator: e.target.value }),
459
+ children: OPERATORS$1.map((op) => /* @__PURE__ */ jsx("option", {
460
+ value: op.value,
461
+ children: op.label
462
+ }, op.value))
463
+ }),
464
+ cond.operator !== "blank" && cond.operator !== "notBlank" && /* @__PURE__ */ jsx("input", {
465
+ type: "number",
466
+ value: cond.value,
467
+ onChange: (e) => updateCondition(index, { value: e.target.value }),
468
+ placeholder: "Value"
469
+ }),
470
+ cond.operator === "between" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
471
+ className: "gp-grid-filter-to",
472
+ children: "to"
473
+ }), /* @__PURE__ */ jsx("input", {
474
+ type: "number",
475
+ value: cond.valueTo,
476
+ onChange: (e) => updateCondition(index, { valueTo: e.target.value }),
477
+ placeholder: "Value"
478
+ })] }),
479
+ conditions.length > 1 && /* @__PURE__ */ jsx("button", {
480
+ type: "button",
481
+ className: "gp-grid-filter-remove",
482
+ onClick: () => removeCondition(index),
483
+ children: "×"
484
+ })
485
+ ]
486
+ })]
487
+ }, index)),
488
+ /* @__PURE__ */ jsx("button", {
489
+ type: "button",
490
+ className: "gp-grid-filter-add",
491
+ onClick: addCondition,
492
+ children: "+ Add condition"
493
+ }),
494
+ /* @__PURE__ */ jsxs("div", {
495
+ className: "gp-grid-filter-buttons",
496
+ children: [/* @__PURE__ */ jsx("button", {
497
+ type: "button",
498
+ className: "gp-grid-filter-btn-clear",
499
+ onClick: handleClear,
500
+ children: "Clear"
501
+ }), /* @__PURE__ */ jsx("button", {
502
+ type: "button",
503
+ className: "gp-grid-filter-btn-apply",
504
+ onClick: handleApply,
505
+ children: "Apply"
506
+ })]
507
+ })
508
+ ]
509
+ });
510
+ }
511
+
512
+ //#endregion
513
+ //#region src/components/DateFilterContent.tsx
514
+ const OPERATORS = [
515
+ {
516
+ value: "=",
517
+ label: "="
518
+ },
519
+ {
520
+ value: "!=",
521
+ label: "≠"
522
+ },
523
+ {
524
+ value: ">",
525
+ label: ">"
526
+ },
527
+ {
528
+ value: "<",
529
+ label: "<"
530
+ },
531
+ {
532
+ value: "between",
533
+ label: "↔"
534
+ },
535
+ {
536
+ value: "blank",
537
+ label: "Is blank"
538
+ },
539
+ {
540
+ value: "notBlank",
541
+ label: "Not blank"
542
+ }
543
+ ];
544
+ function formatDateForInput(date) {
545
+ if (!date) return "";
546
+ const d = typeof date === "string" ? new Date(date) : date;
547
+ if (isNaN(d.getTime())) return "";
548
+ return d.toISOString().split("T")[0];
549
+ }
550
+ function DateFilterContent({ currentFilter, onApply, onClose }) {
551
+ const [conditions, setConditions] = useState(useMemo(() => {
552
+ if (!currentFilter?.conditions.length) return [{
553
+ operator: "=",
554
+ value: "",
555
+ valueTo: "",
556
+ nextOperator: "and"
557
+ }];
558
+ const defaultCombination = currentFilter.combination ?? "and";
559
+ return currentFilter.conditions.map((c) => {
560
+ const cond = c;
561
+ return {
562
+ operator: cond.operator,
563
+ value: formatDateForInput(cond.value),
564
+ valueTo: formatDateForInput(cond.valueTo),
565
+ nextOperator: cond.nextOperator ?? defaultCombination
566
+ };
567
+ });
568
+ }, [currentFilter]));
569
+ const updateCondition = useCallback((index, updates) => {
570
+ setConditions((prev) => {
571
+ const next = [...prev];
572
+ next[index] = {
573
+ ...next[index],
574
+ ...updates
575
+ };
576
+ return next;
577
+ });
578
+ }, []);
579
+ const addCondition = useCallback(() => {
580
+ setConditions((prev) => [...prev, {
581
+ operator: "=",
582
+ value: "",
583
+ valueTo: "",
584
+ nextOperator: "and"
585
+ }]);
586
+ }, []);
587
+ const removeCondition = useCallback((index) => {
588
+ setConditions((prev) => prev.filter((_, i) => i !== index));
589
+ }, []);
590
+ const handleApply = useCallback(() => {
591
+ const validConditions = conditions.filter((c) => {
592
+ if (c.operator === "blank" || c.operator === "notBlank") return true;
593
+ if (c.operator === "between") return c.value !== "" && c.valueTo !== "";
594
+ return c.value !== "";
595
+ });
596
+ if (validConditions.length === 0) {
597
+ onApply(null);
598
+ return;
599
+ }
600
+ onApply({
601
+ conditions: validConditions.map((c) => ({
602
+ type: "date",
603
+ operator: c.operator,
604
+ value: c.value || void 0,
605
+ valueTo: c.valueTo || void 0,
606
+ nextOperator: c.nextOperator
607
+ })),
608
+ combination: "and"
609
+ });
610
+ }, [conditions, onApply]);
611
+ const handleClear = useCallback(() => {
612
+ onApply(null);
613
+ }, [onApply]);
614
+ return /* @__PURE__ */ jsxs("div", {
615
+ className: "gp-grid-filter-content gp-grid-filter-date",
616
+ children: [
617
+ conditions.map((cond, index) => /* @__PURE__ */ jsxs("div", {
618
+ className: "gp-grid-filter-condition",
619
+ children: [index > 0 && /* @__PURE__ */ jsxs("div", {
620
+ className: "gp-grid-filter-combination",
621
+ children: [/* @__PURE__ */ jsx("button", {
622
+ type: "button",
623
+ className: conditions[index - 1]?.nextOperator === "and" ? "active" : "",
624
+ onClick: () => updateCondition(index - 1, { nextOperator: "and" }),
625
+ children: "AND"
626
+ }), /* @__PURE__ */ jsx("button", {
627
+ type: "button",
628
+ className: conditions[index - 1]?.nextOperator === "or" ? "active" : "",
629
+ onClick: () => updateCondition(index - 1, { nextOperator: "or" }),
630
+ children: "OR"
631
+ })]
632
+ }), /* @__PURE__ */ jsxs("div", {
633
+ className: "gp-grid-filter-row",
634
+ children: [
635
+ /* @__PURE__ */ jsx("select", {
636
+ value: cond.operator,
637
+ onChange: (e) => updateCondition(index, { operator: e.target.value }),
638
+ children: OPERATORS.map((op) => /* @__PURE__ */ jsx("option", {
639
+ value: op.value,
640
+ children: op.label
641
+ }, op.value))
642
+ }),
643
+ cond.operator !== "blank" && cond.operator !== "notBlank" && /* @__PURE__ */ jsx("input", {
644
+ type: "date",
645
+ value: cond.value,
646
+ onChange: (e) => updateCondition(index, { value: e.target.value })
647
+ }),
648
+ cond.operator === "between" && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
649
+ className: "gp-grid-filter-to",
650
+ children: "to"
651
+ }), /* @__PURE__ */ jsx("input", {
652
+ type: "date",
653
+ value: cond.valueTo,
654
+ onChange: (e) => updateCondition(index, { valueTo: e.target.value })
655
+ })] }),
656
+ conditions.length > 1 && /* @__PURE__ */ jsx("button", {
657
+ type: "button",
658
+ className: "gp-grid-filter-remove",
659
+ onClick: () => removeCondition(index),
660
+ children: "×"
661
+ })
662
+ ]
663
+ })]
664
+ }, index)),
665
+ /* @__PURE__ */ jsx("button", {
666
+ type: "button",
667
+ className: "gp-grid-filter-add",
668
+ onClick: addCondition,
669
+ children: "+ Add condition"
670
+ }),
671
+ /* @__PURE__ */ jsxs("div", {
672
+ className: "gp-grid-filter-buttons",
673
+ children: [/* @__PURE__ */ jsx("button", {
674
+ type: "button",
675
+ className: "gp-grid-filter-btn-clear",
676
+ onClick: handleClear,
677
+ children: "Clear"
678
+ }), /* @__PURE__ */ jsx("button", {
679
+ type: "button",
680
+ className: "gp-grid-filter-btn-apply",
681
+ onClick: handleApply,
682
+ children: "Apply"
683
+ })]
684
+ })
685
+ ]
686
+ });
687
+ }
688
+
689
+ //#endregion
690
+ //#region src/components/FilterPopup.tsx
691
+ function FilterPopup({ column, colIndex, anchorRect, distinctValues, currentFilter, onApply, onClose }) {
692
+ const popupRef = useRef(null);
693
+ useEffect(() => {
694
+ const handleClickOutside = (e) => {
695
+ const target = e.target;
696
+ if (target.closest(".gp-grid-filter-icon")) return;
697
+ if (popupRef.current && !popupRef.current.contains(target)) onClose();
698
+ };
699
+ const handleKeyDown = (e) => {
700
+ if (e.key === "Escape") onClose();
701
+ };
702
+ requestAnimationFrame(() => {
703
+ document.addEventListener("mousedown", handleClickOutside);
704
+ document.addEventListener("keydown", handleKeyDown);
705
+ });
706
+ return () => {
707
+ document.removeEventListener("mousedown", handleClickOutside);
708
+ document.removeEventListener("keydown", handleKeyDown);
709
+ };
710
+ }, [onClose]);
711
+ const handleApply = useCallback((filter) => {
712
+ onApply(column.colId ?? column.field, filter);
713
+ onClose();
714
+ }, [
715
+ column,
716
+ onApply,
717
+ onClose
718
+ ]);
719
+ const popupStyle = {
720
+ position: "fixed",
721
+ top: anchorRect.top + anchorRect.height + 4,
722
+ left: anchorRect.left,
723
+ minWidth: Math.max(200, anchorRect.width),
724
+ zIndex: 1e4
725
+ };
726
+ const dataType = column.cellDataType;
727
+ const isTextType = dataType === "text" || dataType === "object";
728
+ const isNumberType = dataType === "number";
729
+ const isDateType = dataType === "date" || dataType === "dateString" || dataType === "dateTime" || dataType === "dateTimeString";
730
+ return /* @__PURE__ */ jsxs("div", {
731
+ ref: popupRef,
732
+ className: "gp-grid-filter-popup",
733
+ style: popupStyle,
734
+ children: [
735
+ /* @__PURE__ */ jsxs("div", {
736
+ className: "gp-grid-filter-header",
737
+ children: ["Filter: ", column.headerName ?? column.field]
738
+ }),
739
+ isTextType && /* @__PURE__ */ jsx(TextFilterContent, {
740
+ distinctValues,
741
+ currentFilter,
742
+ onApply: handleApply,
743
+ onClose
744
+ }),
745
+ isNumberType && /* @__PURE__ */ jsx(NumberFilterContent, {
746
+ currentFilter,
747
+ onApply: handleApply,
748
+ onClose
749
+ }),
750
+ isDateType && /* @__PURE__ */ jsx(DateFilterContent, {
751
+ currentFilter,
752
+ onApply: handleApply,
753
+ onClose
754
+ }),
755
+ !isTextType && !isNumberType && !isDateType && /* @__PURE__ */ jsx(TextFilterContent, {
756
+ distinctValues,
757
+ currentFilter,
758
+ onApply: handleApply,
759
+ onClose
760
+ })
761
+ ]
762
+ });
763
+ }
764
+
765
+ //#endregion
766
+ //#region src/gridState/reducer.ts
767
+ function createInitialState(args) {
768
+ return {
769
+ slots: /* @__PURE__ */ new Map(),
770
+ activeCell: null,
771
+ selectionRange: null,
772
+ editingCell: null,
773
+ contentWidth: 0,
774
+ contentHeight: args?.initialHeight ?? 0,
775
+ viewportWidth: args?.initialWidth ?? 0,
776
+ headers: /* @__PURE__ */ new Map(),
777
+ filterPopup: null,
778
+ isLoading: false,
779
+ error: null,
780
+ totalRows: 0,
781
+ visibleRowRange: null,
782
+ hoverPosition: null
783
+ };
784
+ }
785
+ /**
786
+ * Apply a single instruction to mutable slot maps and return other state changes.
787
+ * This allows batching multiple slot operations efficiently.
788
+ */
789
+ function applyInstruction(instruction, slots, headers) {
790
+ switch (instruction.type) {
791
+ case "CREATE_SLOT":
792
+ slots.set(instruction.slotId, {
793
+ slotId: instruction.slotId,
794
+ rowIndex: -1,
795
+ rowData: {},
796
+ translateY: 0
797
+ });
798
+ return null;
799
+ case "DESTROY_SLOT":
800
+ slots.delete(instruction.slotId);
801
+ return null;
802
+ case "ASSIGN_SLOT": {
803
+ const existing = slots.get(instruction.slotId);
804
+ if (existing) slots.set(instruction.slotId, {
805
+ ...existing,
806
+ rowIndex: instruction.rowIndex,
807
+ rowData: instruction.rowData
808
+ });
809
+ return null;
810
+ }
811
+ case "MOVE_SLOT": {
812
+ const existing = slots.get(instruction.slotId);
813
+ if (existing) slots.set(instruction.slotId, {
814
+ ...existing,
815
+ translateY: instruction.translateY
816
+ });
817
+ return null;
818
+ }
819
+ case "SET_ACTIVE_CELL": return { activeCell: instruction.position };
820
+ case "SET_SELECTION_RANGE": return { selectionRange: instruction.range };
821
+ case "UPDATE_VISIBLE_RANGE": return { visibleRowRange: {
822
+ start: instruction.start,
823
+ end: instruction.end
824
+ } };
825
+ case "SET_HOVER_POSITION": return { hoverPosition: instruction.position };
826
+ case "START_EDIT": return { editingCell: {
827
+ row: instruction.row,
828
+ col: instruction.col,
829
+ initialValue: instruction.initialValue
830
+ } };
831
+ case "STOP_EDIT": return { editingCell: null };
832
+ case "SET_CONTENT_SIZE": return {
833
+ contentWidth: instruction.width,
834
+ contentHeight: instruction.height,
835
+ viewportWidth: instruction.viewportWidth
836
+ };
837
+ case "UPDATE_HEADER":
838
+ headers.set(instruction.colIndex, {
839
+ column: instruction.column,
840
+ sortDirection: instruction.sortDirection,
841
+ sortIndex: instruction.sortIndex,
842
+ sortable: instruction.sortable,
843
+ filterable: instruction.filterable,
844
+ hasFilter: instruction.hasFilter
845
+ });
846
+ return null;
847
+ case "OPEN_FILTER_POPUP": return { filterPopup: {
848
+ isOpen: true,
849
+ colIndex: instruction.colIndex,
850
+ column: instruction.column,
851
+ anchorRect: instruction.anchorRect,
852
+ distinctValues: instruction.distinctValues,
853
+ currentFilter: instruction.currentFilter
854
+ } };
855
+ case "CLOSE_FILTER_POPUP": return { filterPopup: null };
856
+ case "DATA_LOADING": return {
857
+ isLoading: true,
858
+ error: null
859
+ };
860
+ case "DATA_LOADED": return {
861
+ isLoading: false,
862
+ totalRows: instruction.totalRows
863
+ };
864
+ case "DATA_ERROR": return {
865
+ isLoading: false,
866
+ error: instruction.error
867
+ };
868
+ case "ROWS_ADDED":
869
+ case "ROWS_REMOVED": return { totalRows: instruction.totalRows };
870
+ case "ROWS_UPDATED":
871
+ case "TRANSACTION_PROCESSED": return null;
872
+ default: return null;
873
+ }
874
+ }
875
+ function gridReducer(state, action) {
876
+ if (action.type === "RESET") return createInitialState();
877
+ const { instructions } = action;
878
+ if (instructions.length === 0) return state;
879
+ const newSlots = new Map(state.slots);
880
+ const newHeaders = new Map(state.headers);
881
+ let stateChanges = {};
882
+ for (const instruction of instructions) {
883
+ const changes = applyInstruction(instruction, newSlots, newHeaders);
884
+ if (changes) stateChanges = {
885
+ ...stateChanges,
886
+ ...changes
887
+ };
888
+ }
889
+ return {
890
+ ...state,
891
+ ...stateChanges,
892
+ slots: newSlots,
893
+ headers: newHeaders
894
+ };
895
+ }
896
+
897
+ //#endregion
898
+ //#region src/hooks/useInputHandler.ts
899
+ const AUTO_SCROLL_INTERVAL = 16;
900
+ /**
901
+ * Find the slot for a given row index
902
+ */
903
+ function findSlotForRow(slots, rowIndex) {
904
+ for (const slot of slots.values()) if (slot.rowIndex === rowIndex) return slot;
905
+ return null;
906
+ }
907
+ /**
908
+ * Scroll a cell into view if needed
909
+ */
910
+ function scrollCellIntoView(core, container, row, rowHeight, headerHeight, slots) {
911
+ const slot = findSlotForRow(slots, row);
912
+ const cellViewportTop = (slot ? slot.translateY : headerHeight + row * rowHeight) - container.scrollTop;
913
+ const cellViewportBottom = cellViewportTop + rowHeight;
914
+ const visibleTop = headerHeight;
915
+ const visibleBottom = container.clientHeight;
916
+ if (cellViewportTop < visibleTop) container.scrollTop = core.getScrollTopForRow(row);
917
+ else if (cellViewportBottom > visibleBottom) {
918
+ const visibleDataHeight = container.clientHeight - headerHeight;
919
+ const rowsInView = Math.floor(visibleDataHeight / rowHeight);
920
+ const targetRow = Math.max(0, row - rowsInView + 1);
921
+ container.scrollTop = core.getScrollTopForRow(targetRow);
922
+ }
923
+ }
924
+ /**
925
+ * Hook for managing all input handling using core's InputHandler
926
+ */
927
+ function useInputHandler(coreRef, containerRef, columns, options) {
928
+ const { activeCell, selectionRange, editingCell, filterPopupOpen, rowHeight, headerHeight, columnPositions, visibleColumnsWithIndices, slots } = options;
929
+ const autoScrollRef = useRef(null);
930
+ const [dragState, setDragState] = useState({
931
+ isDragging: false,
932
+ dragType: null,
933
+ fillSourceRange: null,
934
+ fillTarget: null
935
+ });
936
+ useEffect(() => {
937
+ const core = coreRef.current;
938
+ if (core?.input) core.input.updateDeps({
939
+ getHeaderHeight: () => headerHeight,
940
+ getRowHeight: () => rowHeight,
941
+ getColumnPositions: () => columnPositions,
942
+ getColumnCount: () => visibleColumnsWithIndices.length,
943
+ getOriginalColumnIndex: (visibleIndex) => {
944
+ const info = visibleColumnsWithIndices[visibleIndex];
945
+ return info ? info.originalIndex : visibleIndex;
946
+ }
947
+ });
948
+ }, [
949
+ coreRef,
950
+ headerHeight,
951
+ rowHeight,
952
+ columnPositions,
953
+ visibleColumnsWithIndices
954
+ ]);
955
+ const startAutoScroll = useCallback((dx, dy) => {
956
+ if (autoScrollRef.current) clearInterval(autoScrollRef.current);
957
+ autoScrollRef.current = setInterval(() => {
958
+ const container = containerRef.current;
959
+ if (container) {
960
+ container.scrollTop += dy;
961
+ container.scrollLeft += dx;
962
+ }
963
+ }, AUTO_SCROLL_INTERVAL);
964
+ }, [containerRef]);
965
+ const stopAutoScroll = useCallback(() => {
966
+ if (autoScrollRef.current) {
967
+ clearInterval(autoScrollRef.current);
968
+ autoScrollRef.current = null;
969
+ }
970
+ }, []);
971
+ const getContainerBounds = useCallback(() => {
972
+ const container = containerRef.current;
973
+ if (!container) return null;
974
+ const rect = container.getBoundingClientRect();
975
+ return {
976
+ top: rect.top,
977
+ left: rect.left,
978
+ width: rect.width,
979
+ height: rect.height,
980
+ scrollTop: container.scrollTop,
981
+ scrollLeft: container.scrollLeft
982
+ };
983
+ }, [containerRef]);
984
+ const toPointerEventData = (e) => ({
985
+ clientX: e.clientX,
986
+ clientY: e.clientY,
987
+ button: e.button,
988
+ shiftKey: e.shiftKey,
989
+ ctrlKey: e.ctrlKey,
990
+ metaKey: e.metaKey
991
+ });
992
+ const startGlobalDragListeners = useCallback(() => {
993
+ const handleMouseMove = (e) => {
994
+ const core = coreRef.current;
995
+ const bounds = getContainerBounds();
996
+ if (!core?.input || !bounds) return;
997
+ const result = core.input.handleDragMove(toPointerEventData(e), bounds);
998
+ if (result) {
999
+ if (result.autoScroll) startAutoScroll(result.autoScroll.dx, result.autoScroll.dy);
1000
+ else stopAutoScroll();
1001
+ setDragState(core.input.getDragState());
1002
+ }
1003
+ };
1004
+ const handleMouseUp = () => {
1005
+ const core = coreRef.current;
1006
+ if (core?.input) {
1007
+ core.input.handleDragEnd();
1008
+ setDragState(core.input.getDragState());
1009
+ }
1010
+ stopAutoScroll();
1011
+ document.removeEventListener("mousemove", handleMouseMove);
1012
+ document.removeEventListener("mouseup", handleMouseUp);
1013
+ };
1014
+ document.addEventListener("mousemove", handleMouseMove);
1015
+ document.addEventListener("mouseup", handleMouseUp);
1016
+ }, [
1017
+ coreRef,
1018
+ getContainerBounds,
1019
+ startAutoScroll,
1020
+ stopAutoScroll
1021
+ ]);
1022
+ const handleCellMouseDown = useCallback((rowIndex, colIndex, e) => {
1023
+ const core = coreRef.current;
1024
+ if (!core?.input) return;
1025
+ const result = core.input.handleCellMouseDown(rowIndex, colIndex, toPointerEventData(e));
1026
+ if (result.focusContainer) containerRef.current?.focus();
1027
+ if (result.startDrag === "selection") {
1028
+ core.input.startSelectionDrag();
1029
+ setDragState(core.input.getDragState());
1030
+ startGlobalDragListeners();
1031
+ }
1032
+ }, [
1033
+ coreRef,
1034
+ containerRef,
1035
+ startGlobalDragListeners
1036
+ ]);
1037
+ const handleCellDoubleClick = useCallback((rowIndex, colIndex) => {
1038
+ const core = coreRef.current;
1039
+ if (!core?.input) return;
1040
+ core.input.handleCellDoubleClick(rowIndex, colIndex);
1041
+ }, [coreRef]);
1042
+ const handleFillHandleMouseDown = useCallback((e) => {
1043
+ const core = coreRef.current;
1044
+ if (!core?.input) return;
1045
+ const result = core.input.handleFillHandleMouseDown(activeCell, selectionRange, toPointerEventData(e));
1046
+ if (result.preventDefault) e.preventDefault();
1047
+ if (result.stopPropagation) e.stopPropagation();
1048
+ if (result.startDrag === "fill") {
1049
+ setDragState(core.input.getDragState());
1050
+ startGlobalDragListeners();
1051
+ }
1052
+ }, [
1053
+ coreRef,
1054
+ activeCell,
1055
+ selectionRange,
1056
+ startGlobalDragListeners
1057
+ ]);
1058
+ const handleHeaderClick = useCallback((colIndex, e) => {
1059
+ const core = coreRef.current;
1060
+ if (!core?.input) return;
1061
+ const column = columns[colIndex];
1062
+ if (!column) return;
1063
+ const colId = column.colId ?? column.field;
1064
+ core.input.handleHeaderClick(colId, e.shiftKey);
1065
+ }, [coreRef, columns]);
1066
+ const handleKeyDown = useCallback((e) => {
1067
+ const core = coreRef.current;
1068
+ const container = containerRef.current;
1069
+ if (!core?.input) return;
1070
+ const result = core.input.handleKeyDown({
1071
+ key: e.key,
1072
+ shiftKey: e.shiftKey,
1073
+ ctrlKey: e.ctrlKey,
1074
+ metaKey: e.metaKey
1075
+ }, activeCell, editingCell, filterPopupOpen);
1076
+ if (result.preventDefault) e.preventDefault();
1077
+ if (result.scrollToCell && container) scrollCellIntoView(core, container, result.scrollToCell.row, rowHeight, headerHeight, slots);
1078
+ }, [
1079
+ coreRef,
1080
+ containerRef,
1081
+ activeCell,
1082
+ editingCell,
1083
+ filterPopupOpen,
1084
+ rowHeight,
1085
+ headerHeight,
1086
+ slots
1087
+ ]);
1088
+ const handleWheel = useCallback((e, wheelDampening) => {
1089
+ const core = coreRef.current;
1090
+ const container = containerRef.current;
1091
+ if (!core?.input || !container) return;
1092
+ const dampened = core.input.handleWheel(e.deltaY, e.deltaX, wheelDampening);
1093
+ if (dampened) {
1094
+ e.preventDefault();
1095
+ container.scrollTop += dampened.dy;
1096
+ container.scrollLeft += dampened.dx;
1097
+ }
1098
+ }, [coreRef, containerRef]);
1099
+ useEffect(() => {
1100
+ return () => {
1101
+ stopAutoScroll();
1102
+ };
1103
+ }, [stopAutoScroll]);
1104
+ return {
1105
+ handleCellMouseDown,
1106
+ handleCellDoubleClick,
1107
+ handleFillHandleMouseDown,
1108
+ handleHeaderClick,
1109
+ handleKeyDown,
1110
+ handleWheel,
1111
+ dragState
1112
+ };
1113
+ }
1114
+
1115
+ //#endregion
1116
+ //#region src/renderers/cellRenderer.tsx
1117
+ /**
1118
+ * Get cell value from row data, supporting dot-notation for nested fields
1119
+ */
1120
+ function getCellValue(rowData, field) {
1121
+ const parts = field.split(".");
1122
+ let value = rowData;
1123
+ for (const part of parts) {
1124
+ if (value == null || typeof value !== "object") return null;
1125
+ value = value[part];
1126
+ }
1127
+ return value ?? null;
1128
+ }
1129
+ /**
1130
+ * Render cell content based on column configuration and renderer registries
1131
+ */
1132
+ function renderCell(options) {
1133
+ const { column, rowData, rowIndex, colIndex, isActive, isSelected, isEditing, cellRenderers, globalCellRenderer } = options;
1134
+ const value = getCellValue(rowData, column.field);
1135
+ const params = {
1136
+ value,
1137
+ rowData,
1138
+ column,
1139
+ rowIndex,
1140
+ colIndex,
1141
+ isActive,
1142
+ isSelected,
1143
+ isEditing
1144
+ };
1145
+ if (column.cellRenderer && typeof column.cellRenderer === "string") {
1146
+ const renderer = cellRenderers[column.cellRenderer];
1147
+ if (renderer) return renderer(params);
1148
+ }
1149
+ if (globalCellRenderer) return globalCellRenderer(params);
1150
+ return value == null ? "" : String(value);
1151
+ }
1152
+
1153
+ //#endregion
1154
+ //#region src/renderers/editRenderer.tsx
1155
+ /**
1156
+ * Render edit cell content based on column configuration and renderer registries
1157
+ */
1158
+ function renderEditCell(options) {
1159
+ const { column, rowData, rowIndex, colIndex, initialValue, coreRef, editRenderers, globalEditRenderer } = options;
1160
+ const core = coreRef.current;
1161
+ if (!core) return null;
1162
+ const params = {
1163
+ value: getCellValue(rowData, column.field),
1164
+ rowData,
1165
+ column,
1166
+ rowIndex,
1167
+ colIndex,
1168
+ isActive: true,
1169
+ isSelected: true,
1170
+ isEditing: true,
1171
+ initialValue,
1172
+ onValueChange: (newValue) => core.updateEditValue(newValue),
1173
+ onCommit: () => core.commitEdit(),
1174
+ onCancel: () => core.cancelEdit()
1175
+ };
1176
+ if (column.editRenderer && typeof column.editRenderer === "string") {
1177
+ const renderer = editRenderers[column.editRenderer];
1178
+ if (renderer) return renderer(params);
1179
+ }
1180
+ if (globalEditRenderer) return globalEditRenderer(params);
1181
+ return /* @__PURE__ */ jsx("input", {
1182
+ className: "gp-grid-edit-input",
1183
+ type: "text",
1184
+ defaultValue: initialValue == null ? "" : String(initialValue),
1185
+ autoFocus: true,
1186
+ onFocus: (e) => e.target.select(),
1187
+ onChange: (e) => core.updateEditValue(e.target.value),
1188
+ onKeyDown: (e) => {
1189
+ e.stopPropagation();
1190
+ if (e.key === "Enter") core.commitEdit();
1191
+ else if (e.key === "Escape") core.cancelEdit();
1192
+ else if (e.key === "Tab") {
1193
+ e.preventDefault();
1194
+ core.commitEdit();
1195
+ core.selection.moveFocus(e.shiftKey ? "left" : "right", false);
1196
+ }
1197
+ },
1198
+ onBlur: () => core.commitEdit()
1199
+ });
1200
+ }
1201
+
1202
+ //#endregion
1203
+ //#region src/renderers/headerRenderer.tsx
1204
+ /**
1205
+ * Render header content based on column configuration and renderer registries
1206
+ */
1207
+ function renderHeader(options) {
1208
+ const { column, colIndex, sortDirection, sortIndex, sortable, filterable, hasFilter, coreRef, containerRef, headerRenderers, globalHeaderRenderer } = options;
1209
+ const core = coreRef.current;
1210
+ const params = {
1211
+ column,
1212
+ colIndex,
1213
+ sortDirection,
1214
+ sortIndex,
1215
+ sortable,
1216
+ filterable,
1217
+ hasFilter,
1218
+ onSort: (direction, addToExisting) => {
1219
+ if (core && sortable) core.setSort(column.colId ?? column.field, direction, addToExisting);
1220
+ },
1221
+ onFilterClick: () => {
1222
+ if (core && filterable) {
1223
+ const headerCell = containerRef.current?.querySelector(`[data-col-index="${colIndex}"]`);
1224
+ if (headerCell) {
1225
+ const rect = headerCell.getBoundingClientRect();
1226
+ core.openFilterPopup(colIndex, {
1227
+ top: rect.top,
1228
+ left: rect.left,
1229
+ width: rect.width,
1230
+ height: rect.height
1231
+ });
1232
+ }
1233
+ }
1234
+ }
1235
+ };
1236
+ if (column.headerRenderer && typeof column.headerRenderer === "string") {
1237
+ const renderer = headerRenderers[column.headerRenderer];
1238
+ if (renderer) return renderer(params);
1239
+ }
1240
+ if (globalHeaderRenderer) return globalHeaderRenderer(params);
1241
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
1242
+ className: "gp-grid-header-text",
1243
+ children: column.headerName ?? column.field
1244
+ }), /* @__PURE__ */ jsxs("span", {
1245
+ className: "gp-grid-header-icons",
1246
+ children: [sortable && /* @__PURE__ */ jsxs("span", {
1247
+ className: "gp-grid-sort-arrows",
1248
+ children: [/* @__PURE__ */ jsxs("span", {
1249
+ className: "gp-grid-sort-arrows-stack",
1250
+ children: [/* @__PURE__ */ jsx("svg", {
1251
+ className: `gp-grid-sort-arrow-up${sortDirection === "asc" ? " active" : ""}`,
1252
+ width: "8",
1253
+ height: "6",
1254
+ viewBox: "0 0 8 6",
1255
+ children: /* @__PURE__ */ jsx("path", {
1256
+ d: "M4 0L8 6H0L4 0Z",
1257
+ fill: "currentColor"
1258
+ })
1259
+ }), /* @__PURE__ */ jsx("svg", {
1260
+ className: `gp-grid-sort-arrow-down${sortDirection === "desc" ? " active" : ""}`,
1261
+ width: "8",
1262
+ height: "6",
1263
+ viewBox: "0 0 8 6",
1264
+ children: /* @__PURE__ */ jsx("path", {
1265
+ d: "M4 6L0 0H8L4 6Z",
1266
+ fill: "currentColor"
1267
+ })
1268
+ })]
1269
+ }), sortIndex !== void 0 && sortIndex > 0 && /* @__PURE__ */ jsx("span", {
1270
+ className: "gp-grid-sort-index",
1271
+ children: sortIndex
1272
+ })]
1273
+ }), filterable && /* @__PURE__ */ jsx("span", {
1274
+ className: `gp-grid-filter-icon${hasFilter ? " active" : ""}`,
1275
+ onMouseDown: (e) => {
1276
+ e.stopPropagation();
1277
+ e.preventDefault();
1278
+ params.onFilterClick();
1279
+ },
1280
+ onClick: (e) => {
1281
+ e.stopPropagation();
1282
+ },
1283
+ children: /* @__PURE__ */ jsx("svg", {
1284
+ width: "16",
1285
+ height: "16",
1286
+ viewBox: "0 0 24 24",
1287
+ fill: "currentColor",
1288
+ children: /* @__PURE__ */ jsx("path", { d: "M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" })
1289
+ })
1290
+ })]
1291
+ })] });
1292
+ }
1293
+
1294
+ //#endregion
1295
+ //#region src/Grid.tsx
1296
+ /**
1297
+ * Grid component
1298
+ * @param props - Grid component props
1299
+ * @returns Grid React component
1300
+ */
1301
+ function Grid(props) {
1302
+ injectStyles();
1303
+ const { columns, dataSource: providedDataSource, rowData, rowHeight, headerHeight = rowHeight, overscan = 3, sortingEnabled = true, darkMode = false, wheelDampening = .1, cellRenderers = {}, editRenderers = {}, headerRenderers = {}, cellRenderer, editRenderer, headerRenderer, initialWidth, initialHeight, gridRef, highlighting } = props;
1304
+ const containerRef = useRef(null);
1305
+ const coreRef = useRef(null);
1306
+ const prevDataSourceRef = useRef(null);
1307
+ const hasInitializedRef = useRef(false);
1308
+ const [state, dispatch] = useReducer(gridReducer, {
1309
+ initialWidth,
1310
+ initialHeight
1311
+ }, createInitialState);
1312
+ const totalHeaderHeight = headerHeight;
1313
+ const dataSourceCacheRef = useRef(null);
1314
+ const cache = dataSourceCacheRef.current;
1315
+ if (!cache || cache.providedDataSource !== providedDataSource || cache.rowData !== rowData) {
1316
+ if (cache?.ownsDataSource) cache.dataSource.destroy?.();
1317
+ if (providedDataSource) dataSourceCacheRef.current = {
1318
+ dataSource: providedDataSource,
1319
+ ownsDataSource: false,
1320
+ providedDataSource,
1321
+ rowData
1322
+ };
1323
+ else if (rowData) dataSourceCacheRef.current = {
1324
+ dataSource: createDataSourceFromArray$1(rowData),
1325
+ ownsDataSource: true,
1326
+ providedDataSource,
1327
+ rowData
1328
+ };
1329
+ else dataSourceCacheRef.current = {
1330
+ dataSource: createClientDataSource$1([]),
1331
+ ownsDataSource: true,
1332
+ providedDataSource,
1333
+ rowData
1334
+ };
1335
+ }
1336
+ const { dataSource, ownsDataSource } = dataSourceCacheRef.current;
1337
+ useEffect(() => {
1338
+ return () => {
1339
+ if (dataSourceCacheRef.current?.ownsDataSource) {
1340
+ dataSourceCacheRef.current.dataSource.destroy?.();
1341
+ dataSourceCacheRef.current = null;
1342
+ }
1343
+ };
1344
+ }, []);
1345
+ useEffect(() => {
1346
+ const prevDataSource = prevDataSourceRef.current;
1347
+ if (prevDataSource && prevDataSource !== dataSource) dispatch({ type: "RESET" });
1348
+ prevDataSourceRef.current = dataSource;
1349
+ }, [dataSource]);
1350
+ const visibleColumnsWithIndices = useMemo(() => columns.map((col, index) => ({
1351
+ column: col,
1352
+ originalIndex: index
1353
+ })).filter(({ column }) => !column.hidden), [columns]);
1354
+ const { positions: columnPositions, widths: columnWidths } = useMemo(() => calculateScaledColumnPositions(visibleColumnsWithIndices.map((v) => v.column), state.viewportWidth), [visibleColumnsWithIndices, state.viewportWidth]);
1355
+ const totalWidth = getTotalWidth(columnPositions);
1356
+ const { handleCellMouseDown, handleCellDoubleClick, handleFillHandleMouseDown, handleHeaderClick, handleKeyDown, handleWheel, dragState } = useInputHandler(coreRef, containerRef, columns, {
1357
+ activeCell: state.activeCell,
1358
+ selectionRange: state.selectionRange,
1359
+ editingCell: state.editingCell,
1360
+ filterPopupOpen: state.filterPopup?.isOpen ?? false,
1361
+ rowHeight,
1362
+ headerHeight: totalHeaderHeight,
1363
+ columnPositions,
1364
+ visibleColumnsWithIndices,
1365
+ slots: state.slots
1366
+ });
1367
+ useEffect(() => {
1368
+ if (hasInitializedRef.current) dispatch({ type: "RESET" });
1369
+ hasInitializedRef.current = true;
1370
+ const core = new GridCore$1({
1371
+ columns,
1372
+ dataSource,
1373
+ rowHeight,
1374
+ headerHeight: totalHeaderHeight,
1375
+ overscan,
1376
+ sortingEnabled,
1377
+ highlighting
1378
+ });
1379
+ coreRef.current = core;
1380
+ core.input.updateDeps({
1381
+ getHeaderHeight: () => totalHeaderHeight,
1382
+ getRowHeight: () => rowHeight,
1383
+ getColumnPositions: () => columnPositions,
1384
+ getColumnCount: () => visibleColumnsWithIndices.length,
1385
+ getOriginalColumnIndex: (visibleIndex) => {
1386
+ const info = visibleColumnsWithIndices[visibleIndex];
1387
+ return info ? info.originalIndex : visibleIndex;
1388
+ }
1389
+ });
1390
+ if (gridRef) gridRef.current = { core };
1391
+ const unsubscribe = core.onBatchInstruction((instructions) => {
1392
+ dispatch({
1393
+ type: "BATCH_INSTRUCTIONS",
1394
+ instructions
1395
+ });
1396
+ });
1397
+ core.initialize();
1398
+ const container = containerRef.current;
1399
+ if (container) core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1400
+ return () => {
1401
+ unsubscribe();
1402
+ core.destroy();
1403
+ coreRef.current = null;
1404
+ if (gridRef) gridRef.current = null;
1405
+ };
1406
+ }, [
1407
+ columns,
1408
+ dataSource,
1409
+ rowHeight,
1410
+ totalHeaderHeight,
1411
+ overscan,
1412
+ sortingEnabled,
1413
+ gridRef,
1414
+ highlighting
1415
+ ]);
1416
+ useEffect(() => {
1417
+ const mutableDataSource = dataSource;
1418
+ if (mutableDataSource.subscribe) return mutableDataSource.subscribe(() => {
1419
+ coreRef.current?.refresh();
1420
+ });
1421
+ }, [dataSource]);
1422
+ const handleScroll = useCallback(() => {
1423
+ const container = containerRef.current;
1424
+ const core = coreRef.current;
1425
+ if (!container || !core) return;
1426
+ core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1427
+ }, []);
1428
+ useEffect(() => {
1429
+ const container = containerRef.current;
1430
+ const core = coreRef.current;
1431
+ if (!container || !core) return;
1432
+ if (typeof ResizeObserver === "undefined") {
1433
+ handleScroll();
1434
+ return;
1435
+ }
1436
+ const resizeObserver = new ResizeObserver(() => {
1437
+ core.setViewport(container.scrollTop, container.scrollLeft, container.clientWidth, container.clientHeight);
1438
+ });
1439
+ resizeObserver.observe(container);
1440
+ handleScroll();
1441
+ return () => resizeObserver.disconnect();
1442
+ }, [handleScroll]);
1443
+ useEffect(() => {
1444
+ const container = containerRef.current;
1445
+ if (!container) return;
1446
+ const wheelHandler = (e) => {
1447
+ handleWheel(e, wheelDampening);
1448
+ };
1449
+ container.addEventListener("wheel", wheelHandler, { passive: false });
1450
+ return () => container.removeEventListener("wheel", wheelHandler);
1451
+ }, [handleWheel, wheelDampening]);
1452
+ const handleFilterApply = useCallback((colId, filter) => {
1453
+ const core = coreRef.current;
1454
+ if (core) core.setFilter(colId, filter);
1455
+ }, []);
1456
+ const handleFilterPopupClose = useCallback(() => {
1457
+ const core = coreRef.current;
1458
+ if (core) core.closeFilterPopup();
1459
+ }, []);
1460
+ const handleCellMouseEnter = useCallback((rowIndex, colIndex) => {
1461
+ coreRef.current?.input.handleCellMouseEnter(rowIndex, colIndex);
1462
+ }, []);
1463
+ const handleCellMouseLeave = useCallback(() => {
1464
+ coreRef.current?.input.handleCellMouseLeave();
1465
+ }, []);
1466
+ const slotsArray = useMemo(() => Array.from(state.slots.values()), [state.slots]);
1467
+ const fillHandlePosition = useMemo(() => {
1468
+ const { activeCell, selectionRange, slots } = state;
1469
+ if (!activeCell && !selectionRange) return null;
1470
+ let row, col;
1471
+ let minCol, maxCol;
1472
+ if (selectionRange) {
1473
+ row = Math.max(selectionRange.startRow, selectionRange.endRow);
1474
+ col = Math.max(selectionRange.startCol, selectionRange.endCol);
1475
+ minCol = Math.min(selectionRange.startCol, selectionRange.endCol);
1476
+ maxCol = Math.max(selectionRange.startCol, selectionRange.endCol);
1477
+ } else if (activeCell) {
1478
+ row = activeCell.row;
1479
+ col = activeCell.col;
1480
+ minCol = col;
1481
+ maxCol = col;
1482
+ } else return null;
1483
+ for (let c = minCol; c <= maxCol; c++) {
1484
+ const column = columns[c];
1485
+ if (!column || column.hidden) continue;
1486
+ if (column.editable !== true) return null;
1487
+ }
1488
+ const visibleIndex = visibleColumnsWithIndices.findIndex((v) => v.originalIndex === col);
1489
+ if (visibleIndex === -1) return null;
1490
+ let cellTop = null;
1491
+ for (const slot of slots.values()) if (slot.rowIndex === row) {
1492
+ cellTop = slot.translateY;
1493
+ break;
1494
+ }
1495
+ if (cellTop === null) return null;
1496
+ const cellLeft = columnPositions[visibleIndex] ?? 0;
1497
+ const cellWidth = columnWidths[visibleIndex] ?? 0;
1498
+ return {
1499
+ top: cellTop + rowHeight - 5,
1500
+ left: cellLeft + cellWidth - 20
1501
+ };
1502
+ }, [
1503
+ state.activeCell,
1504
+ state.selectionRange,
1505
+ state.slots,
1506
+ rowHeight,
1507
+ columnPositions,
1508
+ columnWidths,
1509
+ columns,
1510
+ visibleColumnsWithIndices
1511
+ ]);
1512
+ return /* @__PURE__ */ jsxs("div", {
1513
+ ref: containerRef,
1514
+ className: `gp-grid-container${darkMode ? " gp-grid-container--dark" : ""}`,
1515
+ style: {
1516
+ width: "100%",
1517
+ height: "100%",
1518
+ overflow: "auto",
1519
+ position: "relative"
1520
+ },
1521
+ onScroll: handleScroll,
1522
+ onKeyDown: handleKeyDown,
1523
+ tabIndex: 0,
1524
+ children: [/* @__PURE__ */ jsxs("div", {
1525
+ style: {
1526
+ width: Math.max(state.contentWidth, totalWidth),
1527
+ height: Math.max(state.contentHeight, totalHeaderHeight),
1528
+ position: "relative",
1529
+ minWidth: "100%"
1530
+ },
1531
+ children: [
1532
+ /* @__PURE__ */ jsx("div", {
1533
+ className: "gp-grid-header",
1534
+ style: {
1535
+ position: "sticky",
1536
+ top: 0,
1537
+ left: 0,
1538
+ height: headerHeight,
1539
+ width: Math.max(state.contentWidth, totalWidth),
1540
+ minWidth: "100%"
1541
+ },
1542
+ children: visibleColumnsWithIndices.map(({ column, originalIndex }, visibleIndex) => {
1543
+ const headerInfo = state.headers.get(originalIndex);
1544
+ return /* @__PURE__ */ jsx("div", {
1545
+ className: "gp-grid-header-cell",
1546
+ "data-col-index": originalIndex,
1547
+ style: {
1548
+ position: "absolute",
1549
+ left: `${columnPositions[visibleIndex]}px`,
1550
+ top: 0,
1551
+ width: `${columnWidths[visibleIndex]}px`,
1552
+ height: `${headerHeight}px`,
1553
+ background: "transparent"
1554
+ },
1555
+ onClick: (e) => handleHeaderClick(originalIndex, e),
1556
+ children: renderHeader({
1557
+ column,
1558
+ colIndex: originalIndex,
1559
+ sortDirection: headerInfo?.sortDirection,
1560
+ sortIndex: headerInfo?.sortIndex,
1561
+ sortable: headerInfo?.sortable ?? true,
1562
+ filterable: headerInfo?.filterable ?? true,
1563
+ hasFilter: headerInfo?.hasFilter ?? false,
1564
+ coreRef,
1565
+ containerRef,
1566
+ headerRenderers,
1567
+ globalHeaderRenderer: headerRenderer
1568
+ })
1569
+ }, column.colId ?? column.field);
1570
+ })
1571
+ }),
1572
+ slotsArray.map((slot) => {
1573
+ if (slot.rowIndex < 0) return null;
1574
+ return /* @__PURE__ */ jsx("div", {
1575
+ className: ["gp-grid-row", ...coreRef.current?.highlight?.computeRowClasses(slot.rowIndex, slot.rowData) ?? []].filter(Boolean).join(" "),
1576
+ style: {
1577
+ position: "absolute",
1578
+ top: 0,
1579
+ left: 0,
1580
+ transform: `translateY(${slot.translateY}px)`,
1581
+ width: `${Math.max(state.contentWidth, totalWidth)}px`,
1582
+ height: `${rowHeight}px`
1583
+ },
1584
+ children: visibleColumnsWithIndices.map(({ column, originalIndex }, visibleIndex) => {
1585
+ const isEditing = isCellEditing(slot.rowIndex, originalIndex, state.editingCell);
1586
+ const active = isCellActive(slot.rowIndex, originalIndex, state.activeCell);
1587
+ const selected = isCellSelected(slot.rowIndex, originalIndex, state.selectionRange);
1588
+ return /* @__PURE__ */ jsx("div", {
1589
+ className: [buildCellClasses(active, selected, isEditing, isCellInFillPreview(slot.rowIndex, originalIndex, dragState.dragType === "fill", dragState.fillSourceRange, dragState.fillTarget)), ...coreRef.current?.highlight?.computeCombinedCellClasses(slot.rowIndex, originalIndex, column, slot.rowData) ?? []].filter(Boolean).join(" "),
1590
+ style: {
1591
+ position: "absolute",
1592
+ left: `${columnPositions[visibleIndex]}px`,
1593
+ top: 0,
1594
+ width: `${columnWidths[visibleIndex]}px`,
1595
+ height: `${rowHeight}px`
1596
+ },
1597
+ onMouseDown: (e) => handleCellMouseDown(slot.rowIndex, originalIndex, e),
1598
+ onDoubleClick: () => handleCellDoubleClick(slot.rowIndex, originalIndex),
1599
+ onMouseEnter: () => handleCellMouseEnter(slot.rowIndex, originalIndex),
1600
+ onMouseLeave: handleCellMouseLeave,
1601
+ children: isEditing && state.editingCell ? renderEditCell({
1602
+ column,
1603
+ rowData: slot.rowData,
1604
+ rowIndex: slot.rowIndex,
1605
+ colIndex: originalIndex,
1606
+ initialValue: state.editingCell.initialValue,
1607
+ coreRef,
1608
+ editRenderers,
1609
+ globalEditRenderer: editRenderer
1610
+ }) : renderCell({
1611
+ column,
1612
+ rowData: slot.rowData,
1613
+ rowIndex: slot.rowIndex,
1614
+ colIndex: originalIndex,
1615
+ isActive: active,
1616
+ isSelected: selected,
1617
+ isEditing,
1618
+ cellRenderers,
1619
+ globalCellRenderer: cellRenderer
1620
+ })
1621
+ }, `${slot.slotId}-${originalIndex}`);
1622
+ })
1623
+ }, slot.slotId);
1624
+ }),
1625
+ fillHandlePosition && !state.editingCell && /* @__PURE__ */ jsx("div", {
1626
+ className: "gp-grid-fill-handle",
1627
+ style: {
1628
+ position: "absolute",
1629
+ top: fillHandlePosition.top,
1630
+ left: fillHandlePosition.left,
1631
+ zIndex: 200
1632
+ },
1633
+ onMouseDown: handleFillHandleMouseDown
1634
+ }),
1635
+ state.isLoading && /* @__PURE__ */ jsxs("div", {
1636
+ className: "gp-grid-loading",
1637
+ children: [/* @__PURE__ */ jsx("div", { className: "gp-grid-loading-spinner" }), "Loading..."]
1638
+ }),
1639
+ state.error && /* @__PURE__ */ jsxs("div", {
1640
+ className: "gp-grid-error",
1641
+ children: ["Error: ", state.error]
1642
+ }),
1643
+ !state.isLoading && !state.error && state.totalRows === 0 && /* @__PURE__ */ jsx("div", {
1644
+ className: "gp-grid-empty",
1645
+ children: "No data to display"
1646
+ })
1647
+ ]
1648
+ }), state.filterPopup?.isOpen && state.filterPopup.column && state.filterPopup.anchorRect && /* @__PURE__ */ jsx(FilterPopup, {
1649
+ column: state.filterPopup.column,
1650
+ colIndex: state.filterPopup.colIndex,
1651
+ anchorRect: state.filterPopup.anchorRect,
1652
+ distinctValues: state.filterPopup.distinctValues,
1653
+ currentFilter: state.filterPopup.currentFilter,
1654
+ onApply: handleFilterApply,
1655
+ onClose: handleFilterPopupClose
1656
+ })]
1657
+ });
1658
+ }
1659
+
1660
+ //#endregion
1661
+ export { Grid, GridCore, createClientDataSource, createDataSourceFromArray, createMutableClientDataSource, createServerDataSource };
1662
+ //# sourceMappingURL=index.js.map