mvc-kit 2.8.0 → 2.9.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 (100) hide show
  1. package/README.md +29 -0
  2. package/agent-config/claude-code/skills/guide/anti-patterns.md +3 -3
  3. package/agent-config/claude-code/skills/guide/api-reference.md +138 -1
  4. package/agent-config/claude-code/skills/guide/patterns.md +120 -0
  5. package/agent-config/copilot/copilot-instructions.md +52 -0
  6. package/agent-config/cursor/cursorrules +52 -0
  7. package/dist/Collection.cjs +38 -0
  8. package/dist/Collection.cjs.map +1 -1
  9. package/dist/Collection.d.ts.map +1 -1
  10. package/dist/Collection.js +38 -0
  11. package/dist/Collection.js.map +1 -1
  12. package/dist/Feed.cjs +86 -0
  13. package/dist/Feed.cjs.map +1 -0
  14. package/dist/Feed.d.ts +46 -0
  15. package/dist/Feed.d.ts.map +1 -0
  16. package/dist/Feed.js +86 -0
  17. package/dist/Feed.js.map +1 -0
  18. package/dist/Pagination.cjs +84 -0
  19. package/dist/Pagination.cjs.map +1 -0
  20. package/dist/Pagination.d.ts +39 -0
  21. package/dist/Pagination.d.ts.map +1 -0
  22. package/dist/Pagination.js +84 -0
  23. package/dist/Pagination.js.map +1 -0
  24. package/dist/PersistentCollection.cjs +8 -5
  25. package/dist/PersistentCollection.cjs.map +1 -1
  26. package/dist/PersistentCollection.d.ts +6 -1
  27. package/dist/PersistentCollection.d.ts.map +1 -1
  28. package/dist/PersistentCollection.js +8 -5
  29. package/dist/PersistentCollection.js.map +1 -1
  30. package/dist/Resource.cjs +3 -0
  31. package/dist/Resource.cjs.map +1 -1
  32. package/dist/Resource.d.ts +3 -0
  33. package/dist/Resource.d.ts.map +1 -1
  34. package/dist/Resource.js +3 -0
  35. package/dist/Resource.js.map +1 -1
  36. package/dist/Selection.cjs +99 -0
  37. package/dist/Selection.cjs.map +1 -0
  38. package/dist/Selection.d.ts +36 -0
  39. package/dist/Selection.d.ts.map +1 -0
  40. package/dist/Selection.js +99 -0
  41. package/dist/Selection.js.map +1 -0
  42. package/dist/Sorting.cjs +114 -0
  43. package/dist/Sorting.cjs.map +1 -0
  44. package/dist/Sorting.d.ts +43 -0
  45. package/dist/Sorting.d.ts.map +1 -0
  46. package/dist/Sorting.js +114 -0
  47. package/dist/Sorting.js.map +1 -0
  48. package/dist/index.d.ts +6 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/mvc-kit.cjs +8 -0
  51. package/dist/mvc-kit.cjs.map +1 -1
  52. package/dist/mvc-kit.js +8 -0
  53. package/dist/mvc-kit.js.map +1 -1
  54. package/dist/react/components/CardList.cjs +42 -0
  55. package/dist/react/components/CardList.cjs.map +1 -0
  56. package/dist/react/components/CardList.d.ts +22 -0
  57. package/dist/react/components/CardList.d.ts.map +1 -0
  58. package/dist/react/components/CardList.js +42 -0
  59. package/dist/react/components/CardList.js.map +1 -0
  60. package/dist/react/components/DataTable.cjs +179 -0
  61. package/dist/react/components/DataTable.cjs.map +1 -0
  62. package/dist/react/components/DataTable.d.ts +30 -0
  63. package/dist/react/components/DataTable.d.ts.map +1 -0
  64. package/dist/react/components/DataTable.js +179 -0
  65. package/dist/react/components/DataTable.js.map +1 -0
  66. package/dist/react/components/InfiniteScroll.cjs +44 -0
  67. package/dist/react/components/InfiniteScroll.cjs.map +1 -0
  68. package/dist/react/components/InfiniteScroll.d.ts +21 -0
  69. package/dist/react/components/InfiniteScroll.d.ts.map +1 -0
  70. package/dist/react/components/InfiniteScroll.js +44 -0
  71. package/dist/react/components/InfiniteScroll.js.map +1 -0
  72. package/dist/react/components/types.cjs +15 -0
  73. package/dist/react/components/types.cjs.map +1 -0
  74. package/dist/react/components/types.d.ts +71 -0
  75. package/dist/react/components/types.d.ts.map +1 -0
  76. package/dist/react/components/types.js +15 -0
  77. package/dist/react/components/types.js.map +1 -0
  78. package/dist/react/index.d.ts +7 -0
  79. package/dist/react/index.d.ts.map +1 -1
  80. package/dist/react-native/NativeCollection.cjs +3 -0
  81. package/dist/react-native/NativeCollection.cjs.map +1 -1
  82. package/dist/react-native/NativeCollection.d.ts +3 -0
  83. package/dist/react-native/NativeCollection.d.ts.map +1 -1
  84. package/dist/react-native/NativeCollection.js +3 -0
  85. package/dist/react-native/NativeCollection.js.map +1 -1
  86. package/dist/react.cjs +6 -0
  87. package/dist/react.cjs.map +1 -1
  88. package/dist/react.js +6 -0
  89. package/dist/react.js.map +1 -1
  90. package/dist/web/idb.cjs.map +1 -1
  91. package/dist/web/idb.d.ts +18 -0
  92. package/dist/web/idb.d.ts.map +1 -1
  93. package/dist/web/idb.js.map +1 -1
  94. package/dist/wrapAsyncMethods.cjs +21 -41
  95. package/dist/wrapAsyncMethods.cjs.map +1 -1
  96. package/dist/wrapAsyncMethods.d.ts +2 -0
  97. package/dist/wrapAsyncMethods.d.ts.map +1 -1
  98. package/dist/wrapAsyncMethods.js +21 -41
  99. package/dist/wrapAsyncMethods.js.map +1 -1
  100. package/package.json +1 -1
@@ -0,0 +1,179 @@
1
+ import { jsx, Fragment, jsxs } from "react/jsx-runtime";
2
+ import { isSelectionHelper, isSortingHelper, isPaginationHelper } from "./types.js";
3
+ 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
+ };
16
+ }
17
+ function resolveSortProp(sort, onSort) {
18
+ if (isSortingHelper(sort)) {
19
+ return { sorts: sort.sorts, onSort: (key) => sort.toggle(key) };
20
+ }
21
+ return { sorts: sort, onSort };
22
+ }
23
+ 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
+ };
56
+ }
57
+ const defaultKeyOf = (item) => item.id;
58
+ 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";
63
+ }
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
+ ] });
175
+ }
176
+ export {
177
+ DataTable
178
+ };
179
+ //# sourceMappingURL=DataTable.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,44 @@
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
+ ] });
42
+ }
43
+ exports.InfiniteScroll = InfiniteScroll;
44
+ //# sourceMappingURL=InfiniteScroll.cjs.map
@@ -0,0 +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;;"}
@@ -0,0 +1,21 @@
1
+ import { type ReactNode } from 'react';
2
+ /** Props for the InfiniteScroll headless component. */
3
+ export interface InfiniteScrollProps {
4
+ hasMore: boolean;
5
+ loading?: boolean;
6
+ onLoadMore: () => void;
7
+ threshold?: number;
8
+ rootMargin?: string;
9
+ direction?: 'down' | 'up';
10
+ children: ReactNode;
11
+ renderLoading?: () => ReactNode;
12
+ renderEnd?: () => ReactNode;
13
+ className?: string;
14
+ }
15
+ /**
16
+ * Headless infinite scroll wrapper using IntersectionObserver.
17
+ * Renders a sentinel element that triggers `onLoadMore` when visible.
18
+ * Use `direction="up"` for reverse-scroll chat UIs.
19
+ */
20
+ export declare function InfiniteScroll({ hasMore, loading, onLoadMore, threshold, rootMargin, direction, children, renderLoading, renderEnd, className, }: InfiniteScrollProps): import("react/jsx-runtime").JSX.Element;
21
+ //# sourceMappingURL=InfiniteScroll.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfiniteScroll.d.ts","sourceRoot":"","sources":["../../../src/react/components/InfiniteScroll.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1D,uDAAuD;AACvD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,SAAS,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,OAAe,EACf,UAAU,EACV,SAAe,EACf,UAAkB,EAClB,SAAkB,EAClB,QAAQ,EACR,aAAa,EACb,SAAS,EACT,SAAS,GACV,EAAE,mBAAmB,2CAqCrB"}
@@ -0,0 +1,44 @@
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
+ ] });
40
+ }
41
+ export {
42
+ InfiniteScroll
43
+ };
44
+ //# sourceMappingURL=InfiniteScroll.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InfiniteScroll.js","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":[],"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,cAAc,OAAuB,IAAI;AAC/C,QAAM,gBAAgB,OAAO,UAAU;AACvC,gBAAc,UAAU;AAExB,YAAU,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,SACE,qBAAC,OAAA,EAAI,kBAAe,mBAAkB,WAAsB,OACzD,UAAA;AAAA,IAAA;AAAA,IACA,WAAW,CAAC,WACX,oBAAC,OAAA,EAAI,KAAK,aAAa,eAAY,QAAO,iBAAa,KAAA,CAAC;AAAA,IAEzD,WAAW,gBAAA;AAAA,IACX,CAAC,WAAW,YAAA;AAAA,EAAY,GAC3B;AAEJ;"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ function isSelectionHelper(s) {
4
+ return "toggle" in s && !("onToggle" in s);
5
+ }
6
+ function isPaginationHelper(p) {
7
+ return "setPage" in p && !("onPageChange" in p);
8
+ }
9
+ function isSortingHelper(s) {
10
+ return s != null && !Array.isArray(s) && "sorts" in s && "toggle" in s;
11
+ }
12
+ exports.isPaginationHelper = isPaginationHelper;
13
+ exports.isSelectionHelper = isSelectionHelper;
14
+ exports.isSortingHelper = isSortingHelper;
15
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","sources":["../../../src/react/components/types.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { SortDescriptor } from '../../Sorting';\n\n/** Props passed to a custom sort indicator render function. */\nexport interface SortHeaderProps {\n active: boolean;\n direction: 'asc' | 'desc';\n index: number;\n onToggle: () => void;\n}\n\n/** Controlled selection state using callback props. */\nexport interface SelectionState<K = string | number> {\n selected: ReadonlySet<K>;\n onToggle: (key: K) => void;\n onToggleAll: (allKeys: K[]) => void;\n}\n\n/** Structural interface matching Selection<K> — duck-type for direct helper pass-through. */\nexport interface SelectionHelper {\n readonly selected: ReadonlySet<any>;\n toggle(key: any): void;\n toggleAll(allKeys: any[]): void;\n}\n\n/** Controlled pagination state using callback props. */\nexport interface PaginationState {\n page: number;\n total: number;\n onPageChange: (page: number) => void;\n}\n\n/** Structural interface matching Pagination — duck-type for direct helper pass-through. */\nexport interface PaginationHelper {\n readonly page: number;\n readonly pageSize: number;\n setPage(page: number): void;\n}\n\n/** Structural interface matching Sorting<T> — duck-type for direct helper pass-through. */\nexport interface SortingHelper {\n readonly sorts: readonly SortDescriptor[];\n toggle(key: string): void;\n}\n\n/** Computed pagination info passed to renderPagination slots. */\nexport interface PaginationInfo {\n page: number;\n pageCount: number;\n total: number;\n pageSize: number;\n hasPrev: boolean;\n hasNext: boolean;\n goToPage: (p: number) => void;\n goPrev: () => void;\n goNext: () => void;\n}\n\n/** Loading and error state props for async-aware components. */\nexport interface AsyncStateProps {\n loading?: boolean;\n error?: string | null;\n}\n\n/** Column definition for DataTable. */\nexport interface Column<T> {\n key: string;\n header: ReactNode;\n render: (item: T, index: number) => ReactNode;\n sortable?: boolean;\n width?: string;\n align?: 'left' | 'center' | 'right';\n}\n\n// ── Detection functions (duck-type discriminators) ──\n\n/** @internal Detect whether a selection prop is a Selection helper instance. */\nexport function isSelectionHelper(s: SelectionState | SelectionHelper): s is SelectionHelper {\n return 'toggle' in s && !('onToggle' in s);\n}\n\n/** @internal Detect whether a pagination prop is a Pagination helper instance. */\nexport function isPaginationHelper(p: PaginationState | PaginationHelper): p is PaginationHelper {\n return 'setPage' in p && !('onPageChange' in p);\n}\n\n/** @internal Detect whether a sort prop is a Sorting helper instance. */\nexport function isSortingHelper(s: readonly SortDescriptor[] | SortingHelper): s is SortingHelper {\n return s != null && !Array.isArray(s) && 'sorts' in s && 'toggle' in s;\n}\n"],"names":[],"mappings":";;AA6EO,SAAS,kBAAkB,GAA2D;AAC3F,SAAO,YAAY,KAAK,EAAE,cAAc;AAC1C;AAGO,SAAS,mBAAmB,GAA8D;AAC/F,SAAO,aAAa,KAAK,EAAE,kBAAkB;AAC/C;AAGO,SAAS,gBAAgB,GAAkE;AAChG,SAAO,KAAK,QAAQ,CAAC,MAAM,QAAQ,CAAC,KAAK,WAAW,KAAK,YAAY;AACvE;;;;"}
@@ -0,0 +1,71 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SortDescriptor } from '../../Sorting';
3
+ /** Props passed to a custom sort indicator render function. */
4
+ export interface SortHeaderProps {
5
+ active: boolean;
6
+ direction: 'asc' | 'desc';
7
+ index: number;
8
+ onToggle: () => void;
9
+ }
10
+ /** Controlled selection state using callback props. */
11
+ export interface SelectionState<K = string | number> {
12
+ selected: ReadonlySet<K>;
13
+ onToggle: (key: K) => void;
14
+ onToggleAll: (allKeys: K[]) => void;
15
+ }
16
+ /** Structural interface matching Selection<K> — duck-type for direct helper pass-through. */
17
+ export interface SelectionHelper {
18
+ readonly selected: ReadonlySet<any>;
19
+ toggle(key: any): void;
20
+ toggleAll(allKeys: any[]): void;
21
+ }
22
+ /** Controlled pagination state using callback props. */
23
+ export interface PaginationState {
24
+ page: number;
25
+ total: number;
26
+ onPageChange: (page: number) => void;
27
+ }
28
+ /** Structural interface matching Pagination — duck-type for direct helper pass-through. */
29
+ export interface PaginationHelper {
30
+ readonly page: number;
31
+ readonly pageSize: number;
32
+ setPage(page: number): void;
33
+ }
34
+ /** Structural interface matching Sorting<T> — duck-type for direct helper pass-through. */
35
+ export interface SortingHelper {
36
+ readonly sorts: readonly SortDescriptor[];
37
+ toggle(key: string): void;
38
+ }
39
+ /** Computed pagination info passed to renderPagination slots. */
40
+ export interface PaginationInfo {
41
+ page: number;
42
+ pageCount: number;
43
+ total: number;
44
+ pageSize: number;
45
+ hasPrev: boolean;
46
+ hasNext: boolean;
47
+ goToPage: (p: number) => void;
48
+ goPrev: () => void;
49
+ goNext: () => void;
50
+ }
51
+ /** Loading and error state props for async-aware components. */
52
+ export interface AsyncStateProps {
53
+ loading?: boolean;
54
+ error?: string | null;
55
+ }
56
+ /** Column definition for DataTable. */
57
+ export interface Column<T> {
58
+ key: string;
59
+ header: ReactNode;
60
+ render: (item: T, index: number) => ReactNode;
61
+ sortable?: boolean;
62
+ width?: string;
63
+ align?: 'left' | 'center' | 'right';
64
+ }
65
+ /** @internal Detect whether a selection prop is a Selection helper instance. */
66
+ export declare function isSelectionHelper(s: SelectionState | SelectionHelper): s is SelectionHelper;
67
+ /** @internal Detect whether a pagination prop is a Pagination helper instance. */
68
+ export declare function isPaginationHelper(p: PaginationState | PaginationHelper): p is PaginationHelper;
69
+ /** @internal Detect whether a sort prop is a Sorting helper instance. */
70
+ export declare function isSortingHelper(s: readonly SortDescriptor[] | SortingHelper): s is SortingHelper;
71
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/react/components/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ function isSelectionHelper(s) {
2
+ return "toggle" in s && !("onToggle" in s);
3
+ }
4
+ function isPaginationHelper(p) {
5
+ return "setPage" in p && !("onPageChange" in p);
6
+ }
7
+ function isSortingHelper(s) {
8
+ return s != null && !Array.isArray(s) && "sorts" in s && "toggle" in s;
9
+ }
10
+ export {
11
+ isPaginationHelper,
12
+ isSelectionHelper,
13
+ isSortingHelper
14
+ };
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sources":["../../../src/react/components/types.ts"],"sourcesContent":["import type { ReactNode } from 'react';\nimport type { SortDescriptor } from '../../Sorting';\n\n/** Props passed to a custom sort indicator render function. */\nexport interface SortHeaderProps {\n active: boolean;\n direction: 'asc' | 'desc';\n index: number;\n onToggle: () => void;\n}\n\n/** Controlled selection state using callback props. */\nexport interface SelectionState<K = string | number> {\n selected: ReadonlySet<K>;\n onToggle: (key: K) => void;\n onToggleAll: (allKeys: K[]) => void;\n}\n\n/** Structural interface matching Selection<K> — duck-type for direct helper pass-through. */\nexport interface SelectionHelper {\n readonly selected: ReadonlySet<any>;\n toggle(key: any): void;\n toggleAll(allKeys: any[]): void;\n}\n\n/** Controlled pagination state using callback props. */\nexport interface PaginationState {\n page: number;\n total: number;\n onPageChange: (page: number) => void;\n}\n\n/** Structural interface matching Pagination — duck-type for direct helper pass-through. */\nexport interface PaginationHelper {\n readonly page: number;\n readonly pageSize: number;\n setPage(page: number): void;\n}\n\n/** Structural interface matching Sorting<T> — duck-type for direct helper pass-through. */\nexport interface SortingHelper {\n readonly sorts: readonly SortDescriptor[];\n toggle(key: string): void;\n}\n\n/** Computed pagination info passed to renderPagination slots. */\nexport interface PaginationInfo {\n page: number;\n pageCount: number;\n total: number;\n pageSize: number;\n hasPrev: boolean;\n hasNext: boolean;\n goToPage: (p: number) => void;\n goPrev: () => void;\n goNext: () => void;\n}\n\n/** Loading and error state props for async-aware components. */\nexport interface AsyncStateProps {\n loading?: boolean;\n error?: string | null;\n}\n\n/** Column definition for DataTable. */\nexport interface Column<T> {\n key: string;\n header: ReactNode;\n render: (item: T, index: number) => ReactNode;\n sortable?: boolean;\n width?: string;\n align?: 'left' | 'center' | 'right';\n}\n\n// ── Detection functions (duck-type discriminators) ──\n\n/** @internal Detect whether a selection prop is a Selection helper instance. */\nexport function isSelectionHelper(s: SelectionState | SelectionHelper): s is SelectionHelper {\n return 'toggle' in s && !('onToggle' in s);\n}\n\n/** @internal Detect whether a pagination prop is a Pagination helper instance. */\nexport function isPaginationHelper(p: PaginationState | PaginationHelper): p is PaginationHelper {\n return 'setPage' in p && !('onPageChange' in p);\n}\n\n/** @internal Detect whether a sort prop is a Sorting helper instance. */\nexport function isSortingHelper(s: readonly SortDescriptor[] | SortingHelper): s is SortingHelper {\n return s != null && !Array.isArray(s) && 'sorts' in s && 'toggle' in s;\n}\n"],"names":[],"mappings":"AA6EO,SAAS,kBAAkB,GAA2D;AAC3F,SAAO,YAAY,KAAK,EAAE,cAAc;AAC1C;AAGO,SAAS,mBAAmB,GAA8D;AAC/F,SAAO,aAAa,KAAK,EAAE,kBAAkB;AAC/C;AAGO,SAAS,gBAAgB,GAAkE;AAChG,SAAO,KAAK,QAAQ,CAAC,MAAM,QAAQ,CAAC,KAAK,WAAW,KAAK,YAAY;AACvE;"}
@@ -8,4 +8,11 @@ export { useEvent, useEmit } from './use-event-bus';
8
8
  export { useTeardown } from './use-teardown';
9
9
  export { Provider, useResolve } from './provider';
10
10
  export type { ProviderProps } from './provider';
11
+ export { DataTable } from './components/DataTable';
12
+ export type { DataTableProps } from './components/DataTable';
13
+ export { CardList } from './components/CardList';
14
+ export type { CardListProps } from './components/CardList';
15
+ export { InfiniteScroll } from './components/InfiniteScroll';
16
+ export type { InfiniteScrollProps } from './components/InfiniteScroll';
17
+ export type { Column, SortHeaderProps, SelectionState, SelectionHelper, PaginationState, PaginationHelper, PaginationInfo, SortingHelper, AsyncStateProps, } from './components/types';
11
18
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGjF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAGjF,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG/C,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC9D,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG5D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhD,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,YAAY,EACV,MAAM,EACN,eAAe,EACf,cAAc,EACd,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,eAAe,GAChB,MAAM,oBAAoB,CAAC"}
@@ -16,6 +16,7 @@ class NativeCollection extends PersistentCollection.PersistentCollection {
16
16
  _adapter = null;
17
17
  }
18
18
  // ── Per-class override points ──
19
+ /** Read a value from the storage adapter. Override for custom storage backends. @protected */
19
20
  getItem(key) {
20
21
  if (_adapter) return _adapter.getItem(key);
21
22
  if (__DEV__) {
@@ -25,6 +26,7 @@ class NativeCollection extends PersistentCollection.PersistentCollection {
25
26
  }
26
27
  throw new Error("[mvc-kit] No storage adapter configured.");
27
28
  }
29
+ /** Write a value to the storage adapter. Override for custom storage backends. @protected */
28
30
  setItem(key, value) {
29
31
  if (_adapter) return _adapter.setItem(key, value);
30
32
  if (__DEV__) {
@@ -34,6 +36,7 @@ class NativeCollection extends PersistentCollection.PersistentCollection {
34
36
  }
35
37
  throw new Error("[mvc-kit] No storage adapter configured.");
36
38
  }
39
+ /** Remove a value from the storage adapter. Override for custom storage backends. @protected */
37
40
  removeItem(key) {
38
41
  if (_adapter) return _adapter.removeItem(key);
39
42
  if (__DEV__) {
@@ -1 +1 @@
1
- {"version":3,"file":"NativeCollection.cjs","sources":["../../src/react-native/NativeCollection.ts"],"sourcesContent":["import { PersistentCollection } from '../PersistentCollection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\ninterface StorageAdapter {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nlet _adapter: StorageAdapter | null = null;\n\n/**\n * PersistentCollection for React Native, backed by any async key-value store.\n * Uses blob strategy (full state as a single JSON string under `storageKey`).\n *\n * **Requires manual `hydrate()` call** (async storage).\n *\n * ## Setup (once at app startup)\n *\n * ```ts\n * import { NativeCollection } from 'mvc-kit/react-native';\n * import AsyncStorage from '@react-native-async-storage/async-storage';\n *\n * NativeCollection.configure({\n * getItem: (key) => AsyncStorage.getItem(key),\n * setItem: (key, value) => AsyncStorage.setItem(key, value),\n * removeItem: (key) => AsyncStorage.removeItem(key),\n * });\n * ```\n *\n * ## Usage\n *\n * ```ts\n * class TodosCollection extends NativeCollection<Todo> {\n * protected readonly storageKey = 'todos';\n * }\n * ```\n *\n * ## Per-class override (edge cases)\n *\n * ```ts\n * class SecureCollection extends NativeCollection<Secret> {\n * protected readonly storageKey = 'secrets';\n * protected async getItem(key: string) { return SecureStore.getItem(key); }\n * protected async setItem(key: string, value: string) { await SecureStore.setItem(key, value); }\n * protected async removeItem(key: string) { await SecureStore.removeItem(key); }\n * }\n * ```\n */\nexport abstract class NativeCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /**\n * Configure the default storage adapter for all NativeCollection subclasses.\n * Call once at app startup. Per-class method overrides take priority.\n */\n static configure(adapter: StorageAdapter): void {\n _adapter = adapter;\n }\n\n /** Reset the configured adapter (for testing). */\n static resetAdapter(): void {\n _adapter = null;\n }\n\n // ── Per-class override points ──\n\n protected getItem(key: string): Promise<string | null> {\n if (_adapter) return _adapter.getItem(key);\n if (__DEV__) {\n throw new Error(\n `[mvc-kit] No storage adapter configured for \"${this.constructor.name}\". ` +\n `Call NativeCollection.configure() at app startup, or override getItem/setItem/removeItem.`,\n );\n }\n throw new Error('[mvc-kit] No storage adapter configured.');\n }\n\n protected setItem(key: string, value: string): Promise<void> {\n if (_adapter) return _adapter.setItem(key, value);\n if (__DEV__) {\n throw new Error(\n `[mvc-kit] No storage adapter configured for \"${this.constructor.name}\". ` +\n `Call NativeCollection.configure() at app startup, or override getItem/setItem/removeItem.`,\n );\n }\n throw new Error('[mvc-kit] No storage adapter configured.');\n }\n\n protected removeItem(key: string): Promise<void> {\n if (_adapter) return _adapter.removeItem(key);\n if (__DEV__) {\n throw new Error(\n `[mvc-kit] No storage adapter configured for \"${this.constructor.name}\". ` +\n `Call NativeCollection.configure() at app startup, or override getItem/setItem/removeItem.`,\n );\n }\n throw new Error('[mvc-kit] No storage adapter configured.');\n }\n\n // ── Persist interface (blob strategy) ──\n\n protected async persistGetAll(): Promise<T[]> {\n const raw = await this.getItem(this.storageKey);\n if (!raw) return [];\n try {\n return this.deserialize(raw);\n } catch {\n if (__DEV__) {\n console.warn(\n `[mvc-kit] Corrupted data in storage key \"${this.storageKey}\". Ignoring stored data.`,\n );\n }\n return [];\n }\n }\n\n protected async persistGet(id: T['id']): Promise<T | null> {\n const all = await this.persistGetAll();\n return all.find((i) => i.id === id) ?? null;\n }\n\n protected async persistSet(_items: T[]): Promise<void> {\n await this.setItem(this.storageKey, this.serialize([...this.items]));\n }\n\n protected async persistRemove(_ids: T['id'][]): Promise<void> {\n await this.setItem(this.storageKey, this.serialize([...this.items]));\n }\n\n protected async persistClear(): Promise<void> {\n await this.removeItem(this.storageKey);\n }\n}\n"],"names":["PersistentCollection"],"mappings":";;;AAEA,MAAM,UAAU,OAAO,oBAAoB,eAAe;AAQ1D,IAAI,WAAkC;AAwC/B,MAAe,yBAEZA,qBAAAA,qBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhC,OAAO,UAAU,SAA+B;AAC9C,eAAW;AAAA,EACb;AAAA;AAAA,EAGA,OAAO,eAAqB;AAC1B,eAAW;AAAA,EACb;AAAA;AAAA,EAIU,QAAQ,KAAqC;AACrD,QAAI,SAAU,QAAO,SAAS,QAAQ,GAAG;AACzC,QAAI,SAAS;AACX,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGzE;AACA,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA,EAEU,QAAQ,KAAa,OAA8B;AAC3D,QAAI,SAAU,QAAO,SAAS,QAAQ,KAAK,KAAK;AAChD,QAAI,SAAS;AACX,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGzE;AACA,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA,EAEU,WAAW,KAA4B;AAC/C,QAAI,SAAU,QAAO,SAAS,WAAW,GAAG;AAC5C,QAAI,SAAS;AACX,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGzE;AACA,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA;AAAA,EAIA,MAAgB,gBAA8B;AAC5C,UAAM,MAAM,MAAM,KAAK,QAAQ,KAAK,UAAU;AAC9C,QAAI,CAAC,IAAK,QAAO,CAAA;AACjB,QAAI;AACF,aAAO,KAAK,YAAY,GAAG;AAAA,IAC7B,QAAQ;AACN,UAAI,SAAS;AACX,gBAAQ;AAAA,UACN,4CAA4C,KAAK,UAAU;AAAA,QAAA;AAAA,MAE/D;AACA,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAgB,WAAW,IAAgC;AACzD,UAAM,MAAM,MAAM,KAAK,cAAA;AACvB,WAAO,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AAAA,EACzC;AAAA,EAEA,MAAgB,WAAW,QAA4B;AACrD,UAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,MAAgB,cAAc,MAAgC;AAC5D,UAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,MAAgB,eAA8B;AAC5C,UAAM,KAAK,WAAW,KAAK,UAAU;AAAA,EACvC;AACF;;"}
1
+ {"version":3,"file":"NativeCollection.cjs","sources":["../../src/react-native/NativeCollection.ts"],"sourcesContent":["import { PersistentCollection } from '../PersistentCollection';\n\nconst __DEV__ = typeof __MVC_KIT_DEV__ !== 'undefined' && __MVC_KIT_DEV__;\n\ninterface StorageAdapter {\n getItem(key: string): Promise<string | null>;\n setItem(key: string, value: string): Promise<void>;\n removeItem(key: string): Promise<void>;\n}\n\nlet _adapter: StorageAdapter | null = null;\n\n/**\n * PersistentCollection for React Native, backed by any async key-value store.\n * Uses blob strategy (full state as a single JSON string under `storageKey`).\n *\n * **Requires manual `hydrate()` call** (async storage).\n *\n * ## Setup (once at app startup)\n *\n * ```ts\n * import { NativeCollection } from 'mvc-kit/react-native';\n * import AsyncStorage from '@react-native-async-storage/async-storage';\n *\n * NativeCollection.configure({\n * getItem: (key) => AsyncStorage.getItem(key),\n * setItem: (key, value) => AsyncStorage.setItem(key, value),\n * removeItem: (key) => AsyncStorage.removeItem(key),\n * });\n * ```\n *\n * ## Usage\n *\n * ```ts\n * class TodosCollection extends NativeCollection<Todo> {\n * protected readonly storageKey = 'todos';\n * }\n * ```\n *\n * ## Per-class override (edge cases)\n *\n * ```ts\n * class SecureCollection extends NativeCollection<Secret> {\n * protected readonly storageKey = 'secrets';\n * protected async getItem(key: string) { return SecureStore.getItem(key); }\n * protected async setItem(key: string, value: string) { await SecureStore.setItem(key, value); }\n * protected async removeItem(key: string) { await SecureStore.removeItem(key); }\n * }\n * ```\n */\nexport abstract class NativeCollection<\n T extends { id: string | number },\n> extends PersistentCollection<T> {\n /**\n * Configure the default storage adapter for all NativeCollection subclasses.\n * Call once at app startup. Per-class method overrides take priority.\n */\n static configure(adapter: StorageAdapter): void {\n _adapter = adapter;\n }\n\n /** Reset the configured adapter (for testing). */\n static resetAdapter(): void {\n _adapter = null;\n }\n\n // ── Per-class override points ──\n\n /** Read a value from the storage adapter. Override for custom storage backends. @protected */\n protected getItem(key: string): Promise<string | null> {\n if (_adapter) return _adapter.getItem(key);\n if (__DEV__) {\n throw new Error(\n `[mvc-kit] No storage adapter configured for \"${this.constructor.name}\". ` +\n `Call NativeCollection.configure() at app startup, or override getItem/setItem/removeItem.`,\n );\n }\n throw new Error('[mvc-kit] No storage adapter configured.');\n }\n\n /** Write a value to the storage adapter. Override for custom storage backends. @protected */\n protected setItem(key: string, value: string): Promise<void> {\n if (_adapter) return _adapter.setItem(key, value);\n if (__DEV__) {\n throw new Error(\n `[mvc-kit] No storage adapter configured for \"${this.constructor.name}\". ` +\n `Call NativeCollection.configure() at app startup, or override getItem/setItem/removeItem.`,\n );\n }\n throw new Error('[mvc-kit] No storage adapter configured.');\n }\n\n /** Remove a value from the storage adapter. Override for custom storage backends. @protected */\n protected removeItem(key: string): Promise<void> {\n if (_adapter) return _adapter.removeItem(key);\n if (__DEV__) {\n throw new Error(\n `[mvc-kit] No storage adapter configured for \"${this.constructor.name}\". ` +\n `Call NativeCollection.configure() at app startup, or override getItem/setItem/removeItem.`,\n );\n }\n throw new Error('[mvc-kit] No storage adapter configured.');\n }\n\n // ── Persist interface (blob strategy) ──\n\n protected async persistGetAll(): Promise<T[]> {\n const raw = await this.getItem(this.storageKey);\n if (!raw) return [];\n try {\n return this.deserialize(raw);\n } catch {\n if (__DEV__) {\n console.warn(\n `[mvc-kit] Corrupted data in storage key \"${this.storageKey}\". Ignoring stored data.`,\n );\n }\n return [];\n }\n }\n\n protected async persistGet(id: T['id']): Promise<T | null> {\n const all = await this.persistGetAll();\n return all.find((i) => i.id === id) ?? null;\n }\n\n protected async persistSet(_items: T[]): Promise<void> {\n await this.setItem(this.storageKey, this.serialize([...this.items]));\n }\n\n protected async persistRemove(_ids: T['id'][]): Promise<void> {\n await this.setItem(this.storageKey, this.serialize([...this.items]));\n }\n\n protected async persistClear(): Promise<void> {\n await this.removeItem(this.storageKey);\n }\n}\n"],"names":["PersistentCollection"],"mappings":";;;AAEA,MAAM,UAAU,OAAO,oBAAoB,eAAe;AAQ1D,IAAI,WAAkC;AAwC/B,MAAe,yBAEZA,qBAAAA,qBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA,EAKhC,OAAO,UAAU,SAA+B;AAC9C,eAAW;AAAA,EACb;AAAA;AAAA,EAGA,OAAO,eAAqB;AAC1B,eAAW;AAAA,EACb;AAAA;AAAA;AAAA,EAKU,QAAQ,KAAqC;AACrD,QAAI,SAAU,QAAO,SAAS,QAAQ,GAAG;AACzC,QAAI,SAAS;AACX,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGzE;AACA,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA;AAAA,EAGU,QAAQ,KAAa,OAA8B;AAC3D,QAAI,SAAU,QAAO,SAAS,QAAQ,KAAK,KAAK;AAChD,QAAI,SAAS;AACX,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGzE;AACA,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA;AAAA,EAGU,WAAW,KAA4B;AAC/C,QAAI,SAAU,QAAO,SAAS,WAAW,GAAG;AAC5C,QAAI,SAAS;AACX,YAAM,IAAI;AAAA,QACR,gDAAgD,KAAK,YAAY,IAAI;AAAA,MAAA;AAAA,IAGzE;AACA,UAAM,IAAI,MAAM,0CAA0C;AAAA,EAC5D;AAAA;AAAA,EAIA,MAAgB,gBAA8B;AAC5C,UAAM,MAAM,MAAM,KAAK,QAAQ,KAAK,UAAU;AAC9C,QAAI,CAAC,IAAK,QAAO,CAAA;AACjB,QAAI;AACF,aAAO,KAAK,YAAY,GAAG;AAAA,IAC7B,QAAQ;AACN,UAAI,SAAS;AACX,gBAAQ;AAAA,UACN,4CAA4C,KAAK,UAAU;AAAA,QAAA;AAAA,MAE/D;AACA,aAAO,CAAA;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAgB,WAAW,IAAgC;AACzD,UAAM,MAAM,MAAM,KAAK,cAAA;AACvB,WAAO,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK;AAAA,EACzC;AAAA,EAEA,MAAgB,WAAW,QAA4B;AACrD,UAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,MAAgB,cAAc,MAAgC;AAC5D,UAAM,KAAK,QAAQ,KAAK,YAAY,KAAK,UAAU,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;AAAA,EACrE;AAAA,EAEA,MAAgB,eAA8B;AAC5C,UAAM,KAAK,WAAW,KAAK,UAAU;AAAA,EACvC;AACF;;"}
@@ -52,8 +52,11 @@ export declare abstract class NativeCollection<T extends {
52
52
  static configure(adapter: StorageAdapter): void;
53
53
  /** Reset the configured adapter (for testing). */
54
54
  static resetAdapter(): void;
55
+ /** Read a value from the storage adapter. Override for custom storage backends. @protected */
55
56
  protected getItem(key: string): Promise<string | null>;
57
+ /** Write a value to the storage adapter. Override for custom storage backends. @protected */
56
58
  protected setItem(key: string, value: string): Promise<void>;
59
+ /** Remove a value from the storage adapter. Override for custom storage backends. @protected */
57
60
  protected removeItem(key: string): Promise<void>;
58
61
  protected persistGetAll(): Promise<T[]>;
59
62
  protected persistGet(id: T['id']): Promise<T | null>;
@@ -1 +1 @@
1
- {"version":3,"file":"NativeCollection.d.ts","sourceRoot":"","sources":["../../src/react-native/NativeCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI/D,UAAU,cAAc;IACtB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,8BAAsB,gBAAgB,CACpC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CACjC,SAAQ,oBAAoB,CAAC,CAAC,CAAC;IAC/B;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAI/C,kDAAkD;IAClD,MAAM,CAAC,YAAY,IAAI,IAAI;IAM3B,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAWtD,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAahC,aAAa,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;cAe7B,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;cAK1C,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;cAItC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;cAI7C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9C"}
1
+ {"version":3,"file":"NativeCollection.d.ts","sourceRoot":"","sources":["../../src/react-native/NativeCollection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAI/D,UAAU,cAAc;IACtB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,8BAAsB,gBAAgB,CACpC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CACjC,SAAQ,oBAAoB,CAAC,CAAC,CAAC;IAC/B;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI;IAI/C,kDAAkD;IAClD,MAAM,CAAC,YAAY,IAAI,IAAI;IAM3B,8FAA8F;IAC9F,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAWtD,6FAA6F;IAC7F,SAAS,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW5D,gGAAgG;IAChG,SAAS,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;cAahC,aAAa,IAAI,OAAO,CAAC,CAAC,EAAE,CAAC;cAe7B,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;cAK1C,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;cAItC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;cAI7C,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;CAG9C"}
@@ -14,6 +14,7 @@ class NativeCollection extends PersistentCollection {
14
14
  _adapter = null;
15
15
  }
16
16
  // ── Per-class override points ──
17
+ /** Read a value from the storage adapter. Override for custom storage backends. @protected */
17
18
  getItem(key) {
18
19
  if (_adapter) return _adapter.getItem(key);
19
20
  if (__DEV__) {
@@ -23,6 +24,7 @@ class NativeCollection extends PersistentCollection {
23
24
  }
24
25
  throw new Error("[mvc-kit] No storage adapter configured.");
25
26
  }
27
+ /** Write a value to the storage adapter. Override for custom storage backends. @protected */
26
28
  setItem(key, value) {
27
29
  if (_adapter) return _adapter.setItem(key, value);
28
30
  if (__DEV__) {
@@ -32,6 +34,7 @@ class NativeCollection extends PersistentCollection {
32
34
  }
33
35
  throw new Error("[mvc-kit] No storage adapter configured.");
34
36
  }
37
+ /** Remove a value from the storage adapter. Override for custom storage backends. @protected */
35
38
  removeItem(key) {
36
39
  if (_adapter) return _adapter.removeItem(key);
37
40
  if (__DEV__) {