twintrinsic 0.0.1

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 (212) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +150 -0
  3. package/dist/App/App.svelte +54 -0
  4. package/dist/App/App.svelte.d.ts +65 -0
  5. package/dist/Section.svelte +25 -0
  6. package/dist/Section.svelte.d.ts +34 -0
  7. package/dist/actions/clickOutside.d.ts +9 -0
  8. package/dist/actions/clickOutside.js +19 -0
  9. package/dist/actions/index.d.ts +1 -0
  10. package/dist/actions/index.js +1 -0
  11. package/dist/components/Accordion/Accordion.svelte +75 -0
  12. package/dist/components/Accordion/Accordion.svelte.d.ts +39 -0
  13. package/dist/components/Accordion/AccordionItem.svelte +150 -0
  14. package/dist/components/Accordion/AccordionItem.svelte.d.ts +30 -0
  15. package/dist/components/App/App.story.md +8 -0
  16. package/dist/components/App/App.story.svelte +170 -0
  17. package/dist/components/App/App.story.svelte.d.ts +22 -0
  18. package/dist/components/App/App.svelte +77 -0
  19. package/dist/components/App/App.svelte.d.ts +66 -0
  20. package/dist/components/App/Split.svelte +346 -0
  21. package/dist/components/App/Split.svelte.d.ts +54 -0
  22. package/dist/components/App/index.d.ts +2 -0
  23. package/dist/components/App/index.js +3 -0
  24. package/dist/components/AppHeader/AppHeader.svelte +439 -0
  25. package/dist/components/AppHeader/AppHeader.svelte.d.ts +24 -0
  26. package/dist/components/Avatar/Avatar.svelte +300 -0
  27. package/dist/components/Avatar/Avatar.svelte.d.ts +48 -0
  28. package/dist/components/Avatar/AvatarGroup.svelte +185 -0
  29. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +46 -0
  30. package/dist/components/Badge/Badge.svelte +186 -0
  31. package/dist/components/Badge/Badge.svelte.d.ts +51 -0
  32. package/dist/components/BottomBar/BottomBar.svelte +146 -0
  33. package/dist/components/BottomBar/BottomBar.svelte.d.ts +38 -0
  34. package/dist/components/Breadcrumb/Breadcrumb.svelte +77 -0
  35. package/dist/components/Breadcrumb/Breadcrumb.svelte.d.ts +42 -0
  36. package/dist/components/Breadcrumb/BreadcrumbItem.svelte +171 -0
  37. package/dist/components/Breadcrumb/BreadcrumbItem.svelte.d.ts +38 -0
  38. package/dist/components/Button/Button.svelte +252 -0
  39. package/dist/components/Button/Button.svelte.d.ts +80 -0
  40. package/dist/components/Button/ButtonGroup.svelte +127 -0
  41. package/dist/components/Button/ButtonGroup.svelte.d.ts +44 -0
  42. package/dist/components/Card/Card.svelte +152 -0
  43. package/dist/components/Card/Card.svelte.d.ts +55 -0
  44. package/dist/components/Carousel/Carousel.svelte +461 -0
  45. package/dist/components/Carousel/Carousel.svelte.d.ts +79 -0
  46. package/dist/components/Carousel/CarouselItem.svelte +149 -0
  47. package/dist/components/Carousel/CarouselItem.svelte.d.ts +35 -0
  48. package/dist/components/Chip/Chip.svelte +288 -0
  49. package/dist/components/Chip/Chip.svelte.d.ts +71 -0
  50. package/dist/components/Chip/ChipGroup.svelte +190 -0
  51. package/dist/components/Chip/ChipGroup.svelte.d.ts +71 -0
  52. package/dist/components/CodeBlock/CodeBlock.svelte +356 -0
  53. package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +44 -0
  54. package/dist/components/CodeBlock/index.d.ts +1 -0
  55. package/dist/components/CodeBlock/index.js +1 -0
  56. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte +145 -0
  57. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte.d.ts +44 -0
  58. package/dist/components/CodeEditor/CodeEditor.svelte +229 -0
  59. package/dist/components/CodeEditor/CodeEditor.svelte.d.ts +23 -0
  60. package/dist/components/Combobox/Combobox.svelte +279 -0
  61. package/dist/components/Combobox/Combobox.svelte.d.ts +34 -0
  62. package/dist/components/Container/Container.svelte +45 -0
  63. package/dist/components/Container/Container.svelte.d.ts +36 -0
  64. package/dist/components/DataTable/DataTable.svelte +879 -0
  65. package/dist/components/DataTable/DataTable.svelte.d.ts +102 -0
  66. package/dist/components/Form/AutoComplete.svelte +357 -0
  67. package/dist/components/Form/AutoComplete.svelte.d.ts +73 -0
  68. package/dist/components/Form/Calendar.svelte +429 -0
  69. package/dist/components/Form/Calendar.svelte.d.ts +53 -0
  70. package/dist/components/Form/Checkbox.svelte +196 -0
  71. package/dist/components/Form/Checkbox.svelte.d.ts +50 -0
  72. package/dist/components/Form/ColorPicker.svelte +396 -0
  73. package/dist/components/Form/ColorPicker.svelte.d.ts +43 -0
  74. package/dist/components/Form/Combobox.svelte +645 -0
  75. package/dist/components/Form/Combobox.svelte.d.ts +93 -0
  76. package/dist/components/Form/Dropdown.svelte +773 -0
  77. package/dist/components/Form/Dropdown.svelte.d.ts +81 -0
  78. package/dist/components/Form/FileUpload.svelte +796 -0
  79. package/dist/components/Form/FileUpload.svelte.d.ts +78 -0
  80. package/dist/components/Form/FloatLabel.svelte +245 -0
  81. package/dist/components/Form/FloatLabel.svelte.d.ts +44 -0
  82. package/dist/components/Form/Form.svelte +281 -0
  83. package/dist/components/Form/Form.svelte.d.ts +54 -0
  84. package/dist/components/Form/FormField.svelte +218 -0
  85. package/dist/components/Form/FormField.svelte.d.ts +47 -0
  86. package/dist/components/Form/Input.svelte +340 -0
  87. package/dist/components/Form/Input.svelte.d.ts +79 -0
  88. package/dist/components/Form/InputSwitch.svelte +189 -0
  89. package/dist/components/Form/InputSwitch.svelte.d.ts +46 -0
  90. package/dist/components/Form/InvalidState.svelte +97 -0
  91. package/dist/components/Form/InvalidState.svelte.d.ts +37 -0
  92. package/dist/components/Form/Knob.svelte +537 -0
  93. package/dist/components/Form/Knob.svelte.d.ts +78 -0
  94. package/dist/components/Form/ListInput.svelte +469 -0
  95. package/dist/components/Form/ListInput.svelte.d.ts +70 -0
  96. package/dist/components/Form/Listbox.svelte +513 -0
  97. package/dist/components/Form/Listbox.svelte.d.ts +74 -0
  98. package/dist/components/Form/NumberInput.svelte +452 -0
  99. package/dist/components/Form/NumberInput.svelte.d.ts +82 -0
  100. package/dist/components/Form/Radio.svelte +192 -0
  101. package/dist/components/Form/Radio.svelte.d.ts +53 -0
  102. package/dist/components/Form/RadioGroup.svelte +155 -0
  103. package/dist/components/Form/RadioGroup.svelte.d.ts +48 -0
  104. package/dist/components/Form/Rating.svelte +380 -0
  105. package/dist/components/Form/Rating.svelte.d.ts +64 -0
  106. package/dist/components/Form/Select.svelte +436 -0
  107. package/dist/components/Form/Select.svelte.d.ts +49 -0
  108. package/dist/components/Form/SelectGroup.svelte +34 -0
  109. package/dist/components/Form/SelectGroup.svelte.d.ts +33 -0
  110. package/dist/components/Form/Slider.svelte +622 -0
  111. package/dist/components/Form/Slider.svelte.d.ts +73 -0
  112. package/dist/components/Form/Switch.svelte +192 -0
  113. package/dist/components/Form/Switch.svelte.d.ts +46 -0
  114. package/dist/components/Form/TextInput.svelte +274 -0
  115. package/dist/components/Form/TextInput.svelte.d.ts +74 -0
  116. package/dist/components/Form/Textarea.svelte +207 -0
  117. package/dist/components/Form/Textarea.svelte.d.ts +62 -0
  118. package/dist/components/Icon/Icon.svelte +140 -0
  119. package/dist/components/Icon/Icon.svelte.d.ts +25 -0
  120. package/dist/components/Icon/index.d.ts +1 -0
  121. package/dist/components/Icon/index.js +1 -0
  122. package/dist/components/Lazy/Lazy.svelte +158 -0
  123. package/dist/components/Lazy/Lazy.svelte.d.ts +42 -0
  124. package/dist/components/Masonry/Masonry.svelte +299 -0
  125. package/dist/components/Masonry/Masonry.svelte.d.ts +55 -0
  126. package/dist/components/Menu/Menu/Menu.svelte +65 -0
  127. package/dist/components/Menu/Menu/Menu.svelte.d.ts +17 -0
  128. package/dist/components/Menu/Menu/MenuItem.svelte +90 -0
  129. package/dist/components/Menu/Menu/MenuItem.svelte.d.ts +27 -0
  130. package/dist/components/Modal/Modal.svelte +334 -0
  131. package/dist/components/Modal/Modal.svelte.d.ts +55 -0
  132. package/dist/components/Panel/Card.svelte +141 -0
  133. package/dist/components/Panel/Card.svelte.d.ts +52 -0
  134. package/dist/components/Panel/Hero/Hero.story.md +9 -0
  135. package/dist/components/Panel/Hero/Hero.story.svelte +49 -0
  136. package/dist/components/Panel/Hero/Hero.story.svelte.d.ts +21 -0
  137. package/dist/components/Panel/Hero/Hero.svelte +24 -0
  138. package/dist/components/Panel/Hero/Hero.svelte.d.ts +32 -0
  139. package/dist/components/Panel/LazyPanel.svelte +110 -0
  140. package/dist/components/Panel/LazyPanel.svelte.d.ts +46 -0
  141. package/dist/components/Panel/Panel.svelte +205 -0
  142. package/dist/components/Panel/Panel.svelte.d.ts +23 -0
  143. package/dist/components/Progress/Progress.svelte +220 -0
  144. package/dist/components/Progress/Progress.svelte.d.ts +61 -0
  145. package/dist/components/Separator/Separator.svelte +109 -0
  146. package/dist/components/Separator/Separator.svelte.d.ts +35 -0
  147. package/dist/components/Sidebar/Sidebar.svelte +213 -0
  148. package/dist/components/Sidebar/Sidebar.svelte.d.ts +60 -0
  149. package/dist/components/Skeleton/Skeleton.svelte +170 -0
  150. package/dist/components/Skeleton/Skeleton.svelte.d.ts +48 -0
  151. package/dist/components/Stepper/Stepper.svelte +111 -0
  152. package/dist/components/Stepper/Stepper.svelte.d.ts +54 -0
  153. package/dist/components/Stepper/StepperStep.svelte +369 -0
  154. package/dist/components/Stepper/StepperStep.svelte.d.ts +63 -0
  155. package/dist/components/Table/Table.svelte +167 -0
  156. package/dist/components/Table/Table.svelte.d.ts +56 -0
  157. package/dist/components/Table/TableBody.svelte +41 -0
  158. package/dist/components/Table/TableBody.svelte.d.ts +33 -0
  159. package/dist/components/Table/TableCell.svelte +76 -0
  160. package/dist/components/Table/TableCell.svelte.d.ts +36 -0
  161. package/dist/components/Table/TableHead.svelte +41 -0
  162. package/dist/components/Table/TableHead.svelte.d.ts +32 -0
  163. package/dist/components/Table/TableHeader.svelte +148 -0
  164. package/dist/components/Table/TableHeader.svelte.d.ts +42 -0
  165. package/dist/components/Table/TableRow.svelte +99 -0
  166. package/dist/components/Table/TableRow.svelte.d.ts +40 -0
  167. package/dist/components/Tabs/Tab.svelte +145 -0
  168. package/dist/components/Tabs/Tab.svelte.d.ts +36 -0
  169. package/dist/components/Tabs/TabList.svelte +60 -0
  170. package/dist/components/Tabs/TabList.svelte.d.ts +32 -0
  171. package/dist/components/Tabs/TabPanel.svelte +118 -0
  172. package/dist/components/Tabs/TabPanel.svelte.d.ts +38 -0
  173. package/dist/components/Tabs/Tabs.svelte +287 -0
  174. package/dist/components/Tabs/Tabs.svelte.d.ts +50 -0
  175. package/dist/components/Tag/Tag.svelte +260 -0
  176. package/dist/components/Tag/Tag.svelte.d.ts +54 -0
  177. package/dist/components/Tag/TagGroup.svelte +147 -0
  178. package/dist/components/Tag/TagGroup.svelte.d.ts +62 -0
  179. package/dist/components/ThemeToggle/ThemeToggle.svelte +93 -0
  180. package/dist/components/ThemeToggle/ThemeToggle.svelte.d.ts +12 -0
  181. package/dist/components/Timeline/Timeline.svelte +144 -0
  182. package/dist/components/Timeline/Timeline.svelte.d.ts +48 -0
  183. package/dist/components/Timeline/TimelineItem.svelte +391 -0
  184. package/dist/components/Timeline/TimelineItem.svelte.d.ts +63 -0
  185. package/dist/components/Toast/Toast.svelte +313 -0
  186. package/dist/components/Toast/Toast.svelte.d.ts +44 -0
  187. package/dist/components/Toast/toastStore.d.ts +40 -0
  188. package/dist/components/Toast/toastStore.js +293 -0
  189. package/dist/components/Tooltip/Tooltip.svelte +282 -0
  190. package/dist/components/Tooltip/Tooltip.svelte.d.ts +55 -0
  191. package/dist/components/Tree/Tree.svelte +129 -0
  192. package/dist/components/Tree/Tree.svelte.d.ts +61 -0
  193. package/dist/components/Tree/TreeNode.svelte +332 -0
  194. package/dist/components/Tree/TreeNode.svelte.d.ts +55 -0
  195. package/dist/components/icons/TwintrinsicLogo.svelte +73 -0
  196. package/dist/components/icons/TwintrinsicLogo.svelte.d.ts +17 -0
  197. package/dist/components/icons/twintrinsic-source.svg +73 -0
  198. package/dist/components/icons/twintrinsic.svg +38 -0
  199. package/dist/docs/EventsTable.svelte +86 -0
  200. package/dist/docs/EventsTable.svelte.d.ts +27 -0
  201. package/dist/docs/PropsTable.svelte +103 -0
  202. package/dist/docs/PropsTable.svelte.d.ts +28 -0
  203. package/dist/docs/index.d.ts +2 -0
  204. package/dist/docs/index.js +2 -0
  205. package/dist/helpers/detectLanguage.d.ts +6 -0
  206. package/dist/helpers/detectLanguage.js +60 -0
  207. package/dist/helpers/index.d.ts +1 -0
  208. package/dist/helpers/index.js +1 -0
  209. package/dist/index.d.ts +86 -0
  210. package/dist/index.js +94 -0
  211. package/dist/twintrinsic.css +347 -0
  212. package/package.json +98 -0
@@ -0,0 +1,879 @@
1
+ <!--
2
+ @component
3
+ DataTable - A component for displaying tabular data with advanced features.
4
+ Provides sorting, filtering, pagination, and selection capabilities with proper accessibility.
5
+
6
+ Usage:
7
+ ```svelte
8
+ <DataTable
9
+ data={users}
10
+ columns={[
11
+ { field: 'id', header: 'ID' },
12
+ { field: 'name', header: 'Name' },
13
+ { field: 'email', header: 'Email' }
14
+ ]}
15
+ />
16
+
17
+ <DataTable
18
+ data={products}
19
+ columns={columns}
20
+ sortable
21
+ filterable
22
+ pageable
23
+ selectable
24
+ onsort={handleSort}
25
+ onfilter={handleFilter}
26
+ onpage={handlePage}
27
+ onselect={handleSelect}
28
+ />
29
+ ```
30
+ -->
31
+ <script>
32
+ import { setContext } from "svelte"
33
+
34
+ const {
35
+ /** @type {string} - Additional CSS classes */
36
+ class: className = "",
37
+
38
+ /** @type {string} - HTML id for accessibility */
39
+ id = crypto.randomUUID(),
40
+
41
+ /** @type {Array} - Data to display */
42
+ data = [],
43
+
44
+ /** @type {Array} - Column definitions */
45
+ columns = [],
46
+
47
+ /** @type {boolean} - Whether to enable sorting */
48
+ sortable = false,
49
+
50
+ /** @type {boolean} - Whether to enable filtering */
51
+ filterable = false,
52
+
53
+ /** @type {boolean} - Whether to enable pagination */
54
+ pageable = false,
55
+
56
+ /** @type {boolean} - Whether to enable row selection */
57
+ selectable = false,
58
+
59
+ /** @type {boolean} - Whether to enable multiple row selection */
60
+ multiSelect = false,
61
+
62
+ /** @type {Array} - Selected row keys */
63
+ selected = [],
64
+
65
+ /** @type {string} - Key field for row identification */
66
+ keyField = "id",
67
+
68
+ /** @type {number} - Current page (1-based) */
69
+ page = 1,
70
+
71
+ /** @type {number} - Number of rows per page */
72
+ pageSize = 10,
73
+
74
+ /** @type {Array} - Available page size options */
75
+ pageSizeOptions = [5, 10, 20, 50, 100],
76
+
77
+ /** @type {string} - Field to sort by */
78
+ sortField,
79
+
80
+ /** @type {string} - Sort order (asc, desc) */
81
+ sortOrder = "asc",
82
+
83
+ /** @type {Object} - Filter values by field */
84
+ filters = {},
85
+
86
+ /** @type {boolean} - Whether to show a loading indicator */
87
+ loading = false,
88
+
89
+ /** @type {string} - Text to display when there is no data */
90
+ emptyMessage = "No data available",
91
+
92
+ /** @type {string} - ARIA label for the table */
93
+ ariaLabel = "Data table",
94
+
95
+ /** @type {Function} - Custom row class function */
96
+ rowClass,
97
+
98
+ /** @type {Function} - Custom cell formatter */
99
+ cellFormatter,
100
+
101
+ /** @type {boolean} - Whether to enable responsive mode */
102
+ responsive = true,
103
+
104
+ /** @type {boolean} - Whether to enable striped rows */
105
+ striped = false,
106
+
107
+ /** @type {boolean} - Whether to enable hoverable rows */
108
+ hoverable = true,
109
+
110
+ /** @type {boolean} - Whether to show a border */
111
+ bordered = false,
112
+
113
+ /** @type {boolean} - Whether to make the header sticky */
114
+ stickyHeader = false,
115
+
116
+ /** @type {boolean} - Whether to enable compact mode */
117
+ compact = false,
118
+
119
+ /** @type {(event: CustomEvent) => void} - Sort event handler */
120
+ onsort,
121
+ /** @type {(event: CustomEvent) => void} - Filter event handler */
122
+ onfilter,
123
+ /** @type {(event: CustomEvent) => void} - Page event handler */
124
+ onpage,
125
+ /** @type {(event: CustomEvent) => void} - Select event handler */
126
+ onselect,
127
+ } = $props()
128
+
129
+ // Component state
130
+ let currentPage = $state(1)
131
+ let currentPageSize = $state(10)
132
+ let currentSortField = $state("")
133
+ let currentSortOrder = $state("asc")
134
+ let currentFilters = $state({})
135
+ let selectedRows = $state([])
136
+ let allSelected = $state(false)
137
+ let tableElement = $state()
138
+
139
+ // Update state when props change
140
+ $effect(() => {
141
+ currentPage = page
142
+ currentPageSize = pageSize
143
+ currentSortField = sortField || ""
144
+ currentSortOrder = sortOrder || "asc"
145
+ currentFilters = filters || {}
146
+ selectedRows = Array.isArray(selected) ? [...selected] : []
147
+ })
148
+
149
+ // Provide context for child components
150
+ $effect(() => {
151
+ setContext("dataTable", {
152
+ sortable,
153
+ filterable,
154
+ selectable,
155
+ multiSelect,
156
+ keyField,
157
+ getSortField: () => currentSortField,
158
+ getSortOrder: () => currentSortOrder,
159
+ getFilters: () => currentFilters,
160
+ getSelected: () => selectedRows,
161
+ isSelected: (key) => selectedRows.includes(key),
162
+ toggleSort: (field) => handleSort(field),
163
+ setFilter: (field, value) => handleFilter(field, value),
164
+ toggleSelection: (key) => toggleRowSelection(key),
165
+ selectAll: () => toggleSelectAll(),
166
+ cellFormatter,
167
+ })
168
+ })
169
+
170
+ // Computed values
171
+ const totalRecords = $derived(data.length)
172
+ const totalPages = $derived(Math.max(1, Math.ceil(totalRecords / currentPageSize)))
173
+ const startIndex = $derived((currentPage - 1) * currentPageSize)
174
+ const endIndex = $derived(Math.min(startIndex + currentPageSize, totalRecords))
175
+
176
+ // Process data with sorting, filtering, and pagination
177
+ const processedData = $derived.by(() => {
178
+ let result = [...data]
179
+
180
+ // Apply filters
181
+ if (filterable && Object.keys(currentFilters).length > 0) {
182
+ result = result.filter((item) => {
183
+ return Object.entries(currentFilters).every(([field, filterValue]) => {
184
+ if (!filterValue) return true
185
+
186
+ const value = item[field]
187
+ if (value === undefined || value === null) return false
188
+
189
+ return String(value).toLowerCase().includes(String(filterValue).toLowerCase())
190
+ })
191
+ })
192
+ }
193
+
194
+ // Apply sorting
195
+ if (sortable && currentSortField) {
196
+ result.sort((a, b) => {
197
+ const valueA = a[currentSortField]
198
+ const valueB = b[currentSortField]
199
+
200
+ // Handle null/undefined values
201
+ if (valueA === undefined || valueA === null) return currentSortOrder === "asc" ? -1 : 1
202
+ if (valueB === undefined || valueB === null) return currentSortOrder === "asc" ? 1 : -1
203
+
204
+ // Compare based on type
205
+ if (typeof valueA === "string" && typeof valueB === "string") {
206
+ return currentSortOrder === "asc"
207
+ ? valueA.localeCompare(valueB)
208
+ : valueB.localeCompare(valueA)
209
+ }
210
+
211
+ return currentSortOrder === "asc" ? valueA - valueB : valueB - valueA
212
+ })
213
+ }
214
+
215
+ // Get total after filtering
216
+ const filteredTotal = result.length
217
+
218
+ // Apply pagination
219
+ if (pageable) {
220
+ result = result.slice(startIndex, endIndex)
221
+ }
222
+
223
+ return {
224
+ rows: result,
225
+ filteredTotal,
226
+ }
227
+ })
228
+
229
+ /**
230
+ * Handles sorting
231
+ * @param {string} field - Field to sort by
232
+ */
233
+ function handleSort(field) {
234
+ if (!sortable) return
235
+
236
+ if (currentSortField === field) {
237
+ // Toggle order if same field
238
+ currentSortOrder = currentSortOrder === "asc" ? "desc" : "asc"
239
+ } else {
240
+ // Set new field and default to ascending
241
+ currentSortField = field
242
+ currentSortOrder = "asc"
243
+ }
244
+
245
+ onsort?.(new CustomEvent("sort", { detail: { field: currentSortField, order: currentSortOrder } }))
246
+ }
247
+
248
+ /**
249
+ * Handles filtering
250
+ * @param {string} field - Field to filter
251
+ * @param {string} value - Filter value
252
+ */
253
+ function handleFilter(field, value) {
254
+ if (!filterable) return
255
+
256
+ if (value) {
257
+ currentFilters = { ...currentFilters, [field]: value }
258
+ } else {
259
+ // Remove filter if value is empty
260
+ const { [field]: removed, ...rest } = currentFilters
261
+ currentFilters = rest
262
+ }
263
+
264
+ // Reset to first page when filtering
265
+ currentPage = 1
266
+
267
+ onfilter?.(new CustomEvent("filter", { detail: { filters: currentFilters } }))
268
+ }
269
+
270
+ /**
271
+ * Handles page change
272
+ * @param {number} newPage - New page number
273
+ */
274
+ function handlePageChange(newPage) {
275
+ if (!pageable) return
276
+
277
+ if (newPage >= 1 && newPage <= totalPages) {
278
+ currentPage = newPage
279
+ onpage?.(new CustomEvent("page", { detail: { page: currentPage, pageSize: currentPageSize } }))
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Handles page size change
285
+ * @param {Event} event - Change event
286
+ */
287
+ function handlePageSizeChange(event) {
288
+ if (!pageable) return
289
+
290
+ const newPageSize = Number(event.target.value)
291
+ currentPageSize = newPageSize
292
+
293
+ // Adjust current page to maintain position
294
+ const newTotalPages = Math.max(1, Math.ceil(totalRecords / newPageSize))
295
+ if (currentPage > newTotalPages) {
296
+ currentPage = newTotalPages
297
+ }
298
+
299
+ onpage?.(new CustomEvent("page", { detail: { page: currentPage, pageSize: currentPageSize } }))
300
+ }
301
+
302
+ /**
303
+ * Toggles selection of a row
304
+ * @param {string|number} key - Row key
305
+ */
306
+ function toggleRowSelection(key) {
307
+ if (!selectable) return
308
+
309
+ if (selectedRows.includes(key)) {
310
+ // Remove if already selected
311
+ selectedRows = selectedRows.filter((k) => k !== key)
312
+ allSelected = false
313
+ } else {
314
+ // Add if not selected
315
+ if (multiSelect) {
316
+ selectedRows = [...selectedRows, key]
317
+
318
+ // Check if all visible rows are now selected
319
+ const visibleKeys = processedData.rows.map((row) => row[keyField])
320
+ allSelected = visibleKeys.every((k) => selectedRows.includes(k))
321
+ } else {
322
+ selectedRows = [key]
323
+ allSelected = false
324
+ }
325
+ }
326
+
327
+ onselect?.(new CustomEvent("select", { detail: { selected: selectedRows } }))
328
+ }
329
+
330
+ /**
331
+ * Toggles selection of all rows
332
+ */
333
+ function toggleSelectAll() {
334
+ if (!selectable || !multiSelect) return
335
+
336
+ if (allSelected) {
337
+ // Deselect all visible rows
338
+ const visibleKeys = processedData.rows.map((row) => row[keyField])
339
+ selectedRows = selectedRows.filter((key) => !visibleKeys.includes(key))
340
+ allSelected = false
341
+ } else {
342
+ // Select all visible rows
343
+ const visibleKeys = processedData.rows.map((row) => row[keyField])
344
+ const newSelected = [...selectedRows]
345
+
346
+ visibleKeys.forEach((key) => {
347
+ if (!newSelected.includes(key)) {
348
+ newSelected.push(key)
349
+ }
350
+ })
351
+
352
+ selectedRows = newSelected
353
+ allSelected = true
354
+ }
355
+
356
+ onselect?.(new CustomEvent("select", { detail: { selected: selectedRows } }))
357
+ }
358
+
359
+ /**
360
+ * Gets CSS classes for a row
361
+ * @param {Object} row - Row data
362
+ * @param {number} index - Row index
363
+ * @returns {string} - CSS classes
364
+ */
365
+ function getRowClasses(row, index) {
366
+ const isSelected = selectedRows.includes(row[keyField])
367
+ const classes = [
368
+ "data-table-row",
369
+ isSelected ? "data-table-row-selected" : "",
370
+ striped && index % 2 === 1 ? "data-table-row-striped" : "",
371
+ hoverable ? "data-table-row-hoverable" : "",
372
+ ]
373
+
374
+ if (rowClass && typeof rowClass === "function") {
375
+ const customClass = rowClass(row, index)
376
+ if (customClass) {
377
+ classes.push(customClass)
378
+ }
379
+ }
380
+
381
+ return classes.filter(Boolean).join(" ")
382
+ }
383
+
384
+ /**
385
+ * Formats a cell value
386
+ * @param {any} value - Cell value
387
+ * @param {Object} column - Column definition
388
+ * @param {Object} row - Row data
389
+ * @returns {string} - Formatted value
390
+ */
391
+ function formatCell(value, column, row) {
392
+ if (column.formatter) {
393
+ return column.formatter(value, row)
394
+ }
395
+
396
+ if (cellFormatter) {
397
+ return cellFormatter(value, column, row)
398
+ }
399
+
400
+ if (value === null || value === undefined) {
401
+ return ""
402
+ }
403
+
404
+ return String(value)
405
+ }
406
+ </script>
407
+
408
+ <div
409
+ class="
410
+ data-table-wrapper
411
+ {responsive ? 'data-table-responsive' : ''}
412
+ {className}
413
+ "
414
+ >
415
+ <div class="data-table-container">
416
+ <table
417
+ {id}
418
+ class="
419
+ data-table
420
+ {bordered ? 'data-table-bordered' : ''}
421
+ {compact ? 'data-table-compact' : ''}
422
+ {stickyHeader ? 'data-table-sticky-header' : ''}
423
+ "
424
+ aria-label={ariaLabel}
425
+ aria-busy={loading}
426
+ bind:this={tableElement}
427
+ >
428
+ <thead class="data-table-header">
429
+ <tr>
430
+ {#if selectable}
431
+ <th class="data-table-selection-cell">
432
+ {#if multiSelect}
433
+ <div class="data-table-checkbox">
434
+ <input
435
+ type="checkbox"
436
+ checked={allSelected}
437
+ onchange={toggleSelectAll}
438
+ aria-label="Select all rows"
439
+ />
440
+ </div>
441
+ {/if}
442
+ </th>
443
+ {/if}
444
+
445
+ {#each columns as column, colIndex}
446
+ <th
447
+ class="
448
+ data-table-header-cell
449
+ {column.sortable !== false && sortable ? 'data-table-sortable' : ''}
450
+ {column.class || ''}
451
+ "
452
+ style={column.style || ''}
453
+ onclick={() => column.sortable !== false && sortable && handleSort(column.field)}
454
+ aria-sort={currentSortField === column.field
455
+ ? (currentSortOrder === 'asc' ? 'ascending' : 'descending')
456
+ : undefined}
457
+ >
458
+ <div class="data-table-header-content">
459
+ <span class="data-table-header-text">
460
+ {column.header || column.field}
461
+ </span>
462
+
463
+ {#if column.sortable !== false && sortable}
464
+ <span class="data-table-sort-icon">
465
+ {#if currentSortField === column.field}
466
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
467
+ <path
468
+ stroke-linecap="round"
469
+ stroke-linejoin="round"
470
+ stroke-width="2"
471
+ d={currentSortOrder === 'asc'
472
+ ? "M5 15l7-7 7 7"
473
+ : "M19 9l-7 7-7-7"}
474
+ ></path>
475
+ </svg>
476
+ {:else}
477
+ <svg class="w-4 h-4 opacity-0 hover:parent:opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
478
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"></path>
479
+ </svg>
480
+ {/if}
481
+ </span>
482
+ {/if}
483
+ </div>
484
+
485
+ {#if column.filterable !== false && filterable}
486
+ <div class="data-table-filter">
487
+ <input
488
+ type="text"
489
+ placeholder="Filter..."
490
+ value={currentFilters[column.field] || ''}
491
+ oninput={(e) => handleFilter(column.field, e.target.value)}
492
+ onclick={(e) => e.stopPropagation()}
493
+ aria-label={`Filter by ${column.header || column.field}`}
494
+ class="data-table-filter-input"
495
+ />
496
+ </div>
497
+ {/if}
498
+ </th>
499
+ {/each}
500
+ </tr>
501
+ </thead>
502
+
503
+ <tbody class="data-table-body">
504
+ {#if loading}
505
+ <tr class="data-table-loading-row">
506
+ <td colspan={columns.length + (selectable ? 1 : 0)} class="data-table-loading-cell">
507
+ <div class="data-table-loading">
508
+ <svg class="data-table-spinner" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
509
+ <circle class="data-table-spinner-track" cx="12" cy="12" r="10" />
510
+ <circle class="data-table-spinner-path" cx="12" cy="12" r="10" />
511
+ </svg>
512
+ <span>Loading...</span>
513
+ </div>
514
+ </td>
515
+ </tr>
516
+ {:else if processedData.rows.length === 0}
517
+ <tr class="data-table-empty-row">
518
+ <td colspan={columns.length + (selectable ? 1 : 0)} class="data-table-empty-cell">
519
+ {emptyMessage}
520
+ </td>
521
+ </tr>
522
+ {:else}
523
+ {#each processedData.rows as row, rowIndex}
524
+ <tr
525
+ class={getRowClasses(row, rowIndex)}
526
+ onclick={() => selectable && toggleRowSelection(row[keyField])}
527
+ aria-selected={selectable && selectedRows.includes(row[keyField])}
528
+ >
529
+ {#if selectable}
530
+ <td class="data-table-selection-cell">
531
+ <div class="data-table-checkbox">
532
+ <input
533
+ type={multiSelect ? 'checkbox' : 'radio'}
534
+ checked={selectedRows.includes(row[keyField])}
535
+ onchange={() => toggleRowSelection(row[keyField])}
536
+ name={multiSelect ? undefined : `${id}-selection`}
537
+ aria-label={`Select row ${rowIndex + 1}`}
538
+ onclick={(e) => e.stopPropagation()}
539
+ />
540
+ </div>
541
+ </td>
542
+ {/if}
543
+
544
+ {#each columns as column, colIndex}
545
+ <td
546
+ class="
547
+ data-table-cell
548
+ {column.cellClass || ''}
549
+ "
550
+ style={column.cellStyle || ''}
551
+ data-label={responsive ? (column.header || column.field) : undefined}
552
+ >
553
+ {#if column.template}
554
+ {@html column.template(row[column.field])}
555
+ {:else}
556
+ {formatCell(row[column.field], column, row)}
557
+ {/if}
558
+ </td>
559
+ {/each}
560
+ </tr>
561
+ {/each}
562
+ {/if}
563
+ </tbody>
564
+ </table>
565
+ </div>
566
+
567
+ {#if pageable && totalRecords > 0}
568
+ <div class="data-table-pagination">
569
+ <div class="data-table-pagination-info">
570
+ Showing {startIndex + 1} to {endIndex} of {processedData.filteredTotal} entries
571
+ </div>
572
+
573
+ <div class="data-table-pagination-controls">
574
+ <div class="data-table-page-size">
575
+ <label>
576
+ <span>Rows per page:</span>
577
+ <select
578
+ value={currentPageSize}
579
+ onchange={handlePageSizeChange}
580
+ aria-label="Rows per page"
581
+ >
582
+ {#each pageSizeOptions as option}
583
+ <option value={option}>{option}</option>
584
+ {/each}
585
+ </select>
586
+ </label>
587
+ </div>
588
+
589
+ <div class="data-table-page-controls">
590
+ <button
591
+ type="button"
592
+ class="data-table-page-button"
593
+ disabled={currentPage === 1}
594
+ onclick={() => handlePageChange(1)}
595
+ aria-label="First page"
596
+ >
597
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
598
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 19l-7-7 7-7m8 14l-7-7 7-7"></path>
599
+ </svg>
600
+ </button>
601
+
602
+ <button
603
+ type="button"
604
+ class="data-table-page-button"
605
+ disabled={currentPage === 1}
606
+ onclick={() => handlePageChange(currentPage - 1)}
607
+ aria-label="Previous page"
608
+ >
609
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
610
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
611
+ </svg>
612
+ </button>
613
+
614
+ <span class="data-table-page-info">
615
+ Page {currentPage} of {totalPages}
616
+ </span>
617
+
618
+ <button
619
+ type="button"
620
+ class="data-table-page-button"
621
+ disabled={currentPage === totalPages}
622
+ onclick={() => handlePageChange(currentPage + 1)}
623
+ aria-label="Next page"
624
+ >
625
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
626
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
627
+ </svg>
628
+ </button>
629
+
630
+ <button
631
+ type="button"
632
+ class="data-table-page-button"
633
+ disabled={currentPage === totalPages}
634
+ onclick={() => handlePageChange(totalPages)}
635
+ aria-label="Last page"
636
+ >
637
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
638
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 5l7 7-7 7M5 5l7 7-7 7"></path>
639
+ </svg>
640
+ </button>
641
+ </div>
642
+ </div>
643
+ </div>
644
+ {/if}
645
+ </div>
646
+
647
+ <style>
648
+ @reference "../../twintrinsic.css";
649
+
650
+ .data-table-wrapper {
651
+ @apply w-full;
652
+ }
653
+
654
+ .data-table-container {
655
+ @apply w-full overflow-x-auto;
656
+ }
657
+
658
+ .data-table {
659
+ @apply w-full border-collapse;
660
+ @apply text-text dark:text-text;
661
+ }
662
+
663
+ .data-table-bordered {
664
+ @apply border border-border dark:border-border;
665
+ }
666
+
667
+ .data-table-bordered th,
668
+ .data-table-bordered td {
669
+ @apply border border-border dark:border-border;
670
+ }
671
+
672
+ .data-table-compact th,
673
+ .data-table-compact td {
674
+ @apply py-1 px-2;
675
+ }
676
+
677
+ .data-table-sticky-header thead {
678
+ @apply sticky top-0;
679
+ @apply z-10;
680
+ }
681
+
682
+ .data-table-header {
683
+ @apply bg-surface dark:bg-surface;
684
+ }
685
+
686
+ .data-table-header-cell {
687
+ @apply py-3 px-4;
688
+ @apply font-medium text-left;
689
+ @apply whitespace-nowrap;
690
+ }
691
+
692
+ .data-table-sortable {
693
+ @apply cursor-pointer;
694
+ @apply hover:bg-hover dark:hover:bg-hover;
695
+ }
696
+
697
+ .data-table-header-content {
698
+ @apply flex items-center;
699
+ }
700
+
701
+ .data-table-header-text {
702
+ @apply flex-grow;
703
+ }
704
+
705
+ .data-table-sort-icon {
706
+ @apply ml-1 flex-shrink-0;
707
+ }
708
+
709
+ .data-table-filter {
710
+ @apply mt-2;
711
+ }
712
+
713
+ .data-table-filter-input {
714
+ @apply w-full px-2 py-1;
715
+ @apply bg-background dark:bg-background;
716
+ @apply border border-border dark:border-border;
717
+ @apply rounded-md;
718
+ @apply text-sm;
719
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
720
+ }
721
+
722
+ .data-table-body {
723
+ @apply bg-background dark:bg-background;
724
+ }
725
+
726
+ .data-table-row {
727
+ @apply border-t border-border dark:border-border;
728
+ }
729
+
730
+ .data-table-row-striped {
731
+ @apply bg-muted/5 dark:bg-muted/5;
732
+ }
733
+
734
+ .data-table-row-hoverable {
735
+ @apply hover:bg-hover dark:hover:bg-hover;
736
+ }
737
+
738
+ .data-table-row-selected {
739
+ @apply bg-primary-50 dark:bg-primary-900/20;
740
+ }
741
+
742
+ .data-table-cell {
743
+ @apply py-3 px-4;
744
+ @apply align-middle;
745
+ }
746
+
747
+ .data-table-selection-cell {
748
+ @apply w-10 py-3 px-4;
749
+ @apply text-center;
750
+ }
751
+
752
+ .data-table-checkbox {
753
+ @apply flex items-center justify-center;
754
+ }
755
+
756
+ .data-table-checkbox input {
757
+ @apply w-4 h-4;
758
+ @apply text-primary-500 dark:text-primary-500;
759
+ @apply border border-border dark:border-border;
760
+ @apply rounded;
761
+ @apply focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
762
+ }
763
+
764
+ .data-table-loading-cell,
765
+ .data-table-empty-cell {
766
+ @apply py-8 px-4;
767
+ @apply text-center;
768
+ @apply text-muted dark:text-muted;
769
+ }
770
+
771
+ .data-table-loading {
772
+ @apply flex flex-col items-center justify-center;
773
+ @apply space-y-2;
774
+ }
775
+
776
+ .data-table-spinner {
777
+ @apply w-8 h-8;
778
+ @apply animate-spin;
779
+ }
780
+
781
+ .data-table-spinner-track {
782
+ @apply opacity-25;
783
+ @apply stroke-current;
784
+ @apply fill-none;
785
+ @apply stroke-2;
786
+ }
787
+
788
+ .data-table-spinner-path {
789
+ @apply opacity-75;
790
+ @apply stroke-current;
791
+ @apply fill-none;
792
+ @apply stroke-2;
793
+ stroke-dasharray: 60;
794
+ stroke-dashoffset: 45;
795
+ }
796
+
797
+ .data-table-pagination {
798
+ @apply mt-4;
799
+ @apply flex flex-col sm:flex-row items-center justify-between;
800
+ @apply text-sm;
801
+ }
802
+
803
+ .data-table-pagination-info {
804
+ @apply mb-2 sm:mb-0;
805
+ @apply text-muted dark:text-muted;
806
+ }
807
+
808
+ .data-table-pagination-controls {
809
+ @apply flex flex-col sm:flex-row items-center;
810
+ @apply space-y-2 sm:space-y-0 sm:space-x-4;
811
+ }
812
+
813
+ .data-table-page-size {
814
+ @apply flex items-center;
815
+ @apply space-x-2;
816
+ }
817
+
818
+ .data-table-page-size select {
819
+ @apply px-2 py-1;
820
+ @apply bg-background dark:bg-background;
821
+ @apply border border-border dark:border-border;
822
+ @apply rounded-md;
823
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
824
+ }
825
+
826
+ .data-table-page-controls {
827
+ @apply flex items-center;
828
+ @apply space-x-1;
829
+ }
830
+
831
+ .data-table-page-button {
832
+ @apply p-1;
833
+ @apply bg-background dark:bg-background;
834
+ @apply border border-border dark:border-border;
835
+ @apply rounded-md;
836
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400;
837
+ @apply hover:bg-hover dark:hover:bg-hover;
838
+ @apply transition-colors duration-150;
839
+ }
840
+
841
+ .data-table-page-button:disabled {
842
+ @apply opacity-50 cursor-not-allowed;
843
+ @apply hover:bg-background dark:hover:bg-background;
844
+ }
845
+
846
+ .data-table-page-info {
847
+ @apply px-2;
848
+ }
849
+
850
+ /* Responsive styles */
851
+ @media (max-width: 640px) {
852
+ .data-table-responsive thead {
853
+ @apply hidden;
854
+ }
855
+
856
+ .data-table-responsive tbody tr {
857
+ @apply block border-b border-border dark:border-border;
858
+ }
859
+
860
+ .data-table-responsive tbody td {
861
+ @apply block text-right;
862
+ @apply py-2 px-3;
863
+ @apply border-none;
864
+ }
865
+
866
+ .data-table-responsive tbody td::before {
867
+ content: attr(data-label);
868
+ @apply float-left font-medium;
869
+ }
870
+
871
+ .data-table-responsive .data-table-selection-cell {
872
+ @apply w-full text-left;
873
+ }
874
+
875
+ .data-table-responsive .data-table-checkbox {
876
+ @apply justify-start;
877
+ }
878
+ }
879
+ </style>