@konce-pt/react 0.8.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.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -0
  3. package/dist/accordion/accordion.d.ts +45 -0
  4. package/dist/accordion/accordion.js +69 -0
  5. package/dist/alert/alert.d.ts +25 -0
  6. package/dist/alert/alert.js +31 -0
  7. package/dist/app-shell/app-shell.d.ts +94 -0
  8. package/dist/app-shell/app-shell.js +105 -0
  9. package/dist/auth/auth-types.d.ts +44 -0
  10. package/dist/auth/auth-types.js +2 -0
  11. package/dist/auth/auth.d.ts +76 -0
  12. package/dist/auth/auth.js +184 -0
  13. package/dist/autocomplete/autocomplete.d.ts +18 -0
  14. package/dist/autocomplete/autocomplete.js +72 -0
  15. package/dist/avatar/avatar.d.ts +33 -0
  16. package/dist/avatar/avatar.js +30 -0
  17. package/dist/avatar-group/avatar-group.d.ts +21 -0
  18. package/dist/avatar-group/avatar-group.js +15 -0
  19. package/dist/badge/badge.d.ts +25 -0
  20. package/dist/badge/badge.js +14 -0
  21. package/dist/bottom-sheet/bottom-sheet.d.ts +21 -0
  22. package/dist/bottom-sheet/bottom-sheet.js +22 -0
  23. package/dist/breadcrumb/breadcrumb.d.ts +19 -0
  24. package/dist/breadcrumb/breadcrumb.js +20 -0
  25. package/dist/button/button.d.ts +27 -0
  26. package/dist/button/button.js +49 -0
  27. package/dist/button-group/button-group.d.ts +25 -0
  28. package/dist/button-group/button-group.js +30 -0
  29. package/dist/card/card.d.ts +66 -0
  30. package/dist/card/card.js +96 -0
  31. package/dist/carousel/carousel.d.ts +20 -0
  32. package/dist/carousel/carousel.js +24 -0
  33. package/dist/charts/chart-legend.d.ts +23 -0
  34. package/dist/charts/chart-legend.js +9 -0
  35. package/dist/charts/chart-tooltip.d.ts +21 -0
  36. package/dist/charts/chart-tooltip.js +13 -0
  37. package/dist/charts/chart.d.ts +18 -0
  38. package/dist/charts/chart.js +231 -0
  39. package/dist/charts-entry/public-api.d.ts +4 -0
  40. package/dist/charts-entry/public-api.js +7 -0
  41. package/dist/checkbox/checkbox.d.ts +33 -0
  42. package/dist/checkbox/checkbox.js +29 -0
  43. package/dist/chip/chip.d.ts +22 -0
  44. package/dist/chip/chip.js +16 -0
  45. package/dist/chips-input/chips-input.d.ts +25 -0
  46. package/dist/chips-input/chips-input.js +54 -0
  47. package/dist/clock/clock.d.ts +17 -0
  48. package/dist/clock/clock.js +57 -0
  49. package/dist/color-picker/color-picker.d.ts +23 -0
  50. package/dist/color-picker/color-picker.js +28 -0
  51. package/dist/confirm/confirm-container.d.ts +13 -0
  52. package/dist/confirm/confirm-container.js +26 -0
  53. package/dist/confirm/confirm-store.d.ts +29 -0
  54. package/dist/confirm/confirm-store.js +52 -0
  55. package/dist/context-menu/context-menu.d.ts +21 -0
  56. package/dist/context-menu/context-menu.js +36 -0
  57. package/dist/data-table/data-table-types.d.ts +67 -0
  58. package/dist/data-table/data-table-types.js +1 -0
  59. package/dist/data-table/data-table.d.ts +62 -0
  60. package/dist/data-table/data-table.js +453 -0
  61. package/dist/data-table/export-csv.d.ts +7 -0
  62. package/dist/data-table/export-csv.js +19 -0
  63. package/dist/data-view/data-view.d.ts +21 -0
  64. package/dist/data-view/data-view.js +25 -0
  65. package/dist/date-range/date-range.d.ts +30 -0
  66. package/dist/date-range/date-range.js +155 -0
  67. package/dist/datepicker/datepicker.d.ts +39 -0
  68. package/dist/datepicker/datepicker.js +202 -0
  69. package/dist/dialog/dialog.d.ts +35 -0
  70. package/dist/dialog/dialog.js +59 -0
  71. package/dist/divider/divider.d.ts +16 -0
  72. package/dist/divider/divider.js +13 -0
  73. package/dist/drawer/drawer.d.ts +25 -0
  74. package/dist/drawer/drawer.js +25 -0
  75. package/dist/empty/empty.d.ts +18 -0
  76. package/dist/empty/empty.js +14 -0
  77. package/dist/fab/fab.d.ts +22 -0
  78. package/dist/fab/fab.js +17 -0
  79. package/dist/fieldset/fieldset.d.ts +22 -0
  80. package/dist/fieldset/fieldset.js +24 -0
  81. package/dist/file-upload/file-upload.d.ts +22 -0
  82. package/dist/file-upload/file-upload.js +50 -0
  83. package/dist/form-field/form-field-context.d.ts +23 -0
  84. package/dist/form-field/form-field-context.js +17 -0
  85. package/dist/form-field/form-field.d.ts +39 -0
  86. package/dist/form-field/form-field.js +41 -0
  87. package/dist/galleria/galleria.d.ts +22 -0
  88. package/dist/galleria/galleria.js +21 -0
  89. package/dist/grid/col.d.ts +39 -0
  90. package/dist/grid/col.js +32 -0
  91. package/dist/grid/flex.d.ts +45 -0
  92. package/dist/grid/flex.js +35 -0
  93. package/dist/grid/grid.d.ts +47 -0
  94. package/dist/grid/grid.js +38 -0
  95. package/dist/grid/layout.d.ts +18 -0
  96. package/dist/grid/layout.js +32 -0
  97. package/dist/grid-entry/public-api.d.ts +6 -0
  98. package/dist/grid-entry/public-api.js +8 -0
  99. package/dist/i18n/i18n-context.d.ts +45 -0
  100. package/dist/i18n/i18n-context.js +65 -0
  101. package/dist/icon/icon-registry.d.ts +16 -0
  102. package/dist/icon/icon-registry.js +43 -0
  103. package/dist/icon/icon.d.ts +17 -0
  104. package/dist/icon/icon.js +23 -0
  105. package/dist/icon-button/icon-button.d.ts +23 -0
  106. package/dist/icon-button/icon-button.js +14 -0
  107. package/dist/icons-entry/public-api.d.ts +12 -0
  108. package/dist/icons-entry/public-api.js +20 -0
  109. package/dist/image/image.d.ts +17 -0
  110. package/dist/image/image.js +16 -0
  111. package/dist/input/input.d.ts +41 -0
  112. package/dist/input/input.js +36 -0
  113. package/dist/input-mask/input-mask.d.ts +19 -0
  114. package/dist/input-mask/input-mask.js +65 -0
  115. package/dist/input-number/input-number.d.ts +28 -0
  116. package/dist/input-number/input-number.js +52 -0
  117. package/dist/input-otp/input-otp.d.ts +21 -0
  118. package/dist/input-otp/input-otp.js +59 -0
  119. package/dist/internal/cn.d.ts +9 -0
  120. package/dist/internal/cn.js +19 -0
  121. package/dist/internal/compose-refs.d.ts +6 -0
  122. package/dist/internal/compose-refs.js +16 -0
  123. package/dist/internal/overlay-container.d.ts +3 -0
  124. package/dist/internal/overlay-container.js +19 -0
  125. package/dist/internal/overlay.d.ts +26 -0
  126. package/dist/internal/overlay.js +64 -0
  127. package/dist/internal/slot.d.ts +23 -0
  128. package/dist/internal/slot.js +49 -0
  129. package/dist/internal/types.d.ts +25 -0
  130. package/dist/internal/types.js +1 -0
  131. package/dist/internal/use-breakpoint.d.ts +18 -0
  132. package/dist/internal/use-breakpoint.js +52 -0
  133. package/dist/internal/use-controllable-state.d.ts +11 -0
  134. package/dist/internal/use-controllable-state.js +18 -0
  135. package/dist/internal/use-id.d.ts +9 -0
  136. package/dist/internal/use-id.js +13 -0
  137. package/dist/knob/knob.d.ts +27 -0
  138. package/dist/knob/knob.js +86 -0
  139. package/dist/listbox/listbox.d.ts +28 -0
  140. package/dist/listbox/listbox.js +43 -0
  141. package/dist/map/map-create-dialog.d.ts +21 -0
  142. package/dist/map/map-create-dialog.js +196 -0
  143. package/dist/map/map-hours-field.d.ts +22 -0
  144. package/dist/map/map-hours-field.js +90 -0
  145. package/dist/map/map-image-field.d.ts +24 -0
  146. package/dist/map/map-image-field.js +61 -0
  147. package/dist/map/map-legend.d.ts +29 -0
  148. package/dist/map/map-legend.js +41 -0
  149. package/dist/map/map-poi-card.d.ts +42 -0
  150. package/dist/map/map-poi-card.js +152 -0
  151. package/dist/map/map-search.d.ts +37 -0
  152. package/dist/map/map-search.js +201 -0
  153. package/dist/map/map.d.ts +113 -0
  154. package/dist/map/map.js +425 -0
  155. package/dist/map/ports.d.ts +76 -0
  156. package/dist/map/ports.js +174 -0
  157. package/dist/map-entry/public-api.d.ts +20 -0
  158. package/dist/map-entry/public-api.js +19 -0
  159. package/dist/megamenu/megamenu.d.ts +17 -0
  160. package/dist/megamenu/megamenu.js +28 -0
  161. package/dist/menu/menu-item.d.ts +33 -0
  162. package/dist/menu/menu-item.js +1 -0
  163. package/dist/menu/menu-rows.d.ts +9 -0
  164. package/dist/menu/menu-rows.js +15 -0
  165. package/dist/menu/menu.d.ts +22 -0
  166. package/dist/menu/menu.js +31 -0
  167. package/dist/menubar/menubar.d.ts +16 -0
  168. package/dist/menubar/menubar.js +39 -0
  169. package/dist/meter-group/meter-group.d.ts +20 -0
  170. package/dist/meter-group/meter-group.js +24 -0
  171. package/dist/order-list/order-list.d.ts +18 -0
  172. package/dist/order-list/order-list.js +37 -0
  173. package/dist/paginator/paginator.d.ts +29 -0
  174. package/dist/paginator/paginator.js +45 -0
  175. package/dist/panel/panel.d.ts +31 -0
  176. package/dist/panel/panel.js +36 -0
  177. package/dist/password/password.d.ts +28 -0
  178. package/dist/password/password.js +37 -0
  179. package/dist/pick-list/pick-list.d.ts +28 -0
  180. package/dist/pick-list/pick-list.js +65 -0
  181. package/dist/popover/popover.d.ts +23 -0
  182. package/dist/popover/popover.js +27 -0
  183. package/dist/progress/progress.d.ts +19 -0
  184. package/dist/progress/progress.js +21 -0
  185. package/dist/public-api.d.ts +181 -0
  186. package/dist/public-api.js +105 -0
  187. package/dist/radio-group/radio-group.d.ts +24 -0
  188. package/dist/radio-group/radio-group.js +23 -0
  189. package/dist/rating/rating.d.ts +22 -0
  190. package/dist/rating/rating.js +30 -0
  191. package/dist/rich-text/lists.d.ts +17 -0
  192. package/dist/rich-text/lists.js +144 -0
  193. package/dist/rich-text/rich-text.d.ts +28 -0
  194. package/dist/rich-text/rich-text.js +654 -0
  195. package/dist/rich-text/sanitize.d.ts +10 -0
  196. package/dist/rich-text/sanitize.js +85 -0
  197. package/dist/rich-text/tables.d.ts +44 -0
  198. package/dist/rich-text/tables.js +268 -0
  199. package/dist/roadmap/roadmap.d.ts +37 -0
  200. package/dist/roadmap/roadmap.js +271 -0
  201. package/dist/roadmap-entry/public-api.d.ts +4 -0
  202. package/dist/roadmap-entry/public-api.js +7 -0
  203. package/dist/scroll-top/scroll-top.d.ts +16 -0
  204. package/dist/scroll-top/scroll-top.js +38 -0
  205. package/dist/select/select.d.ts +37 -0
  206. package/dist/select/select.js +117 -0
  207. package/dist/shared/types.d.ts +8 -0
  208. package/dist/shared/types.js +1 -0
  209. package/dist/sidenav/sidenav.d.ts +26 -0
  210. package/dist/sidenav/sidenav.js +41 -0
  211. package/dist/skeleton/skeleton.d.ts +20 -0
  212. package/dist/skeleton/skeleton.js +15 -0
  213. package/dist/slider/slider.d.ts +28 -0
  214. package/dist/slider/slider.js +30 -0
  215. package/dist/speed-dial/speed-dial.d.ts +20 -0
  216. package/dist/speed-dial/speed-dial.js +23 -0
  217. package/dist/spinner/spinner.d.ts +15 -0
  218. package/dist/spinner/spinner.js +14 -0
  219. package/dist/split-button/split-button.d.ts +28 -0
  220. package/dist/split-button/split-button.js +34 -0
  221. package/dist/splitter/splitter.d.ts +39 -0
  222. package/dist/splitter/splitter.js +80 -0
  223. package/dist/stepper/stepper.d.ts +51 -0
  224. package/dist/stepper/stepper.js +71 -0
  225. package/dist/switch/switch.d.ts +39 -0
  226. package/dist/switch/switch.js +30 -0
  227. package/dist/switch-group/switch-group.d.ts +29 -0
  228. package/dist/switch-group/switch-group.js +47 -0
  229. package/dist/tabs/tabs.d.ts +38 -0
  230. package/dist/tabs/tabs.js +74 -0
  231. package/dist/textarea/textarea.d.ts +28 -0
  232. package/dist/textarea/textarea.js +44 -0
  233. package/dist/timeline/timeline.d.ts +19 -0
  234. package/dist/timeline/timeline.js +12 -0
  235. package/dist/toast/toast-container.d.ts +12 -0
  236. package/dist/toast/toast-container.js +23 -0
  237. package/dist/toast/toast-store.d.ts +26 -0
  238. package/dist/toast/toast-store.js +51 -0
  239. package/dist/toolbar/toolbar.d.ts +29 -0
  240. package/dist/toolbar/toolbar.js +24 -0
  241. package/dist/tooltip/tooltip.d.ts +26 -0
  242. package/dist/tooltip/tooltip.js +113 -0
  243. package/dist/tree/tree.d.ts +31 -0
  244. package/dist/tree/tree.js +62 -0
  245. package/package.json +91 -0
@@ -0,0 +1,453 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
3
+ import { aggregate, isEmptyFilterValue, matchesColumnFilter, toText, } from '@konce-pt/table';
4
+ import { cn } from "../internal/cn.js";
5
+ import { useControllableState } from "../internal/use-controllable-state.js";
6
+ import { useKptMessages } from "../i18n/i18n-context.js";
7
+ import { KptButton } from "../button/button.js";
8
+ import { KptDatepicker } from "../datepicker/datepicker.js";
9
+ import { KptDateRange } from "../date-range/date-range.js";
10
+ import { KptIcon } from "../icon/icon.js";
11
+ import { KptInput } from "../input/input.js";
12
+ import { KptInputNumber } from "../input-number/input-number.js";
13
+ import { KptPaginator } from "../paginator/paginator.js";
14
+ import { KptSelect } from "../select/select.js";
15
+ import { KptSlider } from "../slider/slider.js";
16
+ import { exportTableCsv } from "./export-csv.js";
17
+ const REM_PX = 16;
18
+ const CHECKBOX_FROZEN_W = 3; // rem
19
+ const EXPANDER_FROZEN_W = 2.5; // rem
20
+ const VIRTUAL_BUFFER = 6;
21
+ /** Edytory z panelem w nakładce — otwierane od razu i zatwierdzane na zamknięciu panelu. */
22
+ const OVERLAY_EDITORS = new Set(['date', 'select', 'multiselect']);
23
+ /** Tablice (multiselect) porównujemy po zawartości, resztę po referencji. */
24
+ function sameEditValue(a, b) {
25
+ if (a === b)
26
+ return true;
27
+ if (Array.isArray(a) && Array.isArray(b))
28
+ return a.length === b.length && a.every((v, i) => v === b[i]);
29
+ return false;
30
+ }
31
+ const EMPTY_ROWS = [];
32
+ const EMPTY_FIELDS = [];
33
+ /**
34
+ * Flagowa tabela danych: kolumny konfiguracją, sortowanie, filtr globalny i filtry kolumn,
35
+ * paginacja, zaznaczanie, kolumny zamrożone i przestawiane, edycja inline, wiersz podsumowania,
36
+ * rozwijane wiersze, wirtualizacja i eksport CSV.
37
+ *
38
+ * @example
39
+ * <KptDataTable columns={cols} data={rows} filterable pageSize={10} selectable="multiple" rowKey="id" />
40
+ */
41
+ export function KptDataTable({ columns, data = EMPTY_ROWS, filterable = false, filterRowPosition = 'bottom', pageSize = 0, pageSizeOptions = [5, 10, 20, 50], selectable = 'none', loading = false, emptyMessage, rowKey = null, scrollHeight = null, virtualScroll = false, rowHeight = 41, reorderable = false, exportable = false, exportFilename = 'export.csv', lazy = false, totalRecords = null, expandable = false, multiExpand = true, summaryLabel, summaryValues = null, selection, defaultSelection = EMPTY_ROWS, onSelectionChange, expandedRows, defaultExpandedRows = EMPTY_ROWS, onExpandedRowsChange, hiddenFields, defaultHiddenFields = EMPTY_FIELDS, onHiddenFieldsChange, onRowClick, onLazyLoad, onCellEdit, cells, cellEditors, renderRowDetail, paginatorActions, locale, dictionary, className, style, ...rest }) {
42
+ const { t } = useKptMessages({ locale, dictionary });
43
+ const [picked, setPicked] = useControllableState({
44
+ value: selection,
45
+ defaultValue: defaultSelection,
46
+ onChange: onSelectionChange,
47
+ });
48
+ const [expanded, setExpanded] = useControllableState({
49
+ value: expandedRows,
50
+ defaultValue: defaultExpandedRows,
51
+ onChange: onExpandedRowsChange,
52
+ });
53
+ const [hidden, setHidden] = useControllableState({
54
+ value: hiddenFields,
55
+ defaultValue: defaultHiddenFields,
56
+ onChange: onHiddenFieldsChange,
57
+ });
58
+ const [filterText, setFilterText] = useState('');
59
+ const [columnFilters, setColumnFilters] = useState({});
60
+ const [sortField, setSortField] = useState(null);
61
+ const [sortDir, setSortDir] = useState(null);
62
+ const [pageIndex, setPageIndex] = useState(0);
63
+ const [pageSizeState, setPageSizeState] = useState(pageSize || 10);
64
+ // Kolejność kolumn żyje w stanie (przestawianie), ale nowe `columns` z zewnątrz ją zastępują.
65
+ const [order, setOrder] = useState(columns);
66
+ const columnsRef = useRef(columns);
67
+ if (columnsRef.current !== columns) {
68
+ columnsRef.current = columns;
69
+ if (order !== columns)
70
+ setOrder(columns);
71
+ }
72
+ const [dragField, setDragField] = useState(null);
73
+ const [scrollTop, setScrollTop] = useState(0);
74
+ const [viewportHeight, setViewportHeight] = useState(400);
75
+ const [theadOffset, setTheadOffset] = useState(0);
76
+ const scrollRef = useRef(null);
77
+ const theadRef = useRef(null);
78
+ const editorHostRef = useRef(null);
79
+ const [editingCell, setEditingCell] = useState(null);
80
+ const [editingValue, setEditingValue] = useState(null);
81
+ const [editorPanelOpen, setEditorPanelOpen] = useState(false);
82
+ const visibleColumns = useMemo(() => order.filter((col) => !hidden.includes(col.field)), [order, hidden]);
83
+ const hasColumnFilters = columns.some((col) => col.filter);
84
+ const hasRowDetail = expandable && !!renderRowDetail;
85
+ const sameRow = (a, b) => rowKey ? a[rowKey] === b[rowKey] : a === b;
86
+ // ── Potok danych ──
87
+ const filtered = useMemo(() => {
88
+ const query = filterText.trim().toLowerCase();
89
+ let rows = data;
90
+ if (query) {
91
+ rows = rows.filter((row) => columns.some((col) => toText(row[col.field]).toLowerCase().includes(query)));
92
+ }
93
+ if (hasColumnFilters) {
94
+ rows = rows.filter((row) => columns.every((col) => {
95
+ if (!col.filter)
96
+ return true;
97
+ const value = columnFilters[col.field];
98
+ if (isEmptyFilterValue(value))
99
+ return true;
100
+ return matchesColumnFilter(col.filter, row[col.field], value);
101
+ }));
102
+ }
103
+ return rows;
104
+ }, [columnFilters, columns, data, filterText, hasColumnFilters]);
105
+ const sorted = useMemo(() => {
106
+ if (!sortField || !sortDir)
107
+ return filtered;
108
+ const sign = sortDir === 'asc' ? 1 : -1;
109
+ return [...filtered].sort((a, b) => {
110
+ const av = a[sortField];
111
+ const bv = b[sortField];
112
+ // Puste komórki lądują na końcu niezależnie od kierunku — sortowanie ma pokazywać dane.
113
+ if (av == null)
114
+ return 1;
115
+ if (bv == null)
116
+ return -1;
117
+ if (typeof av === 'number' && typeof bv === 'number')
118
+ return (av - bv) * sign;
119
+ return toText(av).localeCompare(toText(bv)) * sign;
120
+ });
121
+ }, [filtered, sortDir, sortField]);
122
+ const paged = useMemo(() => {
123
+ if (lazy)
124
+ return data;
125
+ if (pageSize <= 0)
126
+ return sorted;
127
+ const start = pageIndex * pageSizeState;
128
+ return sorted.slice(start, start + pageSizeState);
129
+ }, [data, lazy, pageIndex, pageSize, pageSizeState, sorted]);
130
+ const filteredCount = lazy ? (totalRecords ?? data.length) : filtered.length;
131
+ // Tryb lazy: każda zmiana stanu to prośba do rodzica o dane.
132
+ useEffect(() => {
133
+ if (!lazy)
134
+ return;
135
+ onLazyLoad?.({ pageIndex, pageSize: pageSizeState, sortField, sortDir, filter: filterText, columnFilters });
136
+ // `onLazyLoad` celowo poza zależnościami: nowa referencja funkcji przy każdym renderze
137
+ // rodzica wywołałaby pętlę żądań.
138
+ // eslint-disable-next-line react-hooks/exhaustive-deps
139
+ }, [lazy, pageIndex, pageSizeState, sortField, sortDir, filterText, columnFilters]);
140
+ // Offset sticky drugiego wiersza nagłówka = wysokość pierwszego.
141
+ useLayoutEffect(() => {
142
+ const first = theadRef.current?.rows?.[0];
143
+ if (first)
144
+ setTheadOffset(first.offsetHeight);
145
+ }, [filterRowPosition, hasColumnFilters, visibleColumns]);
146
+ useLayoutEffect(() => {
147
+ const el = scrollRef.current;
148
+ if (el)
149
+ setViewportHeight(el.clientHeight);
150
+ }, []);
151
+ // Wejście w edycję → fokus na kontrolce edytora.
152
+ useEffect(() => {
153
+ if (!editingCell)
154
+ return;
155
+ editorHostRef.current?.querySelector('input, .kpt-select, [tabindex]')?.focus();
156
+ }, [editingCell]);
157
+ // ── Wirtualizacja ──
158
+ const virtualStart = Math.max(0, Math.floor(scrollTop / rowHeight) - VIRTUAL_BUFFER);
159
+ const virtualEnd = Math.min(sorted.length, virtualStart + Math.ceil(viewportHeight / rowHeight) + VIRTUAL_BUFFER * 2);
160
+ const virtualRows = sorted.slice(virtualStart, virtualEnd);
161
+ const topPad = virtualStart * rowHeight;
162
+ const bottomPad = (sorted.length - virtualEnd) * rowHeight;
163
+ const onScroll = (event) => {
164
+ if (!virtualScroll)
165
+ return;
166
+ const el = event.currentTarget;
167
+ setScrollTop(el.scrollTop);
168
+ setViewportHeight(el.clientHeight);
169
+ };
170
+ // ── Kolumny zamrożone ──
171
+ const parseWidthPx = (width) => {
172
+ if (!width)
173
+ return 8 * REM_PX;
174
+ const n = Number.parseFloat(width);
175
+ if (Number.isNaN(n))
176
+ return 8 * REM_PX;
177
+ return width.trim().endsWith('px') ? n : n * REM_PX;
178
+ };
179
+ const leadingCells = (selectable !== 'none' ? 1 : 0) + (hasRowDetail ? 1 : 0);
180
+ const leadingStickyWidth = (hasRowDetail ? EXPANDER_FROZEN_W * REM_PX : 0) + (selectable !== 'none' ? CHECKBOX_FROZEN_W * REM_PX : 0);
181
+ const frozenOffsets = useMemo(() => {
182
+ const map = new Map();
183
+ let acc = leadingStickyWidth;
184
+ for (const col of visibleColumns) {
185
+ if (!col.frozen)
186
+ break; // tylko wiodące, sąsiadujące
187
+ map.set(col.field, acc);
188
+ acc += parseWidthPx(col.width);
189
+ }
190
+ return map;
191
+ }, [leadingStickyWidth, visibleColumns]);
192
+ const hasFrozen = frozenOffsets.size > 0;
193
+ const frozenLeft = (field) => frozenOffsets.get(field);
194
+ const expanderStickyLeft = hasFrozen ? 0 : undefined;
195
+ const checkboxStickyLeft = hasFrozen ? (hasRowDetail ? EXPANDER_FROZEN_W * REM_PX : 0) : undefined;
196
+ // ── Przestawianie kolumn ──
197
+ const frozenLeadCount = (() => {
198
+ let n = 0;
199
+ for (const col of visibleColumns) {
200
+ if (col.frozen)
201
+ n++;
202
+ else
203
+ break;
204
+ }
205
+ return n;
206
+ })();
207
+ const isColDraggable = (col) => reorderable && !col.frozen;
208
+ const onColDrop = (field, event) => {
209
+ event.preventDefault();
210
+ if (dragField === null || dragField === field)
211
+ return;
212
+ const visIndex = visibleColumns.findIndex((col) => col.field === field);
213
+ if (visIndex < frozenLeadCount)
214
+ return; // nie wsuwaj przed kolumny zamrożone
215
+ const cols = [...order];
216
+ const fromIdx = cols.findIndex((col) => col.field === dragField);
217
+ const toIdx = cols.findIndex((col) => col.field === field);
218
+ if (fromIdx === -1 || toIdx === -1)
219
+ return;
220
+ const [moved] = cols.splice(fromIdx, 1);
221
+ cols.splice(toIdx, 0, moved);
222
+ setOrder(cols);
223
+ setDragField(null);
224
+ };
225
+ // ── Sortowanie ──
226
+ const toggleSort = (col) => {
227
+ if (!col.sortable)
228
+ return;
229
+ if (sortField !== col.field) {
230
+ setSortField(col.field);
231
+ setSortDir('asc');
232
+ return;
233
+ }
234
+ if (sortDir === 'asc')
235
+ setSortDir('desc');
236
+ else if (sortDir === 'desc') {
237
+ setSortField(null);
238
+ setSortDir(null);
239
+ }
240
+ else
241
+ setSortDir('asc');
242
+ };
243
+ const ariaSort = (col) => {
244
+ if (sortField !== col.field || !sortDir)
245
+ return 'none';
246
+ return sortDir === 'asc' ? 'ascending' : 'descending';
247
+ };
248
+ // ── Filtry kolumn ──
249
+ const setColumnFilter = (field, value) => {
250
+ setColumnFilters((current) => ({ ...current, [field]: value }));
251
+ setPageIndex(0);
252
+ };
253
+ const rangeVal = (field) => {
254
+ const value = columnFilters[field];
255
+ return { min: value?.min ?? '', max: value?.max ?? '' };
256
+ };
257
+ const sliderVal = (col) => {
258
+ const min = col.filter?.min ?? 0;
259
+ const max = col.filter?.max ?? 100;
260
+ return columnFilters[col.field] ?? [min, max];
261
+ };
262
+ // ── Zaznaczanie ──
263
+ const isSelected = (row) => picked.some((r) => sameRow(r, row));
264
+ const toggleRow = (row) => {
265
+ if (selectable === 'none')
266
+ return;
267
+ if (selectable === 'single') {
268
+ setPicked(isSelected(row) ? [] : [row]);
269
+ return;
270
+ }
271
+ setPicked(isSelected(row) ? picked.filter((r) => !sameRow(r, row)) : [...picked, row]);
272
+ };
273
+ const pageAllSelected = paged.length > 0 && paged.every((row) => isSelected(row));
274
+ const pageSomeSelected = paged.some((row) => isSelected(row)) && !pageAllSelected;
275
+ const toggleAllOnPage = () => {
276
+ if (pageAllSelected)
277
+ setPicked(picked.filter((r) => !paged.some((pr) => sameRow(pr, r))));
278
+ else
279
+ setPicked([...picked, ...paged.filter((row) => !isSelected(row))]);
280
+ };
281
+ // ── Rozwijane wiersze ──
282
+ const isExpanded = (row) => expanded.some((r) => sameRow(r, row));
283
+ const toggleExpand = (row) => {
284
+ if (isExpanded(row))
285
+ setExpanded(expanded.filter((r) => !sameRow(r, row)));
286
+ else
287
+ setExpanded(multiExpand ? [...expanded, row] : [row]);
288
+ };
289
+ // ── Edycja inline ──
290
+ const isEditable = (col) => !!col.edit || !!cellEditors?.[col.field];
291
+ const isEditingCell = (row, field) => !!editingCell && editingCell.field === field && sameRow(editingCell.row, row);
292
+ const startEdit = (row, col, event) => {
293
+ event.stopPropagation();
294
+ if (!isEditable(col) || isEditingCell(row, col.field))
295
+ return;
296
+ setEditingCell({ row, field: col.field });
297
+ setEditingValue(row[col.field]);
298
+ // Edytory z panelem otwieramy od razu: ich zamknięcie jest jedynym momentem, w którym
299
+ // wiadomo, że użytkownik skończył wybierać.
300
+ setEditorPanelOpen(!!col.edit && OVERLAY_EDITORS.has(col.edit.type));
301
+ };
302
+ const commitEdit = (value = editingValue) => {
303
+ if (!editingCell)
304
+ return;
305
+ const { row, field } = editingCell;
306
+ setEditingCell(null);
307
+ setEditorPanelOpen(false);
308
+ if (sameEditValue(value, row[field]))
309
+ return;
310
+ onCellEdit?.({ row, field, value, updatedRow: { ...row, [field]: value } });
311
+ };
312
+ const cancelEdit = () => {
313
+ setEditingCell(null);
314
+ setEditorPanelOpen(false);
315
+ };
316
+ /**
317
+ * Edytory bez panelu (text/number, własne) zatwierdza dopiero wyjście fokusu poza CAŁY edytor:
318
+ * `KptInputNumber` ma fokusowalne strzałki obok pola, więc reakcja na blur samej kontrolki
319
+ * zamykałaby edycję przy każdym kliknięciu w „+".
320
+ */
321
+ const onEditorFocusOut = (event) => {
322
+ if (editorPanelOpen)
323
+ return; // edytory z panelem zatwierdza jego zamknięcie
324
+ const host = editorHostRef.current;
325
+ const next = event.relatedTarget;
326
+ if (host && next && host.contains(next))
327
+ return; // fokus został w edytorze
328
+ commitEdit();
329
+ };
330
+ const onEditorPanelToggle = (open) => {
331
+ setEditorPanelOpen(open);
332
+ if (!open)
333
+ commitEdit();
334
+ };
335
+ // ── Wiersz podsumowania ──
336
+ const summarySource = lazy ? EMPTY_ROWS : filtered;
337
+ const overrides = summaryValues ?? {};
338
+ const summaryFields = visibleColumns
339
+ .filter((col) => col.summary || col.field in overrides)
340
+ .map((col) => col.field);
341
+ const hasSummary = summaryFields.length > 0;
342
+ const summaryStartIndex = summaryFields[0] === undefined ? -1 : visibleColumns.findIndex((col) => col.field === summaryFields[0]);
343
+ const summaryLabelSpan = leadingCells + summaryStartIndex;
344
+ const formatSummary = (raw, format) => format ? format(raw) : toText(raw);
345
+ const summaryText = (col) => {
346
+ if (col.field in overrides)
347
+ return formatSummary(overrides[col.field], col.summary?.format);
348
+ if (!col.summary)
349
+ return '';
350
+ const raw = typeof col.summary.fn === 'function'
351
+ ? col.summary.fn(summarySource)
352
+ : aggregate(summarySource, col.field, col.summary.fn);
353
+ return formatSummary(raw, col.summary.format);
354
+ };
355
+ const colspan = visibleColumns.length + leadingCells;
356
+ const displayCount = virtualScroll ? sorted.length : paged.length;
357
+ const rowsToRender = virtualScroll ? virtualRows : paged;
358
+ // ── Renderowanie ──
359
+ const filterCell = (col) => {
360
+ const filter = col.filter;
361
+ if (!filter)
362
+ return null;
363
+ const stop = (event) => event.stopPropagation();
364
+ switch (filter.type) {
365
+ case 'text':
366
+ return (_jsx("input", { className: "kpt-data-table__fcell", type: "search", placeholder: filter.placeholder || '', "aria-label": t('dataTable.filter', { column: col.header }), value: columnFilters[col.field] || '', onChange: (event) => setColumnFilter(col.field, event.target.value), onClick: stop }));
367
+ case 'number':
368
+ return (_jsx("input", { className: "kpt-data-table__fcell", type: "text", inputMode: "numeric", placeholder: filter.placeholder || '', "aria-label": t('dataTable.filter', { column: col.header }), value: columnFilters[col.field] || '', onChange: (event) => setColumnFilter(col.field, event.target.value.replace(/\D/g, '')), onClick: stop }));
369
+ case 'select':
370
+ return (_jsx(KptSelect, { options: filter.options ?? [], multiple: !!filter.multiple, filter: true, placeholder: filter.placeholder || t('dataTable.all'), value: columnFilters[col.field] ?? null, onValueChange: (value) => setColumnFilter(col.field, value) }));
371
+ case 'numberRange':
372
+ return (_jsxs("div", { className: "kpt-data-table__frange", children: [_jsx("input", { className: "kpt-data-table__fcell", type: "text", inputMode: "numeric", placeholder: t('dataTable.min'), "aria-label": t('dataTable.filterMin', { column: col.header }), value: rangeVal(col.field).min, onChange: (event) => setColumnFilter(col.field, { ...rangeVal(col.field), min: event.target.value.replace(/\D/g, '') }), onClick: stop }), _jsx("input", { className: "kpt-data-table__fcell", type: "text", inputMode: "numeric", placeholder: t('dataTable.max'), "aria-label": t('dataTable.filterMax', { column: col.header }), value: rangeVal(col.field).max, onChange: (event) => setColumnFilter(col.field, { ...rangeVal(col.field), max: event.target.value.replace(/\D/g, '') }), onClick: stop })] }));
373
+ case 'slider':
374
+ return (_jsx("div", { className: "kpt-data-table__fslider", onClick: stop, children: _jsx(KptSlider, { range: true, min: filter.min ?? 0, max: filter.max ?? 100, step: filter.step ?? 1, value: sliderVal(col), onValueChange: (value) => setColumnFilter(col.field, value), showValue: true }) }));
375
+ case 'dateRange':
376
+ return (_jsx(KptDateRange, { placeholder: filter.placeholder || t('dataTable.dateRange'), value: columnFilters[col.field] || { start: '', end: '' }, onValueChange: (value) => setColumnFilter(col.field, value) }));
377
+ default:
378
+ return null;
379
+ }
380
+ };
381
+ const filterRow = (_jsxs("tr", { className: "kpt-data-table__filter-row", children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable !== 'none' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, visibleColumns.map((col) => (_jsx("th", { style: { width: col.width, left: frozenLeft(col.field) }, "data-align": col.align ?? 'start', className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen'), children: filterCell(col) }, col.field)))] }));
382
+ const editor = (row, col) => {
383
+ const custom = cellEditors?.[col.field];
384
+ if (custom) {
385
+ const context = {
386
+ value: editingValue,
387
+ row,
388
+ commit: (value) => commitEdit(value),
389
+ cancel: cancelEdit,
390
+ };
391
+ return custom(context);
392
+ }
393
+ const edit = col.edit;
394
+ if (!edit)
395
+ return null;
396
+ switch (edit.type) {
397
+ case 'text':
398
+ return (_jsx(KptInput, { value: editingValue ?? '', placeholder: edit.placeholder ?? '', onValueChange: setEditingValue, onKeyDown: (event) => {
399
+ if (event.key === 'Enter')
400
+ commitEdit();
401
+ if (event.key === 'Escape')
402
+ cancelEdit();
403
+ } }));
404
+ case 'number':
405
+ return (_jsx(KptInputNumber, { value: editingValue, min: edit.min, max: edit.max, step: edit.step ?? 1, onValueChange: setEditingValue, onKeyDown: (event) => {
406
+ if (event.key === 'Enter')
407
+ commitEdit();
408
+ if (event.key === 'Escape')
409
+ cancelEdit();
410
+ } }));
411
+ case 'date':
412
+ return (_jsx(KptDatepicker, { value: editingValue ?? '', onValueChange: setEditingValue, open: editorPanelOpen, onOpenChange: onEditorPanelToggle }));
413
+ case 'select':
414
+ case 'multiselect':
415
+ return (_jsx(KptSelect, { multiple: edit.type === 'multiselect', filter: edit.type === 'multiselect', options: edit.options ?? [], placeholder: edit.placeholder ?? '', value: editingValue, onValueChange: setEditingValue, open: editorPanelOpen, onOpenChange: onEditorPanelToggle }));
416
+ default:
417
+ return null;
418
+ }
419
+ };
420
+ const renderRow = (row, index) => (_jsxs("tbody", { style: { display: 'contents' }, children: [_jsxs("tr", { className: cn(isSelected(row) && 'kpt-data-table__row--selected', hasRowDetail && isExpanded(row) && 'kpt-data-table__row--expanded'), style: { height: virtualScroll ? rowHeight : undefined }, onClick: () => onRowClick?.(row), children: [hasRowDetail ? (_jsx("td", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft }, children: _jsx("button", { type: "button", className: cn('kpt-data-table__expander-btn', isExpanded(row) && 'kpt-data-table__expander-btn--open'), "aria-expanded": isExpanded(row), "aria-label": t('dataTable.expandRow'), onClick: (event) => {
421
+ event.stopPropagation();
422
+ toggleExpand(row);
423
+ }, children: _jsx(KptIcon, { name: "chevron-right" }) }) })) : null, selectable !== 'none' ? (_jsx("td", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: isSelected(row), "aria-label": t('dataTable.selectRow'), onClick: (event) => event.stopPropagation(), onChange: () => toggleRow(row) }) })) : null, visibleColumns.map((col) => {
424
+ const editing = isEditingCell(row, col.field);
425
+ const editable = isEditable(col) && !editing;
426
+ return (_jsx("td", { "data-align": col.align ?? 'start', style: { left: frozenLeft(col.field) }, className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen', editable && 'kpt-data-table__cell--editable'), tabIndex: editable ? 0 : undefined, "aria-label": editable ? t('dataTable.editCell', { column: col.header }) : undefined, onClick: (event) => {
427
+ if (isEditable(col))
428
+ startEdit(row, col, event);
429
+ }, onKeyDown: (event) => {
430
+ if (event.key === 'Enter' && editable)
431
+ startEdit(row, col, event);
432
+ }, children: editing ? (_jsx("div", { ref: editorHostRef, className: "kpt-data-table__editor", onClick: (event) => event.stopPropagation(), onBlur: onEditorFocusOut, children: editor(row, col) })) : cells?.[col.field] ? (cells[col.field](row, row[col.field])) : row[col.field] }, col.field));
433
+ })] }), hasRowDetail && isExpanded(row) && !virtualScroll ? (_jsx("tr", { className: "kpt-data-table__detail-row", children: _jsx("td", { className: "kpt-data-table__detail", colSpan: colspan, children: renderRowDetail?.(row) }) })) : null] }, rowKey ? String(row[rowKey]) : index));
434
+ return (_jsxs("div", { className: cn('kpt-data-table', className), style: { ['--kpt-thead-offset']: `${theadOffset}px`, ...style }, ...rest, children: [filterable || exportable ? (_jsxs("div", { className: "kpt-data-table__toolbar", children: [filterable ? (_jsx("input", { className: "kpt-data-table__filter", type: "search", placeholder: t('dataTable.search'), value: filterText, onChange: (event) => {
435
+ setFilterText(event.target.value);
436
+ setPageIndex(0);
437
+ } })) : null, exportable ? (_jsx(KptButton, { variant: "outline", size: "sm", onClick: () => exportTableCsv(visibleColumns, (lazy ? data : sorted), exportFilename), children: t('dataTable.exportCsv') })) : null] })) : null, _jsx("div", { ref: scrollRef, className: "kpt-data-table__scroll", style: { maxHeight: scrollHeight ?? undefined }, onScroll: onScroll, children: _jsxs("table", { className: cn('kpt-data-table__table', hasFrozen && 'kpt-data-table__table--frozen', virtualScroll && 'kpt-data-table__table--virtual'), children: [_jsxs("thead", { ref: theadRef, children: [hasColumnFilters && filterRowPosition === 'top' ? filterRow : null, _jsxs("tr", { children: [hasRowDetail ? (_jsx("th", { className: cn('kpt-data-table__expander', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: expanderStickyLeft } })) : null, selectable === 'multiple' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft }, children: _jsx("input", { type: "checkbox", checked: pageAllSelected, ref: (el) => {
438
+ if (el)
439
+ el.indeterminate = pageSomeSelected;
440
+ }, "aria-label": t('dataTable.selectAll'), onChange: toggleAllOnPage }) })) : selectable === 'single' ? (_jsx("th", { className: cn('kpt-data-table__checkbox', hasFrozen && 'kpt-data-table__cell--frozen'), style: { left: checkboxStickyLeft } })) : null, visibleColumns.map((col) => (_jsxs("th", { draggable: isColDraggable(col) || undefined, style: { width: col.width, left: frozenLeft(col.field) }, "data-align": col.align ?? 'start', className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen', col.sortable && 'kpt-data-table__th--sortable', isColDraggable(col) && 'kpt-data-table__th--draggable', dragField !== null && dragField !== col.field && !col.frozen && 'kpt-data-table__th--dragover'), "aria-sort": ariaSort(col), onClick: () => toggleSort(col), onDragStart: (event) => {
441
+ if (col.frozen)
442
+ return;
443
+ setDragField(col.field);
444
+ event.dataTransfer.setData('text/plain', col.field);
445
+ event.dataTransfer.effectAllowed = 'move';
446
+ }, onDragOver: (event) => {
447
+ if (!reorderable)
448
+ return;
449
+ const visIndex = visibleColumns.findIndex((c) => c.field === col.field);
450
+ if (dragField !== null && visIndex >= frozenLeadCount)
451
+ event.preventDefault();
452
+ }, onDrop: (event) => reorderable && onColDrop(col.field, event), onDragEnd: () => setDragField(null), children: [isColDraggable(col) ? (_jsx(KptIcon, { className: "kpt-data-table__grip", name: "grip-vertical" })) : null, _jsx("span", { children: col.header }), col.sortable ? (_jsx(KptIcon, { className: cn('kpt-data-table__sort', ariaSort(col) !== 'none' && 'kpt-data-table__sort--active'), name: sortField === col.field && sortDir === 'asc' ? 'chevron-up' : 'chevron-down' })) : null] }, col.field)))] }), hasColumnFilters && filterRowPosition === 'bottom' ? filterRow : null] }), loading ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { className: "kpt-data-table__state", colSpan: colspan, children: t('dataTable.loading') }) }) })) : displayCount === 0 ? (_jsx("tbody", { children: _jsx("tr", { children: _jsx("td", { className: "kpt-data-table__state", colSpan: colspan, children: emptyMessage ?? t('dataTable.empty') }) }) })) : (_jsxs(_Fragment, { children: [virtualScroll && topPad > 0 ? (_jsx("tbody", { children: _jsx("tr", { className: "kpt-data-table__spacer", "aria-hidden": "true", children: _jsx("td", { colSpan: colspan, style: { height: topPad } }) }) })) : null, rowsToRender.map((row, index) => renderRow(row, index)), virtualScroll && bottomPad > 0 ? (_jsx("tbody", { children: _jsx("tr", { className: "kpt-data-table__spacer", "aria-hidden": "true", children: _jsx("td", { colSpan: colspan, style: { height: bottomPad } }) }) })) : null] })), hasSummary && !loading && displayCount > 0 ? (_jsx("tfoot", { children: _jsxs("tr", { className: "kpt-data-table__summary", children: [summaryLabelSpan > 0 ? (_jsx("th", { className: cn('kpt-data-table__summary-label', hasFrozen && 'kpt-data-table__cell--frozen'), colSpan: summaryLabelSpan, style: { left: hasFrozen ? 0 : undefined }, children: summaryLabel ?? t('dataTable.total') })) : null, visibleColumns.map((col, index) => index >= summaryStartIndex ? (_jsx("td", { "data-align": col.align ?? 'end', style: { width: col.width, left: frozenLeft(col.field) }, className: cn(frozenOffsets.has(col.field) && 'kpt-data-table__cell--frozen'), children: summaryText(col) }, col.field)) : null)] }) })) : null] }) }), pageSize > 0 && !virtualScroll ? (_jsxs("div", { className: "kpt-data-table__pager", children: [paginatorActions, _jsx(KptPaginator, { length: filteredCount, pageIndex: pageIndex, onPageIndexChange: setPageIndex, pageSize: pageSizeState, onPageSizeChange: setPageSizeState, pageSizeOptions: pageSizeOptions })] })) : null] }));
453
+ }
@@ -0,0 +1,7 @@
1
+ import type { KptColumn } from './data-table-types.ts';
2
+ /**
3
+ * Zrzuca wiersze do pliku CSV (separator `;`). Tabela woła to samo z przycisku „Eksport CSV";
4
+ * funkcja jest wyeksportowana, bo w Reakcie nie ma odpowiednika metody publicznej komponentu
5
+ * — eksport z kodu aplikacji wywołuje się właśnie tak.
6
+ */
7
+ export declare function exportTableCsv(columns: readonly KptColumn[], rows: readonly Record<string, unknown>[], filename?: string): void;
@@ -0,0 +1,19 @@
1
+ import { escapeCsvCell } from '@konce-pt/table';
2
+ /**
3
+ * Zrzuca wiersze do pliku CSV (separator `;`). Tabela woła to samo z przycisku „Eksport CSV";
4
+ * funkcja jest wyeksportowana, bo w Reakcie nie ma odpowiednika metody publicznej komponentu
5
+ * — eksport z kodu aplikacji wywołuje się właśnie tak.
6
+ */
7
+ export function exportTableCsv(columns, rows, filename = 'export.csv') {
8
+ const header = columns.map((col) => escapeCsvCell(col.header)).join(';');
9
+ const body = rows.map((row) => columns.map((col) => escapeCsvCell(row[col.field])).join(';')).join('\n');
10
+ // BOM — bez niego Excel czyta UTF-8 jako stronę kodową systemu i psuje polskie znaki.
11
+ const csv = `${header}\n${body}`;
12
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
13
+ const url = URL.createObjectURL(blob);
14
+ const link = document.createElement('a');
15
+ link.href = url;
16
+ link.download = filename;
17
+ link.click();
18
+ URL.revokeObjectURL(url);
19
+ }
@@ -0,0 +1,21 @@
1
+ import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
3
+ export type KptDataViewLayout = 'list' | 'grid';
4
+ export interface KptDataViewProps<T> extends Omit<ComponentPropsWithRef<'div'>, 'children'>, KptI18nOverrides {
5
+ items?: T[];
6
+ layout?: KptDataViewLayout;
7
+ defaultLayout?: KptDataViewLayout;
8
+ onLayoutChange?: (layout: KptDataViewLayout) => void;
9
+ /** Rozmiar strony; 0 wyłącza stronicowanie. */
10
+ pageSize?: number;
11
+ paginated?: boolean;
12
+ /** Szablon elementu — odpowiednik `<ng-template let-item>` z Angulara. */
13
+ renderItem: (item: T, index: number) => ReactNode;
14
+ }
15
+ /**
16
+ * DataView — lista elementów w układzie listy albo siatki, z opcjonalnym stronicowaniem.
17
+ *
18
+ * @example
19
+ * <KptDataView items={products} pageSize={6} renderItem={(p) => <div>{p.name}</div>} />
20
+ */
21
+ export declare function KptDataView<T>({ items, layout, defaultLayout, onLayoutChange, pageSize, paginated, renderItem, locale, dictionary, className, ...rest }: Readonly<KptDataViewProps<T>>): import("react").JSX.Element;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { cn } from "../internal/cn.js";
4
+ import { useControllableState } from "../internal/use-controllable-state.js";
5
+ import { useKptMessages } from "../i18n/i18n-context.js";
6
+ import { KptIcon } from "../icon/icon.js";
7
+ import { KptPaginator } from "../paginator/paginator.js";
8
+ /**
9
+ * DataView — lista elementów w układzie listy albo siatki, z opcjonalnym stronicowaniem.
10
+ *
11
+ * @example
12
+ * <KptDataView items={products} pageSize={6} renderItem={(p) => <div>{p.name}</div>} />
13
+ */
14
+ export function KptDataView({ items = [], layout, defaultLayout = 'grid', onLayoutChange, pageSize = 0, paginated = true, renderItem, locale, dictionary, className, ...rest }) {
15
+ const { t } = useKptMessages({ locale, dictionary });
16
+ const [currentLayout, setLayout] = useControllableState({
17
+ value: layout,
18
+ defaultValue: defaultLayout,
19
+ onChange: onLayoutChange,
20
+ });
21
+ const [pageIndex, setPageIndex] = useState(0);
22
+ const showPager = paginated && pageSize > 0 && items.length > pageSize;
23
+ const pageItems = !paginated || pageSize <= 0 ? items : items.slice(pageIndex * pageSize, pageIndex * pageSize + pageSize);
24
+ return (_jsx("div", { className: cn('kpt-data-view-host', className), ...rest, children: _jsxs("div", { className: "kpt-data-view", children: [_jsxs("div", { className: "kpt-data-view__toolbar", children: [_jsx("div", { className: "kpt-data-view__count", children: t('dataView.count', { count: items.length }) }), _jsxs("div", { className: "kpt-data-view__switch", children: [_jsx("button", { type: "button", className: cn(currentLayout === 'list' && 'is-active'), "aria-label": t('dataView.list'), onClick: () => setLayout('list'), children: _jsx(KptIcon, { name: "list" }) }), _jsx("button", { type: "button", className: cn(currentLayout === 'grid' && 'is-active'), "aria-label": t('dataView.grid'), onClick: () => setLayout('grid'), children: _jsx(KptIcon, { name: "menu" }) })] })] }), _jsx("div", { className: "kpt-data-view__content", "data-layout": currentLayout, children: pageItems.length ? (pageItems.map((item, index) => (_jsx("div", { className: "kpt-data-view__item", children: renderItem(item, index) }, index)))) : (_jsx("div", { className: "kpt-data-view__empty", children: t('dataView.empty') })) }), showPager ? (_jsx(KptPaginator, { length: items.length, pageIndex: pageIndex, onPageIndexChange: setPageIndex, pageSize: pageSize, pageSizeOptions: [pageSize] })) : null] }) }));
25
+ }
@@ -0,0 +1,30 @@
1
+ import { type ComponentPropsWithRef } from 'react';
2
+ import { type KptDateRangeMode, type KptDateRangeValue } from '@konce-pt/datetime';
3
+ import { type KptI18nOverrides } from '../i18n/i18n-context.tsx';
4
+ export type { KptDateRangeMode, KptDateRangeValue };
5
+ export interface KptDateRangeProps extends Omit<ComponentPropsWithRef<'div'>, 'onChange' | 'defaultValue'>, KptI18nOverrides {
6
+ value?: KptDateRangeValue;
7
+ defaultValue?: KptDateRangeValue;
8
+ onValueChange?: (value: KptDateRangeValue) => void;
9
+ mode?: KptDateRangeMode;
10
+ /** Liczba miesięcy obok siebie (1–3). Ignorowana w trybie 'time'. */
11
+ visibleMonths?: number;
12
+ hourFormat?: 12 | 24;
13
+ minuteStep?: number;
14
+ placeholder?: string;
15
+ displayFormat?: 'pl' | 'iso';
16
+ disabled?: boolean;
17
+ invalid?: boolean;
18
+ touched?: boolean;
19
+ onTouch?: () => void;
20
+ }
21
+ /**
22
+ * Wybór zakresu dat (i godzin) z kalendarzem. Pierwszy klik ustawia początek, drugi koniec;
23
+ * `visibleMonths` pokazuje kilka miesięcy obok siebie, a zakres podświetla się ciągłym pasmem
24
+ * przez wszystkie panele.
25
+ *
26
+ * @example
27
+ * <KptDateRange value={period} onValueChange={setPeriod} visibleMonths={2} />
28
+ * <KptDateRange mode="datetime" hourFormat={12} value={span} onValueChange={setSpan} />
29
+ */
30
+ export declare function KptDateRange({ value, defaultValue, onValueChange, mode, visibleMonths, hourFormat, minuteStep, placeholder, displayFormat, disabled, invalid, touched, onTouch, locale, dictionary, className, ...rest }: Readonly<KptDateRangeProps>): import("react").JSX.Element;