@nexgrid/react 0.1.0

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,943 @@
1
+ "use client";
2
+
3
+ // src/nex-grid.tsx
4
+ import * as React4 from "react";
5
+ import {
6
+ DENSITIES,
7
+ PAGE_SIZES,
8
+ buildQueryUrl,
9
+ downloadCsv,
10
+ downloadExcel,
11
+ fetchAllPages,
12
+ filePrefixFromCaption,
13
+ formatMessage,
14
+ getColumnId as getColumnId2,
15
+ getColumnTitle,
16
+ getPageNumbers,
17
+ getRecordRange,
18
+ initialHiddenColumns,
19
+ isHideable,
20
+ isPageSize,
21
+ isSortable,
22
+ primarySort,
23
+ resolveLocale,
24
+ serialNumber,
25
+ timestampedFilename,
26
+ toExportColumns,
27
+ totalPagesFor,
28
+ visibleColumns,
29
+ withPage,
30
+ withPageSize,
31
+ withSearch,
32
+ withToggledSort
33
+ } from "@nexgrid/core";
34
+
35
+ // src/icons.tsx
36
+ import { jsx, jsxs } from "react/jsx-runtime";
37
+ function Icon({
38
+ className,
39
+ size,
40
+ children
41
+ }) {
42
+ return /* @__PURE__ */ jsx(
43
+ "svg",
44
+ {
45
+ xmlns: "http://www.w3.org/2000/svg",
46
+ viewBox: "0 0 24 24",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ strokeWidth: 2,
50
+ strokeLinecap: "round",
51
+ strokeLinejoin: "round",
52
+ "aria-hidden": "true",
53
+ focusable: "false",
54
+ className,
55
+ width: size,
56
+ height: size,
57
+ children
58
+ }
59
+ );
60
+ }
61
+ function SearchIcon(props) {
62
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
63
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "8" }),
64
+ /* @__PURE__ */ jsx("path", { d: "m21 21-4.3-4.3" })
65
+ ] });
66
+ }
67
+ function XIcon(props) {
68
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
69
+ /* @__PURE__ */ jsx("path", { d: "M18 6 6 18" }),
70
+ /* @__PURE__ */ jsx("path", { d: "m6 6 12 12" })
71
+ ] });
72
+ }
73
+ function SlidersIcon(props) {
74
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
75
+ /* @__PURE__ */ jsx("line", { x1: "21", x2: "14", y1: "4", y2: "4" }),
76
+ /* @__PURE__ */ jsx("line", { x1: "10", x2: "3", y1: "4", y2: "4" }),
77
+ /* @__PURE__ */ jsx("line", { x1: "21", x2: "12", y1: "12", y2: "12" }),
78
+ /* @__PURE__ */ jsx("line", { x1: "8", x2: "3", y1: "12", y2: "12" }),
79
+ /* @__PURE__ */ jsx("line", { x1: "21", x2: "16", y1: "20", y2: "20" }),
80
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "3", y1: "20", y2: "20" }),
81
+ /* @__PURE__ */ jsx("line", { x1: "14", x2: "14", y1: "2", y2: "6" }),
82
+ /* @__PURE__ */ jsx("line", { x1: "8", x2: "8", y1: "10", y2: "14" }),
83
+ /* @__PURE__ */ jsx("line", { x1: "16", x2: "16", y1: "18", y2: "22" })
84
+ ] });
85
+ }
86
+ function FilterIcon(props) {
87
+ return /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("polygon", { points: "22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3" }) });
88
+ }
89
+ function DownloadIcon(props) {
90
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
91
+ /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
92
+ /* @__PURE__ */ jsx("polyline", { points: "7 10 12 15 17 10" }),
93
+ /* @__PURE__ */ jsx("line", { x1: "12", x2: "12", y1: "15", y2: "3" })
94
+ ] });
95
+ }
96
+ function FileSpreadsheetIcon(props) {
97
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
98
+ /* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
99
+ /* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
100
+ /* @__PURE__ */ jsx("path", { d: "M8 13h2" }),
101
+ /* @__PURE__ */ jsx("path", { d: "M14 13h2" }),
102
+ /* @__PURE__ */ jsx("path", { d: "M8 17h2" }),
103
+ /* @__PURE__ */ jsx("path", { d: "M14 17h2" })
104
+ ] });
105
+ }
106
+ function FileTextIcon(props) {
107
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
108
+ /* @__PURE__ */ jsx("path", { d: "M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" }),
109
+ /* @__PURE__ */ jsx("path", { d: "M14 2v4a2 2 0 0 0 2 2h4" }),
110
+ /* @__PURE__ */ jsx("path", { d: "M10 9H8" }),
111
+ /* @__PURE__ */ jsx("path", { d: "M16 13H8" }),
112
+ /* @__PURE__ */ jsx("path", { d: "M16 17H8" })
113
+ ] });
114
+ }
115
+ function ChevronLeftIcon(props) {
116
+ return /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "m15 18-6-6 6-6" }) });
117
+ }
118
+ function ChevronRightIcon(props) {
119
+ return /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "m9 18 6-6-6-6" }) });
120
+ }
121
+ function ArrowUpIcon(props) {
122
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
123
+ /* @__PURE__ */ jsx("path", { d: "m5 12 7-7 7 7" }),
124
+ /* @__PURE__ */ jsx("path", { d: "M12 19V5" })
125
+ ] });
126
+ }
127
+ function ArrowDownIcon(props) {
128
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
129
+ /* @__PURE__ */ jsx("path", { d: "M12 5v14" }),
130
+ /* @__PURE__ */ jsx("path", { d: "m19 12-7 7-7-7" })
131
+ ] });
132
+ }
133
+ function ArrowUpDownIcon(props) {
134
+ return /* @__PURE__ */ jsxs(Icon, { ...props, children: [
135
+ /* @__PURE__ */ jsx("path", { d: "m21 16-4 4-4-4" }),
136
+ /* @__PURE__ */ jsx("path", { d: "M17 20V4" }),
137
+ /* @__PURE__ */ jsx("path", { d: "m3 8 4-4 4 4" }),
138
+ /* @__PURE__ */ jsx("path", { d: "M7 4v16" })
139
+ ] });
140
+ }
141
+ function CheckIcon(props) {
142
+ return /* @__PURE__ */ jsx(Icon, { ...props, children: /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }) });
143
+ }
144
+
145
+ // src/render.tsx
146
+ import * as React from "react";
147
+ import {
148
+ getCellText,
149
+ getCellValue,
150
+ getColumnId
151
+ } from "@nexgrid/core";
152
+ import { jsx as jsx2 } from "react/jsx-runtime";
153
+ var DEFAULT_MIN_WIDTH = 120;
154
+ function renderColumnHeader(col) {
155
+ const header = col.header;
156
+ if (typeof header === "function") return header({});
157
+ if (typeof header === "string") return header;
158
+ return getColumnId(col);
159
+ }
160
+ function renderCellContent(col, row, labels) {
161
+ const cell = col.cell;
162
+ if (typeof cell === "function") {
163
+ return cell({ row: { original: row }, getValue: () => getCellValue(col, row) });
164
+ }
165
+ return getCellText(getCellValue(col, row), labels);
166
+ }
167
+ function headerCellStyle(col) {
168
+ const meta = col.meta;
169
+ const style = meta?.width === void 0 ? { minWidth: `${meta?.minWidth ?? DEFAULT_MIN_WIDTH}px` } : { width: `${meta.width}px` };
170
+ if (meta?.align !== void 0) style.textAlign = meta.align;
171
+ return style;
172
+ }
173
+ function bodyCellStyle(col) {
174
+ const align = col.meta?.align;
175
+ return align === void 0 ? void 0 : { textAlign: align };
176
+ }
177
+ function headerInnerClass(col) {
178
+ const align = col.meta?.align;
179
+ if (align === "center") return "nxg-th-inner nxg-th-inner--center";
180
+ if (align === "right") return "nxg-th-inner nxg-th-inner--right";
181
+ return "nxg-th-inner";
182
+ }
183
+ function renderTemplate(template, values) {
184
+ const parts = [];
185
+ const pattern = /\{(\w+)\}/g;
186
+ let cursor = 0;
187
+ for (let match = pattern.exec(template); match !== null; match = pattern.exec(template)) {
188
+ const key = match[1];
189
+ const replacement = key === void 0 ? void 0 : values[key];
190
+ if (replacement === void 0) continue;
191
+ if (match.index > cursor) parts.push(template.slice(cursor, match.index));
192
+ parts.push(replacement);
193
+ cursor = match.index + match[0].length;
194
+ }
195
+ if (cursor < template.length) parts.push(template.slice(cursor));
196
+ return parts.map((part, index) => /* @__PURE__ */ jsx2(React.Fragment, { children: part }, index));
197
+ }
198
+
199
+ // src/use-debounced-search.ts
200
+ import * as React2 from "react";
201
+ var SEARCH_DEBOUNCE_MS = 350;
202
+ function useDebouncedSearch(value, commit, delayMs = SEARCH_DEBOUNCE_MS) {
203
+ const [text, setText] = React2.useState(value);
204
+ const commitRef = React2.useRef(commit);
205
+ React2.useEffect(() => {
206
+ commitRef.current = commit;
207
+ });
208
+ const lastExternal = React2.useRef(value);
209
+ React2.useEffect(() => {
210
+ if (lastExternal.current === value) return;
211
+ lastExternal.current = value;
212
+ setText(value);
213
+ }, [value]);
214
+ React2.useEffect(() => {
215
+ if (text === value) return;
216
+ const timer = setTimeout(() => commitRef.current(text), delayMs);
217
+ return () => clearTimeout(timer);
218
+ }, [text, value, delayMs]);
219
+ return [text, setText];
220
+ }
221
+
222
+ // src/use-dropdown.ts
223
+ import * as React3 from "react";
224
+ function useDropdown() {
225
+ const [isOpen, setIsOpen] = React3.useState(false);
226
+ const containerRef = React3.useRef(null);
227
+ const triggerRef = React3.useRef(null);
228
+ React3.useEffect(() => {
229
+ if (!isOpen) return;
230
+ const onPointerDown = (event) => {
231
+ const container = containerRef.current;
232
+ const target = event.target;
233
+ if (container && target instanceof Node && container.contains(target)) return;
234
+ setIsOpen(false);
235
+ };
236
+ const onKeyDown = (event) => {
237
+ if (event.key !== "Escape") return;
238
+ setIsOpen(false);
239
+ triggerRef.current?.focus();
240
+ };
241
+ document.addEventListener("pointerdown", onPointerDown, true);
242
+ document.addEventListener("keydown", onKeyDown);
243
+ return () => {
244
+ document.removeEventListener("pointerdown", onPointerDown, true);
245
+ document.removeEventListener("keydown", onKeyDown);
246
+ };
247
+ }, [isOpen]);
248
+ const toggle = React3.useCallback(() => setIsOpen((open) => !open), []);
249
+ const close = React3.useCallback(() => setIsOpen(false), []);
250
+ return { isOpen, toggle, close, containerRef, triggerRef };
251
+ }
252
+
253
+ // src/nex-grid.tsx
254
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
255
+ function defaultRowId(row) {
256
+ const record = row;
257
+ const id = record === null || record === void 0 ? void 0 : record["id"];
258
+ return String(id ?? row);
259
+ }
260
+ var DENSITY_LABEL_KEY = {
261
+ compact: "densityCompact",
262
+ default: "densityDefault",
263
+ comfortable: "densityComfortable"
264
+ };
265
+ function NexGrid({
266
+ columns,
267
+ data,
268
+ total,
269
+ query,
270
+ onQueryChange,
271
+ caption,
272
+ density: initialDensity = "default",
273
+ isLoading = false,
274
+ error = false,
275
+ onRetry,
276
+ enableSelection = false,
277
+ onSelectionChange,
278
+ enableSearch = true,
279
+ searchPlaceholder,
280
+ toolbarActions,
281
+ onRowClick,
282
+ getRowId = defaultRowId,
283
+ className,
284
+ showSerialNumber = true,
285
+ enableExport = true,
286
+ exportFileName,
287
+ onExportAll,
288
+ fetchEndpoint,
289
+ badgeRules,
290
+ locale: localeOverrides,
291
+ onNotify,
292
+ theme = "light"
293
+ }) {
294
+ const locale = resolveLocale(localeOverrides);
295
+ const boolLabels = { yes: locale.booleanYes, no: locale.booleanNo };
296
+ const [density, setDensity] = React4.useState(initialDensity);
297
+ const [hiddenCols, setHiddenCols] = React4.useState(
298
+ () => initialHiddenColumns(columns)
299
+ );
300
+ const [selectedIds, setSelectedIds] = React4.useState(
301
+ () => /* @__PURE__ */ new Set()
302
+ );
303
+ const [isExporting, setIsExporting] = React4.useState(false);
304
+ const columnsMenu = useDropdown();
305
+ const densityMenu = useDropdown();
306
+ const exportMenu = useDropdown();
307
+ const instanceId = React4.useId();
308
+ const columnsButtonId = `${instanceId}-columns`;
309
+ const densityButtonId = `${instanceId}-density`;
310
+ const exportButtonId = `${instanceId}-export`;
311
+ const jumpInputId = `${instanceId}-jump`;
312
+ const notify = (type, message) => {
313
+ onNotify?.({ type, message });
314
+ };
315
+ const currentPage = Math.max(1, query.page);
316
+ const pageSize = query.pageSize;
317
+ const totalPages = totalPagesFor(total, pageSize);
318
+ const range = getRecordRange(currentPage, pageSize, total);
319
+ const sort = primarySort(query);
320
+ const visible = React4.useMemo(
321
+ () => visibleColumns(columns, hiddenCols),
322
+ [columns, hiddenCols]
323
+ );
324
+ const hideable = React4.useMemo(() => columns.filter(isHideable), [columns]);
325
+ const pageItems = React4.useMemo(
326
+ () => getPageNumbers(currentPage, totalPages),
327
+ [currentPage, totalPages]
328
+ );
329
+ const pageRowIds = React4.useMemo(() => data.map((row) => getRowId(row)), [data, getRowId]);
330
+ const columnCount = visible.length + (showSerialNumber ? 1 : 0) + (enableSelection ? 1 : 0);
331
+ const [searchText, setSearchText] = useDebouncedSearch(query.q ?? "", (text) => {
332
+ onQueryChange(withSearch(query, text));
333
+ });
334
+ const [jumpText, setJumpText] = React4.useState(() => String(currentPage));
335
+ React4.useEffect(() => setJumpText(String(currentPage)), [currentPage]);
336
+ const submitJump = (event) => {
337
+ event?.preventDefault();
338
+ const parsed = Number.parseInt(jumpText, 10);
339
+ if (Number.isFinite(parsed) && parsed >= 1 && parsed <= totalPages) {
340
+ setJumpText(String(parsed));
341
+ if (parsed !== currentPage) onQueryChange(withPage(query, parsed, totalPages));
342
+ return;
343
+ }
344
+ setJumpText(String(currentPage));
345
+ };
346
+ const toggleSort = (columnId) => {
347
+ onQueryChange(withToggledSort(query, columnId));
348
+ };
349
+ const selectedOnPage = pageRowIds.filter((id) => selectedIds.has(id)).length;
350
+ const allPageSelected = pageRowIds.length > 0 && selectedOnPage === pageRowIds.length;
351
+ const somePageSelected = selectedOnPage > 0 && !allPageSelected;
352
+ const commitSelection = (next) => {
353
+ setSelectedIds(next);
354
+ onSelectionChange?.(Array.from(next), false);
355
+ };
356
+ const toggleSelectAll = () => {
357
+ const next = new Set(selectedIds);
358
+ for (const id of pageRowIds) {
359
+ if (allPageSelected) next.delete(id);
360
+ else next.add(id);
361
+ }
362
+ commitSelection(next);
363
+ };
364
+ const toggleSelectRow = (id) => {
365
+ const next = new Set(selectedIds);
366
+ if (next.has(id)) next.delete(id);
367
+ else next.add(id);
368
+ commitSelection(next);
369
+ };
370
+ const toggleColumn = (columnId) => {
371
+ setHiddenCols((prev) => ({ ...prev, [columnId]: prev[columnId] !== true }));
372
+ };
373
+ const collectExportRows = async () => {
374
+ if (data.length >= total || !fetchEndpoint) return data;
375
+ notify("info", formatMessage(locale.exportFetchingAll, { total: total.toLocaleString() }));
376
+ try {
377
+ const result = await fetchAllPages(async (page, size) => {
378
+ const url = buildQueryUrl(fetchEndpoint, {
379
+ ...query,
380
+ page,
381
+ pageSize: isPageSize(size) ? size : query.pageSize
382
+ });
383
+ const response = await fetch(url, { cache: "no-store" });
384
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
385
+ return await response.json();
386
+ });
387
+ return result.items;
388
+ } catch {
389
+ notify("error", locale.exportFetchFailed);
390
+ return data;
391
+ }
392
+ };
393
+ const handleExport = async (format) => {
394
+ exportMenu.close();
395
+ if (onExportAll) {
396
+ void onExportAll();
397
+ return;
398
+ }
399
+ setIsExporting(true);
400
+ try {
401
+ const rows = await collectExportRows();
402
+ if (rows.length === 0) {
403
+ notify("error", locale.exportNoData);
404
+ return;
405
+ }
406
+ const exportColumns = toExportColumns(visible, boolLabels);
407
+ const prefix = exportFileName ?? filePrefixFromCaption(caption);
408
+ if (format === "excel") {
409
+ const count = downloadExcel({
410
+ filename: prefix,
411
+ caption,
412
+ rows,
413
+ columns: exportColumns,
414
+ badgeRules,
415
+ serialHeader: locale.serialHeader
416
+ });
417
+ notify(
418
+ "success",
419
+ formatMessage(locale.exportExcelSuccess, { count: count.toLocaleString() })
420
+ );
421
+ } else {
422
+ const count = downloadCsv(timestampedFilename(prefix), rows, exportColumns);
423
+ notify(
424
+ "success",
425
+ formatMessage(locale.exportCsvSuccess, { count: count.toLocaleString() })
426
+ );
427
+ }
428
+ } finally {
429
+ setIsExporting(false);
430
+ }
431
+ };
432
+ const rootClassName = [
433
+ "nxg-root",
434
+ theme === "dark" ? "nxg-dark" : theme === "auto" ? "nxg-auto" : void 0,
435
+ className
436
+ ].filter((part) => Boolean(part)).join(" ");
437
+ if (error) {
438
+ return /* @__PURE__ */ jsx3("div", { className: rootClassName, "data-density": density, children: /* @__PURE__ */ jsxs2("div", { className: "nxg-state-card", children: [
439
+ /* @__PURE__ */ jsx3("p", { className: "nxg-state-text", children: locale.errorText }),
440
+ onRetry ? /* @__PURE__ */ jsx3("button", { type: "button", className: "nxg-btn", onClick: onRetry, children: locale.retryButton }) : null
441
+ ] }) });
442
+ }
443
+ const rowIsClickable = typeof onRowClick === "function";
444
+ const activateRow = (row) => () => {
445
+ onRowClick?.(row);
446
+ };
447
+ const rowKeyDown = (row) => (event) => {
448
+ if (event.key !== "Enter") return;
449
+ event.preventDefault();
450
+ onRowClick?.(row);
451
+ };
452
+ return /* @__PURE__ */ jsxs2("div", { className: rootClassName, "data-density": density, children: [
453
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-toolbar", children: [
454
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-toolbar-group", children: [
455
+ enableSearch ? /* @__PURE__ */ jsxs2("div", { className: "nxg-search", children: [
456
+ /* @__PURE__ */ jsx3(SearchIcon, { className: "nxg-search-icon" }),
457
+ /* @__PURE__ */ jsx3(
458
+ "input",
459
+ {
460
+ type: "search",
461
+ className: "nxg-search-input",
462
+ value: searchText,
463
+ onChange: (event) => setSearchText(event.target.value),
464
+ placeholder: searchPlaceholder ?? locale.searchPlaceholder,
465
+ "aria-label": `Search ${caption}`
466
+ }
467
+ ),
468
+ searchText ? /* @__PURE__ */ jsxs2(
469
+ "button",
470
+ {
471
+ type: "button",
472
+ className: "nxg-search-clear",
473
+ onClick: () => setSearchText(""),
474
+ "aria-label": locale.clearSearch,
475
+ children: [
476
+ /* @__PURE__ */ jsx3(XIcon, { className: "nxg-icon" }),
477
+ /* @__PURE__ */ jsx3("span", { className: "nxg-sr-only", children: locale.clearSearch })
478
+ ]
479
+ }
480
+ ) : null
481
+ ] }) : null,
482
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-menu-wrap", ref: columnsMenu.containerRef, children: [
483
+ /* @__PURE__ */ jsxs2(
484
+ "button",
485
+ {
486
+ type: "button",
487
+ id: columnsButtonId,
488
+ className: "nxg-btn",
489
+ ref: columnsMenu.triggerRef,
490
+ "aria-haspopup": "menu",
491
+ "aria-expanded": columnsMenu.isOpen,
492
+ onClick: columnsMenu.toggle,
493
+ children: [
494
+ /* @__PURE__ */ jsx3(SlidersIcon, { className: "nxg-icon" }),
495
+ /* @__PURE__ */ jsx3("span", { children: locale.columnsButton })
496
+ ]
497
+ }
498
+ ),
499
+ columnsMenu.isOpen ? /* @__PURE__ */ jsxs2("div", { className: "nxg-menu", role: "menu", "aria-labelledby": columnsButtonId, children: [
500
+ /* @__PURE__ */ jsx3("div", { className: "nxg-menu-label", children: locale.toggleColumnsLabel }),
501
+ /* @__PURE__ */ jsx3("div", { className: "nxg-menu-separator" }),
502
+ hideable.map((col) => {
503
+ const id = getColumnId2(col);
504
+ const checked = hiddenCols[id] !== true;
505
+ return /* @__PURE__ */ jsxs2(
506
+ "button",
507
+ {
508
+ type: "button",
509
+ className: "nxg-menu-item",
510
+ role: "menuitemcheckbox",
511
+ "aria-checked": checked,
512
+ onClick: () => toggleColumn(id),
513
+ children: [
514
+ /* @__PURE__ */ jsx3(CheckIcon, { className: "nxg-check" }),
515
+ /* @__PURE__ */ jsx3("span", { children: getColumnTitle(col) || id })
516
+ ]
517
+ },
518
+ id
519
+ );
520
+ })
521
+ ] }) : null
522
+ ] }),
523
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-menu-wrap", ref: densityMenu.containerRef, children: [
524
+ /* @__PURE__ */ jsxs2(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ id: densityButtonId,
529
+ className: "nxg-btn nxg-capitalize",
530
+ ref: densityMenu.triggerRef,
531
+ "aria-haspopup": "menu",
532
+ "aria-expanded": densityMenu.isOpen,
533
+ onClick: densityMenu.toggle,
534
+ children: [
535
+ /* @__PURE__ */ jsx3(FilterIcon, { className: "nxg-icon" }),
536
+ /* @__PURE__ */ jsx3("span", { children: formatMessage(locale.densityButton, { density }) })
537
+ ]
538
+ }
539
+ ),
540
+ densityMenu.isOpen ? /* @__PURE__ */ jsx3("div", { className: "nxg-menu", role: "menu", "aria-labelledby": densityButtonId, children: DENSITIES.map((option) => /* @__PURE__ */ jsxs2(
541
+ "button",
542
+ {
543
+ type: "button",
544
+ className: "nxg-menu-item",
545
+ role: "menuitemcheckbox",
546
+ "aria-checked": density === option,
547
+ onClick: () => {
548
+ setDensity(option);
549
+ densityMenu.close();
550
+ },
551
+ children: [
552
+ /* @__PURE__ */ jsx3(CheckIcon, { className: "nxg-check" }),
553
+ /* @__PURE__ */ jsx3("span", { children: locale[DENSITY_LABEL_KEY[option]] })
554
+ ]
555
+ },
556
+ option
557
+ )) }) : null
558
+ ] })
559
+ ] }),
560
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-toolbar-group nxg-toolbar-group--end", children: [
561
+ enableExport ? /* @__PURE__ */ jsxs2("div", { className: "nxg-menu-wrap", ref: exportMenu.containerRef, children: [
562
+ /* @__PURE__ */ jsxs2(
563
+ "button",
564
+ {
565
+ type: "button",
566
+ id: exportButtonId,
567
+ className: "nxg-btn nxg-btn--export",
568
+ ref: exportMenu.triggerRef,
569
+ disabled: isExporting,
570
+ "aria-haspopup": "menu",
571
+ "aria-expanded": exportMenu.isOpen,
572
+ onClick: exportMenu.toggle,
573
+ children: [
574
+ /* @__PURE__ */ jsx3(DownloadIcon, { className: "nxg-icon" }),
575
+ /* @__PURE__ */ jsx3("span", { children: isExporting ? locale.exportingButton : locale.exportButton })
576
+ ]
577
+ }
578
+ ),
579
+ exportMenu.isOpen ? /* @__PURE__ */ jsxs2(
580
+ "div",
581
+ {
582
+ className: "nxg-menu nxg-menu--end",
583
+ role: "menu",
584
+ "aria-labelledby": exportButtonId,
585
+ children: [
586
+ /* @__PURE__ */ jsxs2(
587
+ "button",
588
+ {
589
+ type: "button",
590
+ className: "nxg-menu-item",
591
+ role: "menuitem",
592
+ onClick: () => void handleExport("excel"),
593
+ children: [
594
+ /* @__PURE__ */ jsx3(FileSpreadsheetIcon, { className: "nxg-icon--excel" }),
595
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-menu-item-title", children: [
596
+ /* @__PURE__ */ jsx3("strong", { children: locale.exportExcelTitle }),
597
+ /* @__PURE__ */ jsx3("small", { children: locale.exportExcelSubtitle })
598
+ ] })
599
+ ]
600
+ }
601
+ ),
602
+ /* @__PURE__ */ jsxs2(
603
+ "button",
604
+ {
605
+ type: "button",
606
+ className: "nxg-menu-item",
607
+ role: "menuitem",
608
+ onClick: () => void handleExport("csv"),
609
+ children: [
610
+ /* @__PURE__ */ jsx3(FileTextIcon, { className: "nxg-icon--csv" }),
611
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-menu-item-title", children: [
612
+ /* @__PURE__ */ jsx3("strong", { children: locale.exportCsvTitle }),
613
+ /* @__PURE__ */ jsx3("small", { children: locale.exportCsvSubtitle })
614
+ ] })
615
+ ]
616
+ }
617
+ )
618
+ ]
619
+ }
620
+ ) : null
621
+ ] }) : null,
622
+ toolbarActions
623
+ ] })
624
+ ] }),
625
+ /* @__PURE__ */ jsx3("div", { className: "nxg-table-wrap", children: /* @__PURE__ */ jsxs2("table", { className: "nxg-table", "aria-label": caption, children: [
626
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
627
+ showSerialNumber ? /* @__PURE__ */ jsx3("th", { className: "nxg-th nxg-th--serial", scope: "col", children: locale.serialHeader }) : null,
628
+ enableSelection ? /* @__PURE__ */ jsx3("th", { className: "nxg-th nxg-th--select", scope: "col", children: /* @__PURE__ */ jsx3(
629
+ "input",
630
+ {
631
+ type: "checkbox",
632
+ className: "nxg-checkbox",
633
+ checked: allPageSelected,
634
+ ref: (el) => {
635
+ if (el) el.indeterminate = somePageSelected;
636
+ },
637
+ onChange: toggleSelectAll,
638
+ "aria-label": locale.selectAllLabel
639
+ }
640
+ ) }) : null,
641
+ visible.map((col, index) => {
642
+ const id = getColumnId2(col);
643
+ const sortable = isSortable(col);
644
+ const sorted = sortable && id !== "" && sort?.field === id;
645
+ const title = getColumnTitle(col) || id;
646
+ return /* @__PURE__ */ jsx3(
647
+ "th",
648
+ {
649
+ scope: "col",
650
+ className: sortable ? "nxg-th nxg-th--sortable" : "nxg-th",
651
+ style: headerCellStyle(col),
652
+ "aria-sort": sorted ? sort?.dir === "asc" ? "ascending" : "descending" : sortable ? "none" : void 0,
653
+ "aria-label": title || void 0,
654
+ tabIndex: sortable ? 0 : void 0,
655
+ onClick: sortable ? () => toggleSort(id) : void 0,
656
+ onKeyDown: sortable ? (event) => {
657
+ if (event.key !== "Enter" && event.key !== " ") return;
658
+ event.preventDefault();
659
+ toggleSort(id);
660
+ } : void 0,
661
+ children: /* @__PURE__ */ jsxs2("div", { className: headerInnerClass(col), children: [
662
+ /* @__PURE__ */ jsx3("span", { children: renderColumnHeader(col) }),
663
+ sortable ? /* @__PURE__ */ jsx3("span", { children: sorted ? sort?.dir === "asc" ? /* @__PURE__ */ jsx3(ArrowUpIcon, { className: "nxg-sort-icon" }) : /* @__PURE__ */ jsx3(ArrowDownIcon, { className: "nxg-sort-icon" }) : /* @__PURE__ */ jsx3(ArrowUpDownIcon, { className: "nxg-sort-icon nxg-sort-icon--idle" }) }) : null
664
+ ] })
665
+ },
666
+ id || `col-${index}`
667
+ );
668
+ })
669
+ ] }) }),
670
+ /* @__PURE__ */ jsx3("tbody", { children: isLoading ? /* @__PURE__ */ jsx3("tr", { children: /* @__PURE__ */ jsxs2("td", { className: "nxg-state", colSpan: Math.max(1, columnCount), children: [
671
+ /* @__PURE__ */ jsx3("span", { className: "nxg-spinner", "aria-hidden": "true" }),
672
+ /* @__PURE__ */ jsx3("div", { "aria-live": "polite", children: locale.loadingText })
673
+ ] }) }) : data.length === 0 ? /* @__PURE__ */ jsx3("tr", { children: /* @__PURE__ */ jsx3("td", { className: "nxg-state", colSpan: Math.max(1, columnCount), children: locale.emptyText }) }) : data.map((row, index) => {
674
+ const id = pageRowIds[index] ?? String(index);
675
+ const isSelected = selectedIds.has(id);
676
+ return /* @__PURE__ */ jsxs2(
677
+ "tr",
678
+ {
679
+ className: [
680
+ "nxg-row",
681
+ isSelected ? "nxg-row--selected" : void 0,
682
+ rowIsClickable ? "nxg-row--clickable" : void 0
683
+ ].filter(Boolean).join(" "),
684
+ onClick: rowIsClickable ? activateRow(row) : void 0,
685
+ onKeyDown: rowIsClickable ? rowKeyDown(row) : void 0,
686
+ tabIndex: rowIsClickable ? 0 : void 0,
687
+ children: [
688
+ showSerialNumber ? /* @__PURE__ */ jsx3("td", { className: "nxg-td nxg-td--serial", children: serialNumber(currentPage, pageSize, index) }) : null,
689
+ enableSelection ? /* @__PURE__ */ jsx3(
690
+ "td",
691
+ {
692
+ className: "nxg-td nxg-td--select",
693
+ onClick: (event) => event.stopPropagation(),
694
+ children: /* @__PURE__ */ jsx3(
695
+ "input",
696
+ {
697
+ type: "checkbox",
698
+ className: "nxg-checkbox",
699
+ checked: isSelected,
700
+ onChange: () => toggleSelectRow(id),
701
+ onClick: (event) => event.stopPropagation(),
702
+ "aria-label": formatMessage(locale.selectRowLabel, { id })
703
+ }
704
+ )
705
+ }
706
+ ) : null,
707
+ visible.map((col, colIndex) => /* @__PURE__ */ jsx3(
708
+ "td",
709
+ {
710
+ className: "nxg-td",
711
+ style: bodyCellStyle(col),
712
+ children: renderCellContent(col, row, boolLabels)
713
+ },
714
+ getColumnId2(col) || `col-${colIndex}`
715
+ ))
716
+ ]
717
+ },
718
+ id || index
719
+ );
720
+ }) })
721
+ ] }) }),
722
+ /* @__PURE__ */ jsx3("div", { className: "nxg-cards", children: isLoading ? /* @__PURE__ */ jsx3("div", { className: "nxg-card", children: /* @__PURE__ */ jsxs2("div", { className: "nxg-state", children: [
723
+ /* @__PURE__ */ jsx3("span", { className: "nxg-spinner", "aria-hidden": "true" }),
724
+ /* @__PURE__ */ jsx3("div", { "aria-live": "polite", children: locale.loadingText })
725
+ ] }) }) : data.length === 0 ? /* @__PURE__ */ jsx3("div", { className: "nxg-card", children: /* @__PURE__ */ jsx3("div", { className: "nxg-state", children: locale.emptyText }) }) : data.map((row, index) => {
726
+ const id = pageRowIds[index] ?? String(index);
727
+ const isSelected = selectedIds.has(id);
728
+ return /* @__PURE__ */ jsxs2(
729
+ "div",
730
+ {
731
+ className: [
732
+ "nxg-card",
733
+ isSelected ? "nxg-card--selected" : void 0,
734
+ rowIsClickable ? "nxg-card--clickable" : void 0
735
+ ].filter(Boolean).join(" "),
736
+ onClick: rowIsClickable ? activateRow(row) : void 0,
737
+ onKeyDown: rowIsClickable ? rowKeyDown(row) : void 0,
738
+ tabIndex: rowIsClickable ? 0 : void 0,
739
+ children: [
740
+ showSerialNumber || enableSelection ? /* @__PURE__ */ jsxs2("div", { className: "nxg-card-head", children: [
741
+ showSerialNumber ? /* @__PURE__ */ jsxs2("span", { className: "nxg-card-serial", children: [
742
+ "#",
743
+ serialNumber(currentPage, pageSize, index)
744
+ ] }) : null,
745
+ enableSelection ? /* @__PURE__ */ jsx3(
746
+ "span",
747
+ {
748
+ className: "nxg-card-select",
749
+ onClick: (event) => event.stopPropagation(),
750
+ children: /* @__PURE__ */ jsx3(
751
+ "input",
752
+ {
753
+ type: "checkbox",
754
+ className: "nxg-checkbox",
755
+ checked: isSelected,
756
+ onChange: () => toggleSelectRow(id),
757
+ onClick: (event) => event.stopPropagation(),
758
+ "aria-label": formatMessage(locale.selectRowLabel, { id })
759
+ }
760
+ )
761
+ }
762
+ ) : null
763
+ ] }) : null,
764
+ /* @__PURE__ */ jsx3("dl", { className: "nxg-card-rows", children: visible.map((col, colIndex) => {
765
+ const colId = getColumnId2(col);
766
+ return /* @__PURE__ */ jsxs2("div", { className: "nxg-card-row", children: [
767
+ /* @__PURE__ */ jsx3("dt", { children: getColumnTitle(col) || colId }),
768
+ /* @__PURE__ */ jsx3("dd", { children: renderCellContent(col, row, boolLabels) })
769
+ ] }, colId || `col-${colIndex}`);
770
+ }) })
771
+ ]
772
+ },
773
+ id || index
774
+ );
775
+ }) }),
776
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-footer", children: [
777
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-range", children: [
778
+ /* @__PURE__ */ jsx3("span", { children: renderTemplate(locale.showingRange, {
779
+ start: /* @__PURE__ */ jsx3("strong", { children: range.start }),
780
+ end: /* @__PURE__ */ jsx3("strong", { children: range.end }),
781
+ total: /* @__PURE__ */ jsx3("strong", { className: "nxg-range-total", children: total.toLocaleString() })
782
+ }) }),
783
+ selectedIds.size > 0 ? /* @__PURE__ */ jsx3("span", { className: "nxg-selected-badge", children: formatMessage(locale.selectedBadge, { count: selectedIds.size }) }) : null
784
+ ] }),
785
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-pagination", children: [
786
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-rows-per-page", children: [
787
+ /* @__PURE__ */ jsx3("span", { children: locale.rowsPerPage }),
788
+ /* @__PURE__ */ jsx3(
789
+ "select",
790
+ {
791
+ className: "nxg-rows-select",
792
+ value: String(pageSize),
793
+ onChange: (event) => {
794
+ onQueryChange(withPageSize(query, Number.parseInt(event.target.value, 10)));
795
+ },
796
+ "aria-label": locale.rowsPerPage,
797
+ children: PAGE_SIZES.map((size) => /* @__PURE__ */ jsx3("option", { value: size, children: size }, size))
798
+ }
799
+ )
800
+ ] }),
801
+ /* @__PURE__ */ jsxs2("div", { className: "nxg-pager", children: [
802
+ /* @__PURE__ */ jsxs2(
803
+ "button",
804
+ {
805
+ type: "button",
806
+ className: "nxg-page-nav",
807
+ disabled: currentPage <= 1,
808
+ onClick: () => onQueryChange(withPage(query, currentPage - 1, totalPages)),
809
+ "aria-label": locale.previousPage,
810
+ children: [
811
+ /* @__PURE__ */ jsx3(ChevronLeftIcon, { size: 16 }),
812
+ /* @__PURE__ */ jsx3("span", { className: "nxg-sr-only", children: locale.previousPage })
813
+ ]
814
+ }
815
+ ),
816
+ pageItems.map(
817
+ (item, index) => item === "..." ? /* @__PURE__ */ jsx3("span", { className: "nxg-page-ellipsis", children: "\u2026" }, `gap-${index}`) : /* @__PURE__ */ jsx3(
818
+ "button",
819
+ {
820
+ type: "button",
821
+ className: item === currentPage ? "nxg-page-btn nxg-page-btn--current" : "nxg-page-btn",
822
+ onClick: () => onQueryChange(withPage(query, item, totalPages)),
823
+ "aria-label": formatMessage(locale.pageLabel, { page: item }),
824
+ "aria-current": item === currentPage ? "page" : void 0,
825
+ children: item
826
+ },
827
+ `page-${item}`
828
+ )
829
+ ),
830
+ /* @__PURE__ */ jsxs2(
831
+ "button",
832
+ {
833
+ type: "button",
834
+ className: "nxg-page-nav",
835
+ disabled: currentPage >= totalPages,
836
+ onClick: () => onQueryChange(withPage(query, currentPage + 1, totalPages)),
837
+ "aria-label": locale.nextPage,
838
+ children: [
839
+ /* @__PURE__ */ jsx3(ChevronRightIcon, { size: 16 }),
840
+ /* @__PURE__ */ jsx3("span", { className: "nxg-sr-only", children: locale.nextPage })
841
+ ]
842
+ }
843
+ )
844
+ ] }),
845
+ /* @__PURE__ */ jsxs2("form", { className: "nxg-jump", onSubmit: submitJump, children: [
846
+ /* @__PURE__ */ jsx3("label", { className: "nxg-jump-label", htmlFor: jumpInputId, children: locale.goToPage }),
847
+ /* @__PURE__ */ jsx3(
848
+ "input",
849
+ {
850
+ id: jumpInputId,
851
+ type: "number",
852
+ className: "nxg-jump-input",
853
+ min: 1,
854
+ max: totalPages,
855
+ value: jumpText,
856
+ onChange: (event) => setJumpText(event.target.value),
857
+ onBlur: () => submitJump(),
858
+ "aria-label": locale.goToPageOf
859
+ }
860
+ )
861
+ ] })
862
+ ] })
863
+ ] })
864
+ ] });
865
+ }
866
+
867
+ // src/use-client-nex-grid.ts
868
+ import { useMemo as useMemo2, useState as useState4 } from "react";
869
+ import {
870
+ defaultQuery,
871
+ queryClientData
872
+ } from "@nexgrid/core";
873
+ function useClientNexGrid(allData, options) {
874
+ const [query, setQuery] = useState4(() => ({
875
+ ...defaultQuery(),
876
+ ...options?.initialQuery
877
+ }));
878
+ const page = useMemo2(
879
+ () => queryClientData(allData, query, options),
880
+ [allData, query, options]
881
+ );
882
+ return {
883
+ data: page.items,
884
+ total: page.total,
885
+ query,
886
+ onQueryChange: setQuery,
887
+ page: page.page,
888
+ pageSize: page.pageSize,
889
+ totalPages: page.totalPages,
890
+ setQuery
891
+ };
892
+ }
893
+
894
+ // src/index.ts
895
+ import {
896
+ queryClientData as queryClientData2,
897
+ defaultQuery as defaultQuery2,
898
+ parseQuery,
899
+ serializeQuery,
900
+ buildQueryUrl as buildQueryUrl2,
901
+ toODataParams,
902
+ buildODataUrl,
903
+ fromODataResponse,
904
+ primarySort as primarySort2,
905
+ withToggledSort as withToggledSort2,
906
+ withSort,
907
+ withSearch as withSearch2,
908
+ withPage as withPage2,
909
+ withPageSize as withPageSize2,
910
+ withFilter,
911
+ totalPagesFor as totalPagesFor2,
912
+ isPageSize as isPageSize2,
913
+ PAGE_SIZES as PAGE_SIZES2,
914
+ DEFAULT_PAGE_SIZE,
915
+ DEFAULT_LOCALE,
916
+ resolveLocale as resolveLocale2
917
+ } from "@nexgrid/core";
918
+ export {
919
+ DEFAULT_LOCALE,
920
+ DEFAULT_PAGE_SIZE,
921
+ NexGrid,
922
+ PAGE_SIZES2 as PAGE_SIZES,
923
+ buildODataUrl,
924
+ buildQueryUrl2 as buildQueryUrl,
925
+ defaultQuery2 as defaultQuery,
926
+ fromODataResponse,
927
+ isPageSize2 as isPageSize,
928
+ parseQuery,
929
+ primarySort2 as primarySort,
930
+ queryClientData2 as queryClientData,
931
+ resolveLocale2 as resolveLocale,
932
+ serializeQuery,
933
+ toODataParams,
934
+ totalPagesFor2 as totalPagesFor,
935
+ useClientNexGrid,
936
+ withFilter,
937
+ withPage2 as withPage,
938
+ withPageSize2 as withPageSize,
939
+ withSearch2 as withSearch,
940
+ withSort,
941
+ withToggledSort2 as withToggledSort
942
+ };
943
+ //# sourceMappingURL=index.js.map