mvc-kit 2.12.4 → 2.13.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 (186) hide show
  1. package/agent-config/bin/postinstall.mjs +4 -3
  2. package/agent-config/bin/setup.mjs +5 -1
  3. package/agent-config/claude-code/agents/mvc-kit-architect.md +11 -8
  4. package/agent-config/claude-code/skills/guide/SKILL.md +20 -7
  5. package/agent-config/claude-code/skills/guide/patterns.md +12 -0
  6. package/agent-config/claude-code/skills/guide/recipes.md +510 -0
  7. package/agent-config/claude-code/skills/guide/testing.md +297 -0
  8. package/agent-config/claude-code/skills/review/SKILL.md +3 -13
  9. package/agent-config/claude-code/skills/review/checklist.md +30 -5
  10. package/agent-config/claude-code/skills/scaffold/SKILL.md +4 -13
  11. package/agent-config/lib/install-claude.mjs +84 -25
  12. package/dist/Channel.cjs +276 -300
  13. package/dist/Channel.cjs.map +1 -1
  14. package/dist/Channel.js +275 -299
  15. package/dist/Channel.js.map +1 -1
  16. package/dist/Collection.cjs +424 -504
  17. package/dist/Collection.cjs.map +1 -1
  18. package/dist/Collection.js +423 -503
  19. package/dist/Collection.js.map +1 -1
  20. package/dist/Controller.cjs +70 -67
  21. package/dist/Controller.cjs.map +1 -1
  22. package/dist/Controller.js +69 -66
  23. package/dist/Controller.js.map +1 -1
  24. package/dist/EventBus.cjs +77 -88
  25. package/dist/EventBus.cjs.map +1 -1
  26. package/dist/EventBus.js +76 -87
  27. package/dist/EventBus.js.map +1 -1
  28. package/dist/Feed.cjs +81 -77
  29. package/dist/Feed.cjs.map +1 -1
  30. package/dist/Feed.js +80 -76
  31. package/dist/Feed.js.map +1 -1
  32. package/dist/Model.cjs +181 -207
  33. package/dist/Model.cjs.map +1 -1
  34. package/dist/Model.js +179 -205
  35. package/dist/Model.js.map +1 -1
  36. package/dist/Pagination.cjs +75 -73
  37. package/dist/Pagination.cjs.map +1 -1
  38. package/dist/Pagination.js +74 -72
  39. package/dist/Pagination.js.map +1 -1
  40. package/dist/Pending.cjs +255 -287
  41. package/dist/Pending.cjs.map +1 -1
  42. package/dist/Pending.js +253 -285
  43. package/dist/Pending.js.map +1 -1
  44. package/dist/PersistentCollection.cjs +242 -285
  45. package/dist/PersistentCollection.cjs.map +1 -1
  46. package/dist/PersistentCollection.js +241 -284
  47. package/dist/PersistentCollection.js.map +1 -1
  48. package/dist/Resource.cjs +166 -174
  49. package/dist/Resource.cjs.map +1 -1
  50. package/dist/Resource.js +164 -172
  51. package/dist/Resource.js.map +1 -1
  52. package/dist/Selection.cjs +84 -94
  53. package/dist/Selection.cjs.map +1 -1
  54. package/dist/Selection.js +83 -93
  55. package/dist/Selection.js.map +1 -1
  56. package/dist/Service.cjs +54 -55
  57. package/dist/Service.cjs.map +1 -1
  58. package/dist/Service.js +53 -54
  59. package/dist/Service.js.map +1 -1
  60. package/dist/Sorting.cjs +102 -101
  61. package/dist/Sorting.cjs.map +1 -1
  62. package/dist/Sorting.js +102 -101
  63. package/dist/Sorting.js.map +1 -1
  64. package/dist/Trackable.cjs +112 -80
  65. package/dist/Trackable.cjs.map +1 -1
  66. package/dist/Trackable.js +111 -79
  67. package/dist/Trackable.js.map +1 -1
  68. package/dist/ViewModel.cjs +528 -576
  69. package/dist/ViewModel.cjs.map +1 -1
  70. package/dist/ViewModel.js +525 -573
  71. package/dist/ViewModel.js.map +1 -1
  72. package/dist/bindPublicMethods.cjs +43 -24
  73. package/dist/bindPublicMethods.cjs.map +1 -1
  74. package/dist/bindPublicMethods.js +43 -24
  75. package/dist/bindPublicMethods.js.map +1 -1
  76. package/dist/errors.cjs +67 -68
  77. package/dist/errors.cjs.map +1 -1
  78. package/dist/errors.js +68 -71
  79. package/dist/errors.js.map +1 -1
  80. package/dist/mvc-kit.cjs +44 -46
  81. package/dist/mvc-kit.js +5 -32
  82. package/dist/produceDraft.cjs +105 -95
  83. package/dist/produceDraft.cjs.map +1 -1
  84. package/dist/produceDraft.js +106 -97
  85. package/dist/produceDraft.js.map +1 -1
  86. package/dist/react/components/CardList.cjs +30 -40
  87. package/dist/react/components/CardList.cjs.map +1 -1
  88. package/dist/react/components/CardList.js +31 -41
  89. package/dist/react/components/CardList.js.map +1 -1
  90. package/dist/react/components/DataTable.cjs +146 -169
  91. package/dist/react/components/DataTable.cjs.map +1 -1
  92. package/dist/react/components/DataTable.js +147 -170
  93. package/dist/react/components/DataTable.js.map +1 -1
  94. package/dist/react/components/InfiniteScroll.cjs +51 -42
  95. package/dist/react/components/InfiniteScroll.cjs.map +1 -1
  96. package/dist/react/components/InfiniteScroll.js +52 -43
  97. package/dist/react/components/InfiniteScroll.js.map +1 -1
  98. package/dist/react/components/types.cjs +10 -6
  99. package/dist/react/components/types.cjs.map +1 -1
  100. package/dist/react/components/types.js +11 -9
  101. package/dist/react/components/types.js.map +1 -1
  102. package/dist/react/guards.cjs +10 -6
  103. package/dist/react/guards.cjs.map +1 -1
  104. package/dist/react/guards.js +11 -9
  105. package/dist/react/guards.js.map +1 -1
  106. package/dist/react/provider.cjs +23 -20
  107. package/dist/react/provider.cjs.map +1 -1
  108. package/dist/react/provider.js +23 -21
  109. package/dist/react/provider.js.map +1 -1
  110. package/dist/react/use-event-bus.cjs +24 -20
  111. package/dist/react/use-event-bus.cjs.map +1 -1
  112. package/dist/react/use-event-bus.js +24 -21
  113. package/dist/react/use-event-bus.js.map +1 -1
  114. package/dist/react/use-instance.cjs +43 -36
  115. package/dist/react/use-instance.cjs.map +1 -1
  116. package/dist/react/use-instance.js +43 -36
  117. package/dist/react/use-instance.js.map +1 -1
  118. package/dist/react/use-local.cjs +48 -64
  119. package/dist/react/use-local.cjs.map +1 -1
  120. package/dist/react/use-local.js +47 -63
  121. package/dist/react/use-local.js.map +1 -1
  122. package/dist/react/use-model.cjs +84 -98
  123. package/dist/react/use-model.cjs.map +1 -1
  124. package/dist/react/use-model.js +84 -100
  125. package/dist/react/use-model.js.map +1 -1
  126. package/dist/react/use-singleton.cjs +19 -23
  127. package/dist/react/use-singleton.cjs.map +1 -1
  128. package/dist/react/use-singleton.js +16 -20
  129. package/dist/react/use-singleton.js.map +1 -1
  130. package/dist/react/use-subscribe-only.cjs +28 -22
  131. package/dist/react/use-subscribe-only.cjs.map +1 -1
  132. package/dist/react/use-subscribe-only.js +28 -22
  133. package/dist/react/use-subscribe-only.js.map +1 -1
  134. package/dist/react/use-teardown.cjs +20 -19
  135. package/dist/react/use-teardown.cjs.map +1 -1
  136. package/dist/react/use-teardown.js +20 -19
  137. package/dist/react/use-teardown.js.map +1 -1
  138. package/dist/react-native/NativeCollection.cjs +98 -78
  139. package/dist/react-native/NativeCollection.cjs.map +1 -1
  140. package/dist/react-native/NativeCollection.js +97 -77
  141. package/dist/react-native/NativeCollection.js.map +1 -1
  142. package/dist/react-native.cjs +2 -4
  143. package/dist/react-native.js +1 -4
  144. package/dist/react.cjs +24 -26
  145. package/dist/react.js +1 -17
  146. package/dist/singleton.cjs +28 -22
  147. package/dist/singleton.cjs.map +1 -1
  148. package/dist/singleton.js +29 -26
  149. package/dist/singleton.js.map +1 -1
  150. package/dist/walkPrototypeChain.cjs +20 -12
  151. package/dist/walkPrototypeChain.cjs.map +1 -1
  152. package/dist/walkPrototypeChain.js +21 -13
  153. package/dist/walkPrototypeChain.js.map +1 -1
  154. package/dist/web/IndexedDBCollection.cjs +53 -36
  155. package/dist/web/IndexedDBCollection.cjs.map +1 -1
  156. package/dist/web/IndexedDBCollection.js +52 -35
  157. package/dist/web/IndexedDBCollection.js.map +1 -1
  158. package/dist/web/WebStorageCollection.cjs +82 -84
  159. package/dist/web/WebStorageCollection.cjs.map +1 -1
  160. package/dist/web/WebStorageCollection.js +81 -83
  161. package/dist/web/WebStorageCollection.js.map +1 -1
  162. package/dist/web/idb.cjs +107 -99
  163. package/dist/web/idb.cjs.map +1 -1
  164. package/dist/web/idb.js +108 -105
  165. package/dist/web/idb.js.map +1 -1
  166. package/dist/web.cjs +4 -6
  167. package/dist/web.js +1 -5
  168. package/dist/wrapAsyncMethods.cjs +141 -168
  169. package/dist/wrapAsyncMethods.cjs.map +1 -1
  170. package/dist/wrapAsyncMethods.js +141 -168
  171. package/dist/wrapAsyncMethods.js.map +1 -1
  172. package/package.json +8 -8
  173. package/src/Pending.test.ts +1 -2
  174. package/src/Sorting.test.ts +1 -1
  175. package/src/produceDraft.test.ts +3 -3
  176. package/src/react/components/CardList.test.tsx +1 -1
  177. package/src/react/components/DataTable.test.tsx +1 -1
  178. package/src/react/components/InfiniteScroll.test.tsx +5 -5
  179. package/dist/mvc-kit.cjs.map +0 -1
  180. package/dist/mvc-kit.js.map +0 -1
  181. package/dist/react-native.cjs.map +0 -1
  182. package/dist/react-native.js.map +0 -1
  183. package/dist/react.cjs.map +0 -1
  184. package/dist/react.js.map +0 -1
  185. package/dist/web.cjs.map +0 -1
  186. package/dist/web.js.map +0 -1
@@ -1,179 +1,156 @@
1
- import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
- import { isSelectionHelper, isSortingHelper, isPaginationHelper } from "./types.js";
1
+ import { isPaginationHelper, isSelectionHelper, isSortingHelper } from "./types.js";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/react/components/DataTable.tsx
3
4
  function resolveSelectionProp(selection) {
4
- if (isSelectionHelper(selection)) {
5
- return {
6
- selected: selection.selected,
7
- onToggle: (key) => selection.toggle(key),
8
- onToggleAll: (allKeys) => selection.toggleAll(allKeys)
9
- };
10
- }
11
- return {
12
- selected: selection.selected,
13
- onToggle: selection.onToggle,
14
- onToggleAll: (allKeys) => selection.onToggleAll(allKeys)
15
- };
5
+ if (isSelectionHelper(selection)) return {
6
+ selected: selection.selected,
7
+ onToggle: (key) => selection.toggle(key),
8
+ onToggleAll: (allKeys) => selection.toggleAll(allKeys)
9
+ };
10
+ return {
11
+ selected: selection.selected,
12
+ onToggle: selection.onToggle,
13
+ onToggleAll: (allKeys) => selection.onToggleAll(allKeys)
14
+ };
16
15
  }
17
16
  function resolveSortProp(sort, onSort) {
18
- if (isSortingHelper(sort)) {
19
- return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };
20
- }
21
- return { sorts: sort, onSort };
17
+ if (isSortingHelper(sort)) return {
18
+ sorts: sort.sorts,
19
+ onSort: (key) => sort.toggle(key)
20
+ };
21
+ return {
22
+ sorts: sort,
23
+ onSort
24
+ };
22
25
  }
23
26
  function resolvePaginationProp(pagination, pageSize, paginationTotal) {
24
- if (isPaginationHelper(pagination)) {
25
- const total2 = paginationTotal ?? 0;
26
- const ps2 = pagination.pageSize;
27
- const pageCount2 = Math.max(1, Math.ceil(total2 / ps2));
28
- const page2 = pagination.page;
29
- return {
30
- page: page2,
31
- pageCount: pageCount2,
32
- total: total2,
33
- pageSize: ps2,
34
- hasPrev: page2 > 1,
35
- hasNext: page2 < pageCount2,
36
- goToPage: (p) => pagination.setPage(p),
37
- goPrev: () => pagination.setPage(page2 - 1),
38
- goNext: () => pagination.setPage(page2 + 1)
39
- };
40
- }
41
- const total = pagination.total;
42
- const ps = pageSize ?? 10;
43
- const pageCount = Math.max(1, Math.ceil(total / ps));
44
- const page = pagination.page;
45
- return {
46
- page,
47
- pageCount,
48
- total,
49
- pageSize: ps,
50
- hasPrev: page > 1,
51
- hasNext: page < pageCount,
52
- goToPage: pagination.onPageChange,
53
- goPrev: () => pagination.onPageChange(page - 1),
54
- goNext: () => pagination.onPageChange(page + 1)
55
- };
27
+ if (isPaginationHelper(pagination)) {
28
+ const total = paginationTotal ?? 0;
29
+ const ps = pagination.pageSize;
30
+ const pageCount = Math.max(1, Math.ceil(total / ps));
31
+ const page = pagination.page;
32
+ return {
33
+ page,
34
+ pageCount,
35
+ total,
36
+ pageSize: ps,
37
+ hasPrev: page > 1,
38
+ hasNext: page < pageCount,
39
+ goToPage: (p) => pagination.setPage(p),
40
+ goPrev: () => pagination.setPage(page - 1),
41
+ goNext: () => pagination.setPage(page + 1)
42
+ };
43
+ }
44
+ const total = pagination.total;
45
+ const ps = pageSize ?? 10;
46
+ const pageCount = Math.max(1, Math.ceil(total / ps));
47
+ const page = pagination.page;
48
+ return {
49
+ page,
50
+ pageCount,
51
+ total,
52
+ pageSize: ps,
53
+ hasPrev: page > 1,
54
+ hasNext: page < pageCount,
55
+ goToPage: pagination.onPageChange,
56
+ goPrev: () => pagination.onPageChange(page - 1),
57
+ goNext: () => pagination.onPageChange(page + 1)
58
+ };
56
59
  }
57
- const defaultKeyOf = (item) => item.id;
60
+ var defaultKeyOf = (item) => item.id;
58
61
  function getAriaSortValue(key, sorts) {
59
- if (!sorts) return "none";
60
- const desc = sorts.find((s) => s.key === key);
61
- if (!desc) return "none";
62
- return desc.direction === "asc" ? "ascending" : "descending";
62
+ if (!sorts) return "none";
63
+ const desc = sorts.find((s) => s.key === key);
64
+ if (!desc) return "none";
65
+ return desc.direction === "asc" ? "ascending" : "descending";
63
66
  }
64
- function DataTable({
65
- items,
66
- columns,
67
- keyOf = defaultKeyOf,
68
- pageSize,
69
- sort,
70
- onSort,
71
- selection,
72
- loading,
73
- error,
74
- pagination,
75
- paginationTotal,
76
- renderEmpty,
77
- renderLoading,
78
- renderError,
79
- renderSortIndicator,
80
- renderRow,
81
- renderPagination,
82
- className,
83
- "aria-label": ariaLabel
84
- }) {
85
- if (loading && renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
86
- if (error && renderError) return /* @__PURE__ */ jsx(Fragment, { children: renderError(error) });
87
- if (items.length === 0 && renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
88
- const resolvedSelection = selection ? resolveSelectionProp(selection) : void 0;
89
- let resolvedSorts;
90
- let resolvedOnSort;
91
- if (sort) {
92
- const resolved = resolveSortProp(sort, onSort);
93
- resolvedSorts = resolved.sorts;
94
- resolvedOnSort = resolved.onSort;
95
- }
96
- let displayItems = items;
97
- let paginationInfo;
98
- if (pagination) {
99
- paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);
100
- } else if (pageSize && !pagination) {
101
- displayItems = items.slice(0, pageSize);
102
- }
103
- const allKeys = resolvedSelection ? displayItems.map((item) => keyOf(item)) : [];
104
- const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every((k) => resolvedSelection.selected.has(k));
105
- const someSelected = resolvedSelection && allKeys.some((k) => resolvedSelection.selected.has(k));
106
- return /* @__PURE__ */ jsxs("div", { "data-component": "data-table", className, children: [
107
- /* @__PURE__ */ jsxs("table", { role: "grid", "aria-label": ariaLabel, children: [
108
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
109
- resolvedSelection && /* @__PURE__ */ jsx("th", { "data-column": "select", children: /* @__PURE__ */ jsx(
110
- "input",
111
- {
112
- type: "checkbox",
113
- checked: !!allSelected,
114
- ref: (el) => {
115
- if (el) el.indeterminate = !!someSelected && !allSelected;
116
- },
117
- onChange: () => resolvedSelection.onToggleAll(allKeys),
118
- "aria-label": "Select all"
119
- }
120
- ) }),
121
- columns.map((col) => {
122
- const isSortable = col.sortable && resolvedOnSort;
123
- const sortDesc = resolvedSorts?.find((s) => s.key === col.key);
124
- const isActive = !!sortDesc;
125
- const sortIndex = resolvedSorts ? resolvedSorts.findIndex((s) => s.key === col.key) : -1;
126
- return /* @__PURE__ */ jsx(
127
- "th",
128
- {
129
- "data-sortable": isSortable ? "" : void 0,
130
- "data-sorted": isActive ? "" : void 0,
131
- "data-align": col.align,
132
- style: col.width ? { width: col.width } : void 0,
133
- "aria-sort": isSortable ? getAriaSortValue(col.key, resolvedSorts) : void 0,
134
- children: isSortable ? /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => resolvedOnSort(col.key), children: [
135
- col.header,
136
- renderSortIndicator?.({
137
- active: isActive,
138
- direction: sortDesc?.direction ?? "asc",
139
- index: sortIndex,
140
- onToggle: () => resolvedOnSort(col.key)
141
- })
142
- ] }) : col.header
143
- },
144
- col.key
145
- );
146
- })
147
- ] }) }),
148
- /* @__PURE__ */ jsx("tbody", { children: displayItems.map((item, index) => {
149
- const key = keyOf(item);
150
- const isSelected = resolvedSelection?.selected.has(key);
151
- const cells = /* @__PURE__ */ jsxs(Fragment, { children: [
152
- resolvedSelection && /* @__PURE__ */ jsx("td", { "data-column": "select", children: /* @__PURE__ */ jsx(
153
- "input",
154
- {
155
- type: "checkbox",
156
- checked: !!isSelected,
157
- onChange: () => resolvedSelection.onToggle(key),
158
- "aria-label": `Select row ${key}`
159
- }
160
- ) }),
161
- columns.map((col) => /* @__PURE__ */ jsx(
162
- "td",
163
- {
164
- "data-align": col.align,
165
- children: col.render(item, index)
166
- },
167
- col.key
168
- ))
169
- ] });
170
- return /* @__PURE__ */ jsx("tr", { "data-selected": isSelected ? "" : void 0, children: renderRow ? renderRow(item, index, cells) : cells }, key);
171
- }) })
172
- ] }),
173
- paginationInfo && renderPagination?.(paginationInfo)
174
- ] });
67
+ /**
68
+ * Headless data table with sort headers, selection checkboxes, and pagination slots.
69
+ * Renders semantic HTML (`<table>`) with data attributes for styling.
70
+ * Accepts Sorting/Selection/Pagination helpers directly via duck-typing.
71
+ */
72
+ function DataTable({ items, columns, keyOf = defaultKeyOf, pageSize, sort, onSort, selection, loading, error, pagination, paginationTotal, renderEmpty, renderLoading, renderError, renderSortIndicator, renderRow, renderPagination, className, "aria-label": ariaLabel }) {
73
+ if (loading && renderLoading) return /* @__PURE__ */ jsx(Fragment, { children: renderLoading() });
74
+ if (error && renderError) return /* @__PURE__ */ jsx(Fragment, { children: renderError(error) });
75
+ if (items.length === 0 && renderEmpty) return /* @__PURE__ */ jsx(Fragment, { children: renderEmpty() });
76
+ const resolvedSelection = selection ? resolveSelectionProp(selection) : void 0;
77
+ let resolvedSorts;
78
+ let resolvedOnSort;
79
+ if (sort) {
80
+ const resolved = resolveSortProp(sort, onSort);
81
+ resolvedSorts = resolved.sorts;
82
+ resolvedOnSort = resolved.onSort;
83
+ }
84
+ let displayItems = items;
85
+ let paginationInfo;
86
+ if (pagination) paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);
87
+ else if (pageSize && !pagination) displayItems = items.slice(0, pageSize);
88
+ const allKeys = resolvedSelection ? displayItems.map((item) => keyOf(item)) : [];
89
+ const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every((k) => resolvedSelection.selected.has(k));
90
+ const someSelected = resolvedSelection && allKeys.some((k) => resolvedSelection.selected.has(k));
91
+ return /* @__PURE__ */ jsxs("div", {
92
+ "data-component": "data-table",
93
+ className,
94
+ children: [/* @__PURE__ */ jsxs("table", {
95
+ role: "grid",
96
+ "aria-label": ariaLabel,
97
+ children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [resolvedSelection && /* @__PURE__ */ jsx("th", {
98
+ "data-column": "select",
99
+ children: /* @__PURE__ */ jsx("input", {
100
+ type: "checkbox",
101
+ checked: !!allSelected,
102
+ ref: (el) => {
103
+ if (el) el.indeterminate = !!someSelected && !allSelected;
104
+ },
105
+ onChange: () => resolvedSelection.onToggleAll(allKeys),
106
+ "aria-label": "Select all"
107
+ })
108
+ }), columns.map((col) => {
109
+ const isSortable = col.sortable && resolvedOnSort;
110
+ const sortDesc = resolvedSorts?.find((s) => s.key === col.key);
111
+ const isActive = !!sortDesc;
112
+ const sortIndex = resolvedSorts ? resolvedSorts.findIndex((s) => s.key === col.key) : -1;
113
+ return /* @__PURE__ */ jsx("th", {
114
+ "data-sortable": isSortable ? "" : void 0,
115
+ "data-sorted": isActive ? "" : void 0,
116
+ "data-align": col.align,
117
+ style: col.width ? { width: col.width } : void 0,
118
+ "aria-sort": isSortable ? getAriaSortValue(col.key, resolvedSorts) : void 0,
119
+ children: isSortable ? /* @__PURE__ */ jsxs("button", {
120
+ type: "button",
121
+ onClick: () => resolvedOnSort(col.key),
122
+ children: [col.header, renderSortIndicator?.({
123
+ active: isActive,
124
+ direction: sortDesc?.direction ?? "asc",
125
+ index: sortIndex,
126
+ onToggle: () => resolvedOnSort(col.key)
127
+ })]
128
+ }) : col.header
129
+ }, col.key);
130
+ })] }) }), /* @__PURE__ */ jsx("tbody", { children: displayItems.map((item, index) => {
131
+ const key = keyOf(item);
132
+ const isSelected = resolvedSelection?.selected.has(key);
133
+ const cells = /* @__PURE__ */ jsxs(Fragment, { children: [resolvedSelection && /* @__PURE__ */ jsx("td", {
134
+ "data-column": "select",
135
+ children: /* @__PURE__ */ jsx("input", {
136
+ type: "checkbox",
137
+ checked: !!isSelected,
138
+ onChange: () => resolvedSelection.onToggle(key),
139
+ "aria-label": `Select row ${key}`
140
+ })
141
+ }), columns.map((col) => /* @__PURE__ */ jsx("td", {
142
+ "data-align": col.align,
143
+ children: col.render(item, index)
144
+ }, col.key))] });
145
+ return /* @__PURE__ */ jsx("tr", {
146
+ "data-selected": isSelected ? "" : void 0,
147
+ children: renderRow ? renderRow(item, index, cells) : cells
148
+ }, key);
149
+ }) })]
150
+ }), paginationInfo && renderPagination?.(paginationInfo)]
151
+ });
175
152
  }
176
- export {
177
- DataTable
178
- };
179
- //# sourceMappingURL=DataTable.js.map
153
+ //#endregion
154
+ export { DataTable };
155
+
156
+ //# sourceMappingURL=DataTable.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","sources":["../../../src/react/components/DataTable.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n Column,\n SortHeaderProps,\n SelectionState,\n SelectionHelper,\n PaginationState,\n PaginationHelper,\n PaginationInfo,\n SortingHelper,\n AsyncStateProps,\n} from './types';\nimport { isSelectionHelper, isPaginationHelper, isSortingHelper } from './types';\nimport type { SortDescriptor } from '../../Sorting';\n\n// ── Prop resolution helpers ──\n\ninterface ResolvedSelection {\n selected: ReadonlySet<any>;\n onToggle: (key: any) => void;\n onToggleAll: (allKeys: any[]) => void;\n}\n\nfunction resolveSelectionProp(selection: SelectionState | SelectionHelper): ResolvedSelection {\n if (isSelectionHelper(selection)) {\n return {\n selected: selection.selected,\n onToggle: (key) => selection.toggle(key),\n onToggleAll: (allKeys) => selection.toggleAll(allKeys),\n };\n }\n return {\n selected: selection.selected,\n onToggle: selection.onToggle,\n onToggleAll: (allKeys) => selection.onToggleAll(allKeys),\n };\n}\n\nfunction resolveSortProp(\n sort: readonly SortDescriptor[] | SortingHelper,\n onSort?: (key: string) => void,\n): { sorts: readonly SortDescriptor[]; onSort: ((key: string) => void) | undefined } {\n if (isSortingHelper(sort)) {\n return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };\n }\n return { sorts: sort, onSort };\n}\n\nfunction resolvePaginationProp(\n pagination: PaginationState | PaginationHelper,\n pageSize?: number,\n paginationTotal?: number,\n): PaginationInfo {\n if (isPaginationHelper(pagination)) {\n const total = paginationTotal ?? 0;\n const ps = pagination.pageSize;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: (p) => pagination.setPage(p),\n goPrev: () => pagination.setPage(page - 1),\n goNext: () => pagination.setPage(page + 1),\n };\n }\n const total = pagination.total;\n const ps = pageSize ?? 10;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: pagination.onPageChange,\n goPrev: () => pagination.onPageChange(page - 1),\n goNext: () => pagination.onPageChange(page + 1),\n };\n}\n\n/** Props for the DataTable headless component. */\nexport interface DataTableProps<T> extends AsyncStateProps {\n items: T[];\n columns: Column<T>[];\n keyOf?: (item: T) => string | number;\n pageSize?: number;\n\n // Controlled state — accepts object-literal OR helper instance\n sort?: readonly SortDescriptor[] | SortingHelper;\n onSort?: (key: string) => void;\n selection?: SelectionState | SelectionHelper;\n pagination?: PaginationState | PaginationHelper;\n paginationTotal?: number;\n\n // Render slots\n renderEmpty?: () => ReactNode;\n renderLoading?: () => ReactNode;\n renderError?: (error: string) => ReactNode;\n renderSortIndicator?: (props: SortHeaderProps) => ReactNode;\n renderRow?: (item: T, index: number, defaultCells: ReactNode) => ReactNode;\n renderPagination?: (info: PaginationInfo) => ReactNode;\n\n className?: string;\n 'aria-label'?: string;\n}\n\nconst defaultKeyOf = (item: any) => item.id;\n\nfunction getAriaSortValue(key: string, sorts: readonly SortDescriptor[] | undefined): 'ascending' | 'descending' | 'none' {\n if (!sorts) return 'none';\n const desc = sorts.find(s => s.key === key);\n if (!desc) return 'none';\n return desc.direction === 'asc' ? 'ascending' : 'descending';\n}\n\n/**\n * Headless data table with sort headers, selection checkboxes, and pagination slots.\n * Renders semantic HTML (`<table>`) with data attributes for styling.\n * Accepts Sorting/Selection/Pagination helpers directly via duck-typing.\n */\nexport function DataTable<T>({\n items,\n columns,\n keyOf = defaultKeyOf,\n pageSize,\n sort,\n onSort,\n selection,\n loading,\n error,\n pagination,\n paginationTotal,\n renderEmpty,\n renderLoading,\n renderError,\n renderSortIndicator,\n renderRow,\n renderPagination,\n className,\n 'aria-label': ariaLabel,\n}: DataTableProps<T>) {\n if (loading && renderLoading) return <>{renderLoading()}</>;\n if (error && renderError) return <>{renderError(error)}</>;\n if (items.length === 0 && renderEmpty) return <>{renderEmpty()}</>;\n\n // ── Resolve props ──\n const resolvedSelection = selection ? resolveSelectionProp(selection) : undefined;\n\n let resolvedSorts: readonly SortDescriptor[] | undefined;\n let resolvedOnSort: ((key: string) => void) | undefined;\n if (sort) {\n const resolved = resolveSortProp(sort, onSort);\n resolvedSorts = resolved.sorts;\n resolvedOnSort = resolved.onSort;\n }\n\n let displayItems = items;\n let paginationInfo: PaginationInfo | undefined;\n if (pagination) {\n paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);\n } else if (pageSize && !pagination) {\n displayItems = items.slice(0, pageSize);\n }\n\n // Selection: check indeterminate state\n const allKeys = resolvedSelection ? displayItems.map(item => keyOf(item)) : [];\n const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every(k => resolvedSelection!.selected.has(k));\n const someSelected = resolvedSelection && allKeys.some(k => resolvedSelection!.selected.has(k));\n\n return (\n <div data-component=\"data-table\" className={className}>\n <table role=\"grid\" aria-label={ariaLabel}>\n <thead>\n <tr>\n {resolvedSelection && (\n <th data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!allSelected}\n ref={(el) => {\n if (el) el.indeterminate = !!someSelected && !allSelected;\n }}\n onChange={() => resolvedSelection!.onToggleAll(allKeys)}\n aria-label=\"Select all\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSortable = col.sortable && resolvedOnSort;\n const sortDesc = resolvedSorts?.find(s => s.key === col.key);\n const isActive = !!sortDesc;\n const sortIndex = resolvedSorts ? resolvedSorts.findIndex(s => s.key === col.key) : -1;\n\n return (\n <th\n key={col.key}\n data-sortable={isSortable ? '' : undefined}\n data-sorted={isActive ? '' : undefined}\n data-align={col.align}\n style={col.width ? { width: col.width } : undefined}\n aria-sort={isSortable ? getAriaSortValue(col.key, resolvedSorts) : undefined}\n >\n {isSortable ? (\n <button type=\"button\" onClick={() => resolvedOnSort!(col.key)}>\n {col.header}\n {renderSortIndicator?.({\n active: isActive,\n direction: sortDesc?.direction ?? 'asc',\n index: sortIndex,\n onToggle: () => resolvedOnSort!(col.key),\n })}\n </button>\n ) : (\n col.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {displayItems.map((item, index) => {\n const key = keyOf(item);\n const isSelected = resolvedSelection?.selected.has(key);\n\n const cells = (\n <>\n {resolvedSelection && (\n <td data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!isSelected}\n onChange={() => resolvedSelection!.onToggle(key)}\n aria-label={`Select row ${key}`}\n />\n </td>\n )}\n {columns.map((col) => (\n <td\n key={col.key}\n data-align={col.align}\n >\n {col.render(item, index)}\n </td>\n ))}\n </>\n );\n\n return (\n <tr key={key} data-selected={isSelected ? '' : undefined}>\n {renderRow ? renderRow(item, index, cells) : cells}\n </tr>\n );\n })}\n </tbody>\n </table>\n {paginationInfo && renderPagination?.(paginationInfo)}\n </div>\n );\n}\n"],"names":["total","ps","pageCount","page"],"mappings":"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"}
1
+ {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/react/components/DataTable.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type {\n Column,\n SortHeaderProps,\n SelectionState,\n SelectionHelper,\n PaginationState,\n PaginationHelper,\n PaginationInfo,\n SortingHelper,\n AsyncStateProps,\n} from './types';\nimport { isSelectionHelper, isPaginationHelper, isSortingHelper } from './types';\nimport type { SortDescriptor } from '../../Sorting';\n\n// ── Prop resolution helpers ──\n\ninterface ResolvedSelection {\n selected: ReadonlySet<any>;\n onToggle: (key: any) => void;\n onToggleAll: (allKeys: any[]) => void;\n}\n\nfunction resolveSelectionProp(selection: SelectionState | SelectionHelper): ResolvedSelection {\n if (isSelectionHelper(selection)) {\n return {\n selected: selection.selected,\n onToggle: (key) => selection.toggle(key),\n onToggleAll: (allKeys) => selection.toggleAll(allKeys),\n };\n }\n return {\n selected: selection.selected,\n onToggle: selection.onToggle,\n onToggleAll: (allKeys) => selection.onToggleAll(allKeys),\n };\n}\n\nfunction resolveSortProp(\n sort: readonly SortDescriptor[] | SortingHelper,\n onSort?: (key: string) => void,\n): { sorts: readonly SortDescriptor[]; onSort: ((key: string) => void) | undefined } {\n if (isSortingHelper(sort)) {\n return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };\n }\n return { sorts: sort, onSort };\n}\n\nfunction resolvePaginationProp(\n pagination: PaginationState | PaginationHelper,\n pageSize?: number,\n paginationTotal?: number,\n): PaginationInfo {\n if (isPaginationHelper(pagination)) {\n const total = paginationTotal ?? 0;\n const ps = pagination.pageSize;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: (p) => pagination.setPage(p),\n goPrev: () => pagination.setPage(page - 1),\n goNext: () => pagination.setPage(page + 1),\n };\n }\n const total = pagination.total;\n const ps = pageSize ?? 10;\n const pageCount = Math.max(1, Math.ceil(total / ps));\n const page = pagination.page;\n return {\n page,\n pageCount,\n total,\n pageSize: ps,\n hasPrev: page > 1,\n hasNext: page < pageCount,\n goToPage: pagination.onPageChange,\n goPrev: () => pagination.onPageChange(page - 1),\n goNext: () => pagination.onPageChange(page + 1),\n };\n}\n\n/** Props for the DataTable headless component. */\nexport interface DataTableProps<T> extends AsyncStateProps {\n items: T[];\n columns: Column<T>[];\n keyOf?: (item: T) => string | number;\n pageSize?: number;\n\n // Controlled state — accepts object-literal OR helper instance\n sort?: readonly SortDescriptor[] | SortingHelper;\n onSort?: (key: string) => void;\n selection?: SelectionState | SelectionHelper;\n pagination?: PaginationState | PaginationHelper;\n paginationTotal?: number;\n\n // Render slots\n renderEmpty?: () => ReactNode;\n renderLoading?: () => ReactNode;\n renderError?: (error: string) => ReactNode;\n renderSortIndicator?: (props: SortHeaderProps) => ReactNode;\n renderRow?: (item: T, index: number, defaultCells: ReactNode) => ReactNode;\n renderPagination?: (info: PaginationInfo) => ReactNode;\n\n className?: string;\n 'aria-label'?: string;\n}\n\nconst defaultKeyOf = (item: any) => item.id;\n\nfunction getAriaSortValue(key: string, sorts: readonly SortDescriptor[] | undefined): 'ascending' | 'descending' | 'none' {\n if (!sorts) return 'none';\n const desc = sorts.find(s => s.key === key);\n if (!desc) return 'none';\n return desc.direction === 'asc' ? 'ascending' : 'descending';\n}\n\n/**\n * Headless data table with sort headers, selection checkboxes, and pagination slots.\n * Renders semantic HTML (`<table>`) with data attributes for styling.\n * Accepts Sorting/Selection/Pagination helpers directly via duck-typing.\n */\nexport function DataTable<T>({\n items,\n columns,\n keyOf = defaultKeyOf,\n pageSize,\n sort,\n onSort,\n selection,\n loading,\n error,\n pagination,\n paginationTotal,\n renderEmpty,\n renderLoading,\n renderError,\n renderSortIndicator,\n renderRow,\n renderPagination,\n className,\n 'aria-label': ariaLabel,\n}: DataTableProps<T>) {\n if (loading && renderLoading) return <>{renderLoading()}</>;\n if (error && renderError) return <>{renderError(error)}</>;\n if (items.length === 0 && renderEmpty) return <>{renderEmpty()}</>;\n\n // ── Resolve props ──\n const resolvedSelection = selection ? resolveSelectionProp(selection) : undefined;\n\n let resolvedSorts: readonly SortDescriptor[] | undefined;\n let resolvedOnSort: ((key: string) => void) | undefined;\n if (sort) {\n const resolved = resolveSortProp(sort, onSort);\n resolvedSorts = resolved.sorts;\n resolvedOnSort = resolved.onSort;\n }\n\n let displayItems = items;\n let paginationInfo: PaginationInfo | undefined;\n if (pagination) {\n paginationInfo = resolvePaginationProp(pagination, pageSize, paginationTotal);\n } else if (pageSize && !pagination) {\n displayItems = items.slice(0, pageSize);\n }\n\n // Selection: check indeterminate state\n const allKeys = resolvedSelection ? displayItems.map(item => keyOf(item)) : [];\n const allSelected = resolvedSelection && allKeys.length > 0 && allKeys.every(k => resolvedSelection!.selected.has(k));\n const someSelected = resolvedSelection && allKeys.some(k => resolvedSelection!.selected.has(k));\n\n return (\n <div data-component=\"data-table\" className={className}>\n <table role=\"grid\" aria-label={ariaLabel}>\n <thead>\n <tr>\n {resolvedSelection && (\n <th data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!allSelected}\n ref={(el) => {\n if (el) el.indeterminate = !!someSelected && !allSelected;\n }}\n onChange={() => resolvedSelection!.onToggleAll(allKeys)}\n aria-label=\"Select all\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSortable = col.sortable && resolvedOnSort;\n const sortDesc = resolvedSorts?.find(s => s.key === col.key);\n const isActive = !!sortDesc;\n const sortIndex = resolvedSorts ? resolvedSorts.findIndex(s => s.key === col.key) : -1;\n\n return (\n <th\n key={col.key}\n data-sortable={isSortable ? '' : undefined}\n data-sorted={isActive ? '' : undefined}\n data-align={col.align}\n style={col.width ? { width: col.width } : undefined}\n aria-sort={isSortable ? getAriaSortValue(col.key, resolvedSorts) : undefined}\n >\n {isSortable ? (\n <button type=\"button\" onClick={() => resolvedOnSort!(col.key)}>\n {col.header}\n {renderSortIndicator?.({\n active: isActive,\n direction: sortDesc?.direction ?? 'asc',\n index: sortIndex,\n onToggle: () => resolvedOnSort!(col.key),\n })}\n </button>\n ) : (\n col.header\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {displayItems.map((item, index) => {\n const key = keyOf(item);\n const isSelected = resolvedSelection?.selected.has(key);\n\n const cells = (\n <>\n {resolvedSelection && (\n <td data-column=\"select\">\n <input\n type=\"checkbox\"\n checked={!!isSelected}\n onChange={() => resolvedSelection!.onToggle(key)}\n aria-label={`Select row ${key}`}\n />\n </td>\n )}\n {columns.map((col) => (\n <td\n key={col.key}\n data-align={col.align}\n >\n {col.render(item, index)}\n </td>\n ))}\n </>\n );\n\n return (\n <tr key={key} data-selected={isSelected ? '' : undefined}>\n {renderRow ? renderRow(item, index, cells) : cells}\n </tr>\n );\n })}\n </tbody>\n </table>\n {paginationInfo && renderPagination?.(paginationInfo)}\n </div>\n );\n}\n"],"mappings":";;;AAuBA,SAAS,qBAAqB,WAAgE;AAC5F,KAAI,kBAAkB,UAAU,CAC9B,QAAO;EACL,UAAU,UAAU;EACpB,WAAW,QAAQ,UAAU,OAAO,IAAI;EACxC,cAAc,YAAY,UAAU,UAAU,QAAQ;EACvD;AAEH,QAAO;EACL,UAAU,UAAU;EACpB,UAAU,UAAU;EACpB,cAAc,YAAY,UAAU,YAAY,QAAQ;EACzD;;AAGH,SAAS,gBACP,MACA,QACmF;AACnF,KAAI,gBAAgB,KAAK,CACvB,QAAO;EAAE,OAAO,KAAK;EAAO,SAAS,QAAQ,KAAK,OAAO,IAAI;EAAE;AAEjE,QAAO;EAAE,OAAO;EAAM;EAAQ;;AAGhC,SAAS,sBACP,YACA,UACA,iBACgB;AAChB,KAAI,mBAAmB,WAAW,EAAE;EAClC,MAAM,QAAQ,mBAAmB;EACjC,MAAM,KAAK,WAAW;EACtB,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,GAAG,CAAC;EACpD,MAAM,OAAO,WAAW;AACxB,SAAO;GACL;GACA;GACA;GACA,UAAU;GACV,SAAS,OAAO;GAChB,SAAS,OAAO;GAChB,WAAW,MAAM,WAAW,QAAQ,EAAE;GACtC,cAAc,WAAW,QAAQ,OAAO,EAAE;GAC1C,cAAc,WAAW,QAAQ,OAAO,EAAE;GAC3C;;CAEH,MAAM,QAAQ,WAAW;CACzB,MAAM,KAAK,YAAY;CACvB,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,GAAG,CAAC;CACpD,MAAM,OAAO,WAAW;AACxB,QAAO;EACL;EACA;EACA;EACA,UAAU;EACV,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,UAAU,WAAW;EACrB,cAAc,WAAW,aAAa,OAAO,EAAE;EAC/C,cAAc,WAAW,aAAa,OAAO,EAAE;EAChD;;AA6BH,IAAM,gBAAgB,SAAc,KAAK;AAEzC,SAAS,iBAAiB,KAAa,OAAmF;AACxH,KAAI,CAAC,MAAO,QAAO;CACnB,MAAM,OAAO,MAAM,MAAK,MAAK,EAAE,QAAQ,IAAI;AAC3C,KAAI,CAAC,KAAM,QAAO;AAClB,QAAO,KAAK,cAAc,QAAQ,cAAc;;;;;;;AAQlD,SAAgB,UAAa,EAC3B,OACA,SACA,QAAQ,cACR,UACA,MACA,QACA,WACA,SACA,OACA,YACA,iBACA,aACA,eACA,aACA,qBACA,WACA,kBACA,WACA,cAAc,aACM;AACpB,KAAI,WAAW,cAAe,QAAO,oBAAA,UAAA,EAAA,UAAG,eAAe,EAAI,CAAA;AAC3D,KAAI,SAAS,YAAa,QAAO,oBAAA,UAAA,EAAA,UAAG,YAAY,MAAM,EAAI,CAAA;AAC1D,KAAI,MAAM,WAAW,KAAK,YAAa,QAAO,oBAAA,UAAA,EAAA,UAAG,aAAa,EAAI,CAAA;CAGlE,MAAM,oBAAoB,YAAY,qBAAqB,UAAU,GAAG,KAAA;CAExE,IAAI;CACJ,IAAI;AACJ,KAAI,MAAM;EACR,MAAM,WAAW,gBAAgB,MAAM,OAAO;AAC9C,kBAAgB,SAAS;AACzB,mBAAiB,SAAS;;CAG5B,IAAI,eAAe;CACnB,IAAI;AACJ,KAAI,WACF,kBAAiB,sBAAsB,YAAY,UAAU,gBAAgB;UACpE,YAAY,CAAC,WACtB,gBAAe,MAAM,MAAM,GAAG,SAAS;CAIzC,MAAM,UAAU,oBAAoB,aAAa,KAAI,SAAQ,MAAM,KAAK,CAAC,GAAG,EAAE;CAC9E,MAAM,cAAc,qBAAqB,QAAQ,SAAS,KAAK,QAAQ,OAAM,MAAK,kBAAmB,SAAS,IAAI,EAAE,CAAC;CACrH,MAAM,eAAe,qBAAqB,QAAQ,MAAK,MAAK,kBAAmB,SAAS,IAAI,EAAE,CAAC;AAE/F,QACE,qBAAC,OAAD;EAAK,kBAAe;EAAwB;YAA5C,CACE,qBAAC,SAAD;GAAO,MAAK;GAAO,cAAY;aAA/B,CACE,oBAAC,SAAD,EAAA,UACE,qBAAC,MAAD,EAAA,UAAA,CACG,qBACC,oBAAC,MAAD;IAAI,eAAY;cACd,oBAAC,SAAD;KACE,MAAK;KACL,SAAS,CAAC,CAAC;KACX,MAAM,OAAO;AACX,UAAI,GAAI,IAAG,gBAAgB,CAAC,CAAC,gBAAgB,CAAC;;KAEhD,gBAAgB,kBAAmB,YAAY,QAAQ;KACvD,cAAW;KACX,CAAA;IACC,CAAA,EAEN,QAAQ,KAAK,QAAQ;IACpB,MAAM,aAAa,IAAI,YAAY;IACnC,MAAM,WAAW,eAAe,MAAK,MAAK,EAAE,QAAQ,IAAI,IAAI;IAC5D,MAAM,WAAW,CAAC,CAAC;IACnB,MAAM,YAAY,gBAAgB,cAAc,WAAU,MAAK,EAAE,QAAQ,IAAI,IAAI,GAAG;AAEpF,WACE,oBAAC,MAAD;KAEE,iBAAe,aAAa,KAAK,KAAA;KACjC,eAAa,WAAW,KAAK,KAAA;KAC7B,cAAY,IAAI;KAChB,OAAO,IAAI,QAAQ,EAAE,OAAO,IAAI,OAAO,GAAG,KAAA;KAC1C,aAAW,aAAa,iBAAiB,IAAI,KAAK,cAAc,GAAG,KAAA;eAElE,aACC,qBAAC,UAAD;MAAQ,MAAK;MAAS,eAAe,eAAgB,IAAI,IAAI;gBAA7D,CACG,IAAI,QACJ,sBAAsB;OACrB,QAAQ;OACR,WAAW,UAAU,aAAa;OAClC,OAAO;OACP,gBAAgB,eAAgB,IAAI,IAAI;OACzC,CAAC,CACK;UAET,IAAI;KAEH,EApBE,IAAI,IAoBN;KAEP,CACC,EAAA,CAAA,EACC,CAAA,EACR,oBAAC,SAAD,EAAA,UACG,aAAa,KAAK,MAAM,UAAU;IACjC,MAAM,MAAM,MAAM,KAAK;IACvB,MAAM,aAAa,mBAAmB,SAAS,IAAI,IAAI;IAEvD,MAAM,QACJ,qBAAA,UAAA,EAAA,UAAA,CACG,qBACC,oBAAC,MAAD;KAAI,eAAY;eACd,oBAAC,SAAD;MACE,MAAK;MACL,SAAS,CAAC,CAAC;MACX,gBAAgB,kBAAmB,SAAS,IAAI;MAChD,cAAY,cAAc;MAC1B,CAAA;KACC,CAAA,EAEN,QAAQ,KAAK,QACZ,oBAAC,MAAD;KAEE,cAAY,IAAI;eAEf,IAAI,OAAO,MAAM,MAAM;KACrB,EAJE,IAAI,IAIN,CACL,CACD,EAAA,CAAA;AAGL,WACE,oBAAC,MAAD;KAAc,iBAAe,aAAa,KAAK,KAAA;eAC5C,YAAY,UAAU,MAAM,OAAO,MAAM,GAAG;KAC1C,EAFI,IAEJ;KAEP,EACI,CAAA,CACF;MACP,kBAAkB,mBAAmB,eAAe,CACjD"}
@@ -1,44 +1,53 @@
1
- "use strict";
2
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const jsxRuntime = require("react/jsx-runtime");
4
- const react = require("react");
5
- function InfiniteScroll({
6
- hasMore,
7
- loading = false,
8
- onLoadMore,
9
- threshold = 0.1,
10
- rootMargin = "0px",
11
- direction = "down",
12
- children,
13
- renderLoading,
14
- renderEnd,
15
- className
16
- }) {
17
- const sentinelRef = react.useRef(null);
18
- const onLoadMoreRef = react.useRef(onLoadMore);
19
- onLoadMoreRef.current = onLoadMore;
20
- react.useEffect(() => {
21
- if (typeof IntersectionObserver === "undefined") return;
22
- const sentinel = sentinelRef.current;
23
- if (!sentinel) return;
24
- const observer = new IntersectionObserver(
25
- (entries) => {
26
- if (entries[0]?.isIntersecting) {
27
- onLoadMoreRef.current();
28
- }
29
- },
30
- { threshold, rootMargin }
31
- );
32
- observer.observe(sentinel);
33
- return () => observer.disconnect();
34
- }, [threshold, rootMargin, hasMore, loading]);
35
- const style = direction === "up" ? { display: "flex", flexDirection: "column-reverse" } : void 0;
36
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-component": "infinite-scroll", className, style, children: [
37
- children,
38
- hasMore && !loading && /* @__PURE__ */ jsxRuntime.jsx("div", { ref: sentinelRef, "aria-hidden": "true", "data-sentinel": true }),
39
- loading && renderLoading?.(),
40
- !hasMore && renderEnd?.()
41
- ] });
1
+ let react = require("react");
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ //#region src/react/components/InfiniteScroll.tsx
4
+ /**
5
+ * Headless infinite scroll wrapper using IntersectionObserver.
6
+ * Renders a sentinel element that triggers `onLoadMore` when visible.
7
+ * Use `direction="up"` for reverse-scroll chat UIs.
8
+ */
9
+ function InfiniteScroll({ hasMore, loading = false, onLoadMore, threshold = .1, rootMargin = "0px", direction = "down", children, renderLoading, renderEnd, className }) {
10
+ const sentinelRef = (0, react.useRef)(null);
11
+ const onLoadMoreRef = (0, react.useRef)(onLoadMore);
12
+ onLoadMoreRef.current = onLoadMore;
13
+ (0, react.useEffect)(() => {
14
+ if (typeof IntersectionObserver === "undefined") return;
15
+ const sentinel = sentinelRef.current;
16
+ if (!sentinel) return;
17
+ const observer = new IntersectionObserver((entries) => {
18
+ if (entries[0]?.isIntersecting) onLoadMoreRef.current();
19
+ }, {
20
+ threshold,
21
+ rootMargin
22
+ });
23
+ observer.observe(sentinel);
24
+ return () => observer.disconnect();
25
+ }, [
26
+ threshold,
27
+ rootMargin,
28
+ hasMore,
29
+ loading
30
+ ]);
31
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
32
+ "data-component": "infinite-scroll",
33
+ className,
34
+ style: direction === "up" ? {
35
+ display: "flex",
36
+ flexDirection: "column-reverse"
37
+ } : void 0,
38
+ children: [
39
+ children,
40
+ hasMore && !loading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
41
+ ref: sentinelRef,
42
+ "aria-hidden": "true",
43
+ "data-sentinel": true
44
+ }),
45
+ loading && renderLoading?.(),
46
+ !hasMore && renderEnd?.()
47
+ ]
48
+ });
42
49
  }
50
+ //#endregion
43
51
  exports.InfiniteScroll = InfiniteScroll;
44
- //# sourceMappingURL=InfiniteScroll.cjs.map
52
+
53
+ //# sourceMappingURL=InfiniteScroll.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"InfiniteScroll.cjs","sources":["../../../src/react/components/InfiniteScroll.tsx"],"sourcesContent":["import { useRef, useEffect, type ReactNode } from 'react';\n\n/** Props for the InfiniteScroll headless component. */\nexport interface InfiniteScrollProps {\n hasMore: boolean;\n loading?: boolean;\n onLoadMore: () => void;\n threshold?: number;\n rootMargin?: string;\n direction?: 'down' | 'up';\n children: ReactNode;\n renderLoading?: () => ReactNode;\n renderEnd?: () => ReactNode;\n className?: string;\n}\n\n/**\n * Headless infinite scroll wrapper using IntersectionObserver.\n * Renders a sentinel element that triggers `onLoadMore` when visible.\n * Use `direction=\"up\"` for reverse-scroll chat UIs.\n */\nexport function InfiniteScroll({\n hasMore,\n loading = false,\n onLoadMore,\n threshold = 0.1,\n rootMargin = '0px',\n direction = 'down',\n children,\n renderLoading,\n renderEnd,\n className,\n}: InfiniteScrollProps) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n\n useEffect(() => {\n if (typeof IntersectionObserver === 'undefined') return;\n const sentinel = sentinelRef.current;\n if (!sentinel) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n onLoadMoreRef.current();\n }\n },\n { threshold, rootMargin },\n );\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [threshold, rootMargin, hasMore, loading]);\n\n const style = direction === 'up'\n ? { display: 'flex', flexDirection: 'column-reverse' as const }\n : undefined;\n\n return (\n <div data-component=\"infinite-scroll\" className={className} style={style}>\n {children}\n {hasMore && !loading && (\n <div ref={sentinelRef} aria-hidden=\"true\" data-sentinel />\n )}\n {loading && renderLoading?.()}\n {!hasMore && renderEnd?.()}\n </div>\n );\n}\n"],"names":["useRef","useEffect","jsxs","jsx"],"mappings":";;;;AAqBO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,cAAcA,MAAAA,OAAuB,IAAI;AAC/C,QAAM,gBAAgBA,MAAAA,OAAO,UAAU;AACvC,gBAAc,UAAU;AAExBC,QAAAA,UAAU,MAAM;AACd,QAAI,OAAO,yBAAyB,YAAa;AACjD,UAAM,WAAW,YAAY;AAC7B,QAAI,CAAC,SAAU;AAEf,UAAM,WAAW,IAAI;AAAA,MACnB,CAAC,YAAY;AACX,YAAI,QAAQ,CAAC,GAAG,gBAAgB;AAC9B,wBAAc,QAAA;AAAA,QAChB;AAAA,MACF;AAAA,MACA,EAAE,WAAW,WAAA;AAAA,IAAW;AAG1B,aAAS,QAAQ,QAAQ;AACzB,WAAO,MAAM,SAAS,WAAA;AAAA,EACxB,GAAG,CAAC,WAAW,YAAY,SAAS,OAAO,CAAC;AAE5C,QAAM,QAAQ,cAAc,OACxB,EAAE,SAAS,QAAQ,eAAe,qBAClC;AAEJ,SACEC,2BAAAA,KAAC,OAAA,EAAI,kBAAe,mBAAkB,WAAsB,OACzD,UAAA;AAAA,IAAA;AAAA,IACA,WAAW,CAAC,WACXC,2BAAAA,IAAC,OAAA,EAAI,KAAK,aAAa,eAAY,QAAO,iBAAa,KAAA,CAAC;AAAA,IAEzD,WAAW,gBAAA;AAAA,IACX,CAAC,WAAW,YAAA;AAAA,EAAY,GAC3B;AAEJ;;"}
1
+ {"version":3,"file":"InfiniteScroll.cjs","names":[],"sources":["../../../src/react/components/InfiniteScroll.tsx"],"sourcesContent":["import { useRef, useEffect, type ReactNode } from 'react';\n\n/** Props for the InfiniteScroll headless component. */\nexport interface InfiniteScrollProps {\n hasMore: boolean;\n loading?: boolean;\n onLoadMore: () => void;\n threshold?: number;\n rootMargin?: string;\n direction?: 'down' | 'up';\n children: ReactNode;\n renderLoading?: () => ReactNode;\n renderEnd?: () => ReactNode;\n className?: string;\n}\n\n/**\n * Headless infinite scroll wrapper using IntersectionObserver.\n * Renders a sentinel element that triggers `onLoadMore` when visible.\n * Use `direction=\"up\"` for reverse-scroll chat UIs.\n */\nexport function InfiniteScroll({\n hasMore,\n loading = false,\n onLoadMore,\n threshold = 0.1,\n rootMargin = '0px',\n direction = 'down',\n children,\n renderLoading,\n renderEnd,\n className,\n}: InfiniteScrollProps) {\n const sentinelRef = useRef<HTMLDivElement>(null);\n const onLoadMoreRef = useRef(onLoadMore);\n onLoadMoreRef.current = onLoadMore;\n\n useEffect(() => {\n if (typeof IntersectionObserver === 'undefined') return;\n const sentinel = sentinelRef.current;\n if (!sentinel) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n onLoadMoreRef.current();\n }\n },\n { threshold, rootMargin },\n );\n\n observer.observe(sentinel);\n return () => observer.disconnect();\n }, [threshold, rootMargin, hasMore, loading]);\n\n const style = direction === 'up'\n ? { display: 'flex', flexDirection: 'column-reverse' as const }\n : undefined;\n\n return (\n <div data-component=\"infinite-scroll\" className={className} style={style}>\n {children}\n {hasMore && !loading && (\n <div ref={sentinelRef} aria-hidden=\"true\" data-sentinel />\n )}\n {loading && renderLoading?.()}\n {!hasMore && renderEnd?.()}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAqBA,SAAgB,eAAe,EAC7B,SACA,UAAU,OACV,YACA,YAAY,IACZ,aAAa,OACb,YAAY,QACZ,UACA,eACA,WACA,aACsB;CACtB,MAAM,eAAA,GAAA,MAAA,QAAqC,KAAK;CAChD,MAAM,iBAAA,GAAA,MAAA,QAAuB,WAAW;AACxC,eAAc,UAAU;AAExB,EAAA,GAAA,MAAA,iBAAgB;AACd,MAAI,OAAO,yBAAyB,YAAa;EACjD,MAAM,WAAW,YAAY;AAC7B,MAAI,CAAC,SAAU;EAEf,MAAM,WAAW,IAAI,sBAClB,YAAY;AACX,OAAI,QAAQ,IAAI,eACd,eAAc,SAAS;KAG3B;GAAE;GAAW;GAAY,CAC1B;AAED,WAAS,QAAQ,SAAS;AAC1B,eAAa,SAAS,YAAY;IACjC;EAAC;EAAW;EAAY;EAAS;EAAQ,CAAC;AAM7C,QACE,iBAAA,GAAA,kBAAA,MAAC,OAAD;EAAK,kBAAe;EAA6B;EAAkB,OALvD,cAAc,OACxB;GAAE,SAAS;GAAQ,eAAe;GAA2B,GAC7D,KAAA;YAGF;GACG;GACA,WAAW,CAAC,WACX,iBAAA,GAAA,kBAAA,KAAC,OAAD;IAAK,KAAK;IAAa,eAAY;IAAO,iBAAA;IAAgB,CAAA;GAE3D,WAAW,iBAAiB;GAC5B,CAAC,WAAW,aAAa;GACtB"}
@@ -1,44 +1,53 @@
1
- import { jsxs, jsx } from "react/jsx-runtime";
2
- import { useRef, useEffect } from "react";
3
- function InfiniteScroll({
4
- hasMore,
5
- loading = false,
6
- onLoadMore,
7
- threshold = 0.1,
8
- rootMargin = "0px",
9
- direction = "down",
10
- children,
11
- renderLoading,
12
- renderEnd,
13
- className
14
- }) {
15
- const sentinelRef = useRef(null);
16
- const onLoadMoreRef = useRef(onLoadMore);
17
- onLoadMoreRef.current = onLoadMore;
18
- useEffect(() => {
19
- if (typeof IntersectionObserver === "undefined") return;
20
- const sentinel = sentinelRef.current;
21
- if (!sentinel) return;
22
- const observer = new IntersectionObserver(
23
- (entries) => {
24
- if (entries[0]?.isIntersecting) {
25
- onLoadMoreRef.current();
26
- }
27
- },
28
- { threshold, rootMargin }
29
- );
30
- observer.observe(sentinel);
31
- return () => observer.disconnect();
32
- }, [threshold, rootMargin, hasMore, loading]);
33
- const style = direction === "up" ? { display: "flex", flexDirection: "column-reverse" } : void 0;
34
- return /* @__PURE__ */ jsxs("div", { "data-component": "infinite-scroll", className, style, children: [
35
- children,
36
- hasMore && !loading && /* @__PURE__ */ jsx("div", { ref: sentinelRef, "aria-hidden": "true", "data-sentinel": true }),
37
- loading && renderLoading?.(),
38
- !hasMore && renderEnd?.()
39
- ] });
1
+ import { useEffect, useRef } from "react";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region src/react/components/InfiniteScroll.tsx
4
+ /**
5
+ * Headless infinite scroll wrapper using IntersectionObserver.
6
+ * Renders a sentinel element that triggers `onLoadMore` when visible.
7
+ * Use `direction="up"` for reverse-scroll chat UIs.
8
+ */
9
+ function InfiniteScroll({ hasMore, loading = false, onLoadMore, threshold = .1, rootMargin = "0px", direction = "down", children, renderLoading, renderEnd, className }) {
10
+ const sentinelRef = useRef(null);
11
+ const onLoadMoreRef = useRef(onLoadMore);
12
+ onLoadMoreRef.current = onLoadMore;
13
+ useEffect(() => {
14
+ if (typeof IntersectionObserver === "undefined") return;
15
+ const sentinel = sentinelRef.current;
16
+ if (!sentinel) return;
17
+ const observer = new IntersectionObserver((entries) => {
18
+ if (entries[0]?.isIntersecting) onLoadMoreRef.current();
19
+ }, {
20
+ threshold,
21
+ rootMargin
22
+ });
23
+ observer.observe(sentinel);
24
+ return () => observer.disconnect();
25
+ }, [
26
+ threshold,
27
+ rootMargin,
28
+ hasMore,
29
+ loading
30
+ ]);
31
+ return /* @__PURE__ */ jsxs("div", {
32
+ "data-component": "infinite-scroll",
33
+ className,
34
+ style: direction === "up" ? {
35
+ display: "flex",
36
+ flexDirection: "column-reverse"
37
+ } : void 0,
38
+ children: [
39
+ children,
40
+ hasMore && !loading && /* @__PURE__ */ jsx("div", {
41
+ ref: sentinelRef,
42
+ "aria-hidden": "true",
43
+ "data-sentinel": true
44
+ }),
45
+ loading && renderLoading?.(),
46
+ !hasMore && renderEnd?.()
47
+ ]
48
+ });
40
49
  }
41
- export {
42
- InfiniteScroll
43
- };
44
- //# sourceMappingURL=InfiniteScroll.js.map
50
+ //#endregion
51
+ export { InfiniteScroll };
52
+
53
+ //# sourceMappingURL=InfiniteScroll.js.map