@rimelight/ui 0.0.47 → 0.0.49

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 (197) hide show
  1. package/dist/index.d.mts +4 -5
  2. package/dist/preset.mjs +73 -1
  3. package/dist/resolver.d.mts +3 -4
  4. package/dist/resolver.mjs +2 -4
  5. package/dist/shortcuts.d.mts +10 -11
  6. package/dist/stores.d.mts +3 -4
  7. package/package.json +8 -6
  8. package/src/components/3d-hover/RLS3dHover.tsx +21 -0
  9. package/src/components/accordion/RLSAccordion.tsx +112 -0
  10. package/src/components/app/RLSApp.tsx +21 -0
  11. package/src/components/aspect-ratio/RLSAspectRatio.tsx +30 -0
  12. package/src/components/audio/RLSAudio.tsx +27 -0
  13. package/src/components/avatar/RLSAvatar.tsx +142 -0
  14. package/src/components/avatar/avatar.ts +22 -21
  15. package/src/components/avatar-group/RLSAvatarGroup.tsx +163 -0
  16. package/src/components/badge/RLSBadge.tsx +1 -1
  17. package/src/components/badge/RLVBadge.vue +5 -3
  18. package/src/components/banner/RLSBanner.tsx +151 -0
  19. package/src/components/breadcrumb/RLSBreadcrumb.tsx +194 -0
  20. package/src/components/browser-mockup/RLSBrowserMockup.tsx +21 -0
  21. package/src/components/button/RLSButton.tsx +49 -40
  22. package/src/components/button/RLVButton.vue +44 -32
  23. package/src/components/calendar/RLSCalendar.tsx +26 -0
  24. package/src/components/callout/RLSCallout.tsx +151 -0
  25. package/src/components/card/RLSCard.tsx +4 -0
  26. package/src/components/card/RLVCard.vue +4 -0
  27. package/src/components/card/card.ts +10 -0
  28. package/src/components/carousel/RLACarousel.astro +14 -1
  29. package/src/components/carousel/RLSCarousel.tsx +260 -0
  30. package/src/components/chart/RLSChart.tsx +299 -0
  31. package/src/components/chat/RLSChat.tsx +27 -0
  32. package/src/components/chat-message/RLSChatMessage.tsx +27 -0
  33. package/src/components/chat-messages/RLSChatMessages.tsx +27 -0
  34. package/src/components/chat-palette/RLSChatPalette.tsx +27 -0
  35. package/src/components/chat-prompt/RLSChatPrompt.tsx +27 -0
  36. package/src/components/chat-prompt-submit/RLSChatPromptSubmit.tsx +27 -0
  37. package/src/components/chat-reasoning/RLSChatReasoning.tsx +27 -0
  38. package/src/components/chat-shimmer/RLSChatShimmer.tsx +27 -0
  39. package/src/components/chat-tool/RLSChatTool.tsx +27 -0
  40. package/src/components/checkbox/RLACheckbox.astro +1 -0
  41. package/src/components/checkbox/RLSCheckbox.tsx +1 -1
  42. package/src/components/checkbox/checkbox.theme.ts +2 -2
  43. package/src/components/chip/RLSChip.tsx +65 -0
  44. package/src/components/collapsible/RLSCollapsible.tsx +89 -0
  45. package/src/components/color-area/RLSColorArea.tsx +554 -0
  46. package/src/components/color-field/RLSColorField.tsx +236 -0
  47. package/src/components/color-picker/RLSColorPicker.tsx +26 -0
  48. package/src/components/color-slider/RLSColorSlider.tsx +373 -0
  49. package/src/components/command-palette/RLSCommandPalette.tsx +26 -0
  50. package/src/components/compare/RLSCompare.tsx +26 -0
  51. package/src/components/confirm/RLSConfirm.tsx +173 -0
  52. package/src/components/container/RLSContainer.tsx +24 -0
  53. package/src/components/context-menu/RLSContextMenu.tsx +26 -0
  54. package/src/components/copy-markdown/RLSCopyMarkdown.tsx +57 -0
  55. package/src/components/dashboard-group/RLSDashboardGroup.tsx +29 -0
  56. package/src/components/dashboard-navbar/RLSDashboardNavbar.tsx +116 -0
  57. package/src/components/dashboard-panel/RLSDashboardPanel.tsx +84 -0
  58. package/src/components/dashboard-resize-handle/RLSDashboardResizeHandle.tsx +35 -0
  59. package/src/components/dashboard-search/RLADashboardSearch.astro +1 -1
  60. package/src/components/dashboard-search/RLSDashboardSearch.tsx +125 -0
  61. package/src/components/dashboard-search-button/RLSDashboardSearchButton.tsx +82 -0
  62. package/src/components/dashboard-sidebar/RLSDashboardSidebar.tsx +184 -0
  63. package/src/components/dashboard-sidebar-collapse/RLSDashboardSidebarCollapse.tsx +46 -0
  64. package/src/components/dashboard-sidebar-toggle/RLSDashboardSidebarToggle.tsx +35 -0
  65. package/src/components/dashboard-toolbar/RLSDashboardToolbar.tsx +49 -0
  66. package/src/components/date/RLSDate.tsx +34 -0
  67. package/src/components/dock/RLSDock.tsx +26 -0
  68. package/src/components/drawer/RLSDrawer.tsx +177 -0
  69. package/src/components/dropdown-menu/RLADropdownMenu.astro +250 -241
  70. package/src/components/dropdown-menu/RLSDropdownMenu.tsx +183 -86
  71. package/src/components/editor/RLSEditor.tsx +26 -0
  72. package/src/components/empty/RLSEmpty.tsx +132 -0
  73. package/src/components/error/RLSError.tsx +26 -0
  74. package/src/components/field-group/RLSFieldGroup.tsx +46 -0
  75. package/src/components/fieldset/RLSFieldset.tsx +26 -0
  76. package/src/components/file-upload/RLSFileUpload.tsx +163 -0
  77. package/src/components/floating-action/RLSFloatingAction.tsx +26 -0
  78. package/src/components/footer/RLSFooter.tsx +56 -0
  79. package/src/components/form/RLSForm.tsx +34 -0
  80. package/src/components/gallery/RLSGallery.tsx +21 -0
  81. package/src/components/gamepad/RLSGamepad.tsx +269 -0
  82. package/src/components/grid/RLSGrid.tsx +29 -0
  83. package/src/components/head/RLAHead.astro +9 -0
  84. package/src/components/head/RLSHead.tsx +42 -0
  85. package/src/components/head/UIHead.astro +3 -0
  86. package/src/components/header/RLSHeader.tsx +87 -0
  87. package/src/components/hover-card/RLSHoverCard.tsx +72 -0
  88. package/src/components/icon/RLSIcon.tsx +3 -1
  89. package/src/components/image/RLAImage.astro +447 -138
  90. package/src/components/image/RLSImage.tsx +545 -0
  91. package/src/components/image/image.theme.ts +19 -3
  92. package/src/components/image/image.ts +31 -1
  93. package/src/components/input/RLAInput.astro +62 -51
  94. package/src/components/input/RLSInput.tsx +1 -1
  95. package/src/components/input-date/RLSInputDate.tsx +92 -0
  96. package/src/components/input-menu/RLSInputMenu.tsx +199 -0
  97. package/src/components/input-number/RLSInputNumber.tsx +184 -0
  98. package/src/components/input-pin/RLSInputPin.tsx +185 -0
  99. package/src/components/input-rating/RLSInputRating.tsx +198 -0
  100. package/src/components/input-rating/input-rating.ts +4 -0
  101. package/src/components/input-tags/RLSInputTags.tsx +94 -0
  102. package/src/components/input-time/RLSInputTime.tsx +92 -0
  103. package/src/components/kbd/RLSKbd.tsx +37 -0
  104. package/src/components/keyboard/RLSKeyboard.tsx +476 -0
  105. package/src/components/knob/RLSKnob.tsx +27 -0
  106. package/src/components/label/RLSLabel.tsx +27 -0
  107. package/src/components/layout-grid/RLSLayoutGrid.tsx +402 -0
  108. package/src/components/layout-grid/layout-grid.ts +25 -1
  109. package/src/components/link/RLSLink.tsx +2 -1
  110. package/src/components/link/link.ts +21 -12
  111. package/src/components/link-group/RLSLinkGroup.tsx +76 -0
  112. package/src/components/listbox/RLSListbox.tsx +27 -0
  113. package/src/components/locale-selector/RLALocaleSelector.astro +2 -1
  114. package/src/components/locale-selector/RLSLocaleSelector.tsx +178 -0
  115. package/src/components/logo/RLALogo.astro +69 -4
  116. package/src/components/logo/RLSLogo.tsx +129 -0
  117. package/src/components/main/RLSMain.tsx +40 -0
  118. package/src/components/marquee/RLSMarquee.tsx +80 -0
  119. package/src/components/meter/RLSMeter.tsx +27 -0
  120. package/src/components/modal/RLAModal.astro +1 -1
  121. package/src/components/modal/RLSModal.tsx +109 -72
  122. package/src/components/navigation-menu/RLSNavigationMenu.tsx +228 -0
  123. package/src/components/navigation-menu/navigation-menu.ts +2 -2
  124. package/src/components/package-managers/RLSPackageManagers.tsx +204 -0
  125. package/src/components/page/RLSPage.tsx +21 -0
  126. package/src/components/page-aside/RLSPageAside.tsx +21 -0
  127. package/src/components/page-body/RLSPageBody.tsx +21 -0
  128. package/src/components/page-header/RLSPageHeader.tsx +21 -0
  129. package/src/components/page-section/RLSPageSection.tsx +201 -0
  130. package/src/components/pagination/RLSPagination.tsx +114 -0
  131. package/src/components/password/RLSPassword.tsx +27 -0
  132. package/src/components/phone-mockup/RLSPhoneMockup.tsx +27 -0
  133. package/src/components/placeholder/RLSPlaceholder.tsx +47 -0
  134. package/src/components/popover/RLSPopover.tsx +124 -0
  135. package/src/components/post/RLSPost.tsx +134 -0
  136. package/src/components/pricing-plan/RLSPricingPlan.tsx +26 -0
  137. package/src/components/pricing-table/RLSPricingTable.tsx +26 -0
  138. package/src/components/progress/RLSProgress.tsx +53 -0
  139. package/src/components/prose/RLSProse.tsx +26 -0
  140. package/src/components/qr-code/RLSQrCode.tsx +26 -0
  141. package/src/components/quote/RLAQuote.astro +10 -6
  142. package/src/components/quote/RLSQuote.tsx +37 -0
  143. package/src/components/quote/quote.theme.ts +5 -2
  144. package/src/components/radio-group/RLSRadioGroup.tsx +37 -0
  145. package/src/components/rating/RLSRating.tsx +27 -0
  146. package/src/components/scroll-area/RLSScrollArea.tsx +210 -0
  147. package/src/components/scroll-to-top/RLAScrollToTop.astro +1 -0
  148. package/src/components/scroll-to-top/RLSScrollToTop.tsx +145 -0
  149. package/src/components/search/RLSSearch.tsx +237 -0
  150. package/src/components/select/RLASelect.astro +67 -4
  151. package/src/components/select/RLSSelect.tsx +107 -39
  152. package/src/components/select/RLVSelect.vue +50 -1
  153. package/src/components/select/select.theme.ts +30 -10
  154. package/src/components/select/select.ts +20 -1
  155. package/src/components/separator/RLSSeparator.tsx +92 -0
  156. package/src/components/share/RLSShare.tsx +66 -0
  157. package/src/components/shortcuts/RLSShortcuts.tsx +393 -0
  158. package/src/components/sidebar/RLSSidebar.tsx +271 -0
  159. package/src/components/skeleton/RLSSkeleton.tsx +21 -0
  160. package/src/components/slideover/RLASlideover.astro +1 -1
  161. package/src/components/slideover/RLSSlideover.tsx +159 -0
  162. package/src/components/slider/RLSSlider.tsx +112 -0
  163. package/src/components/speed-dial/RLSSpeedDial.tsx +27 -0
  164. package/src/components/spinner/RLSSpinner.tsx +22 -0
  165. package/src/components/splitter/RLSSplitter.tsx +178 -0
  166. package/src/components/spoiler/RLSSpoiler.tsx +26 -0
  167. package/src/components/stepper/RLSStepper.tsx +205 -0
  168. package/src/components/steps/RLSSteps.tsx +108 -0
  169. package/src/components/sticky-surface/RLSStickySurface.tsx +63 -0
  170. package/src/components/surround/RLSSurround.tsx +57 -0
  171. package/src/components/switch/RLSSwitch.tsx +56 -0
  172. package/src/components/table/RLATable.astro +1044 -579
  173. package/src/components/table/RLSTable.tsx +893 -0
  174. package/src/components/table/table.theme.ts +47 -16
  175. package/src/components/table/table.ts +104 -66
  176. package/src/components/table-of-contents/RLSTableOfContents.tsx +196 -0
  177. package/src/components/tabs/RLSTabs.tsx +298 -0
  178. package/src/components/textarea/RLATextarea.astro +1 -0
  179. package/src/components/textarea/RLSTextarea.tsx +3 -1
  180. package/src/components/theme-selector/RLAThemeSelector.astro +85 -56
  181. package/src/components/theme-selector/RLSThemeSelector.tsx +131 -0
  182. package/src/components/theme-selector/theme-selector.theme.ts +5 -7
  183. package/src/components/theme-selector/theme-selector.ts +11 -1
  184. package/src/components/timeline/RLSTimeline.tsx +171 -0
  185. package/src/components/toast/RLSToast.tsx +179 -0
  186. package/src/components/toaster/RLSToaster.tsx +82 -0
  187. package/src/components/tooltip/RLSTooltip.tsx +48 -0
  188. package/src/components/tour/RLSTour.tsx +26 -0
  189. package/src/components/tree/RLSTree.tsx +26 -0
  190. package/src/components/user/RLSUser.tsx +26 -0
  191. package/src/components/video/RLAVideo.astro +4 -1
  192. package/src/components/video/RLSVideo.tsx +158 -0
  193. package/src/components/watermark/RLSWatermark.tsx +27 -0
  194. package/src/components/window-mockup/RLSWindowMockup.tsx +27 -0
  195. package/src/preset.ts +73 -1
  196. package/src/resolver.ts +6 -1
  197. package/src/virtual.d.ts +6 -0
@@ -0,0 +1,893 @@
1
+ import { createMemo, createSignal, For, Show, type JSX } from "solid-js"
2
+ import type {
3
+ TableProps,
4
+ TableColumn,
5
+ TableRow,
6
+ TableGroup,
7
+ TableApi,
8
+ SortingState,
9
+ ColumnFiltersState,
10
+ TableColumnPinningState,
11
+ TableRowPinningState,
12
+ TablePaginationState
13
+ } from "./table"
14
+ import { tableTheme } from "./table.theme"
15
+ import RLSInput from "../input/RLSInput"
16
+ import RLSDropdownMenu from "../dropdown-menu/RLSDropdownMenu"
17
+ import RLSIcon from "../icon/RLSIcon"
18
+
19
+ type AnyData = Record<string, any>
20
+
21
+ export interface RLSTableProps<T = any> extends TableProps<T> {
22
+ ref?: (api: TableApi<T>) => void | TableApi<T>
23
+ toolbar?: () => JSX.Element
24
+ captionContent?: () => JSX.Element
25
+ groupHeader?: (group: TableGroup<T>, colSpan: number) => JSX.Element
26
+ expandedContent?: (row: TableRow<T>) => JSX.Element
27
+ loadingContent?: () => JSX.Element
28
+ emptyContent?: () => JSX.Element
29
+ bodyTop?: () => JSX.Element
30
+ bodyBottom?: () => JSX.Element
31
+ }
32
+
33
+ function getNestedValue(obj: AnyData, key: string): any {
34
+ if (!obj || typeof obj !== "object") return undefined
35
+ if (key.includes(".")) {
36
+ return key.split(".").reduce((acc: any, k) => acc?.[k], obj)
37
+ }
38
+ return obj[key]
39
+ }
40
+
41
+ export function RLSTable<T = any>(props: RLSTableProps<T>) {
42
+ let tableElementRef: HTMLTableElement | null = null
43
+
44
+ // ─── Controlled / Uncontrolled State Store ──────────────────────────────────
45
+ const [internalSorting, setInternalSorting] = createSignal<SortingState>(props.sorting ?? [])
46
+ const [internalGlobalFilter, setInternalGlobalFilter] = createSignal<string>(
47
+ props.globalFilter ?? ""
48
+ )
49
+ const [internalColumnFilters, setInternalColumnFilters] = createSignal<ColumnFiltersState>(
50
+ props.columnFilters ?? []
51
+ )
52
+ const [internalColumnVisibility, setInternalColumnVisibility] = createSignal<
53
+ Record<string, boolean>
54
+ >(props.columnVisibility ?? {})
55
+ const [internalColumnPinning, setInternalColumnPinning] = createSignal<TableColumnPinningState>(
56
+ props.columnPinning ?? {}
57
+ )
58
+ const [internalRowSelection, setInternalRowSelection] = createSignal<Record<string, boolean>>(
59
+ props.rowSelection ?? {}
60
+ )
61
+ const [internalExpanded, setInternalExpanded] = createSignal<Record<string, boolean>>(
62
+ props.expanded ?? {}
63
+ )
64
+ const [internalRowPinning, setInternalRowPinning] = createSignal<TableRowPinningState>(
65
+ props.rowPinning ?? {}
66
+ )
67
+ const [internalPagination, setInternalPagination] = createSignal<
68
+ TablePaginationState | undefined
69
+ >(props.pagination)
70
+
71
+ // Reactive state readers that prefer controlled props if provided
72
+ const currentSorting = () => (props.sorting !== undefined ? props.sorting : internalSorting())
73
+ const currentGlobalFilter = () =>
74
+ props.globalFilter !== undefined ? props.globalFilter : internalGlobalFilter()
75
+ const currentColumnFilters = () =>
76
+ props.columnFilters !== undefined ? props.columnFilters : internalColumnFilters()
77
+ const currentColumnVisibility = () =>
78
+ props.columnVisibility !== undefined ? props.columnVisibility : internalColumnVisibility()
79
+ const currentColumnPinning = () =>
80
+ props.columnPinning !== undefined ? props.columnPinning : internalColumnPinning()
81
+ const currentRowSelection = () =>
82
+ props.rowSelection !== undefined ? props.rowSelection : internalRowSelection()
83
+ const currentExpanded = () => (props.expanded !== undefined ? props.expanded : internalExpanded())
84
+ const currentRowPinning = () =>
85
+ props.rowPinning !== undefined ? props.rowPinning : internalRowPinning()
86
+ const currentPagination = () =>
87
+ props.pagination !== undefined ? props.pagination : internalPagination()
88
+
89
+ const updateSorting = (next: SortingState) => {
90
+ setInternalSorting(next)
91
+ props.onSortingChange?.(next)
92
+ }
93
+ const updateGlobalFilter = (val: string) => {
94
+ setInternalGlobalFilter(val)
95
+ props.onGlobalFilterChange?.(val)
96
+ }
97
+ const updateColumnFilters = (next: ColumnFiltersState) => {
98
+ setInternalColumnFilters(next)
99
+ props.onColumnFiltersChange?.(next)
100
+ }
101
+ const updateColumnVisibility = (next: Record<string, boolean>) => {
102
+ setInternalColumnVisibility(next)
103
+ props.onColumnVisibilityChange?.(next)
104
+ }
105
+ const updateRowSelection = (next: Record<string, boolean>) => {
106
+ setInternalRowSelection(next)
107
+ props.onRowSelectionChange?.(next)
108
+ }
109
+ const updateExpanded = (next: Record<string, boolean>) => {
110
+ setInternalExpanded(next)
111
+ props.onExpandedChange?.(next)
112
+ }
113
+ const updateColumnPinning = (next: TableColumnPinningState) => {
114
+ setInternalColumnPinning(next)
115
+ props.onColumnPinningChange?.(next)
116
+ }
117
+ const updateRowPinning = (next: TableRowPinningState) => {
118
+ setInternalRowPinning(next)
119
+ props.onRowPinningChange?.(next)
120
+ }
121
+ const updatePagination = (next: TablePaginationState) => {
122
+ setInternalPagination(next)
123
+ props.onPaginationChange?.(next)
124
+ }
125
+
126
+ // ─── Column resolution ───────────────────────────────────────────────────────
127
+ const resolvedColumns = createMemo<TableColumn<T>[]>(() => {
128
+ if (props.columns && props.columns.length > 0) {
129
+ return props.columns
130
+ }
131
+ const d = props.data
132
+ if (d && d.length > 0 && typeof d[0] === "object" && d[0] !== null) {
133
+ const firstRow = d[0] as AnyData
134
+ return Object.keys(firstRow).map((key) => ({
135
+ id: key,
136
+ accessorKey: key as any,
137
+ header: key.charAt(0).toUpperCase() + key.slice(1),
138
+ enableSorting: true
139
+ }))
140
+ }
141
+ return []
142
+ })
143
+
144
+ const visibleColumns = createMemo(() => {
145
+ const visibility = currentColumnVisibility()
146
+ return resolvedColumns().filter((col) => {
147
+ const id = (col.id || col.accessorKey || "") as string
148
+ if (!id) return true
149
+ if (col.enableHiding === false) return true
150
+ return visibility[id] !== false
151
+ })
152
+ })
153
+
154
+ // ─── Row Hierarchy Building ──────────────────────────────────────────────────
155
+ const buildRowTree = (rawItems: T[], parentDepth = 0): TableRow<T>[] => {
156
+ return rawItems.map((item, idx) => {
157
+ const rId = props.getRowId
158
+ ? props.getRowId(item, idx)
159
+ : (item as any)?.id !== undefined
160
+ ? String((item as any).id)
161
+ : String(idx)
162
+
163
+ let subRowsList: TableRow<T>[] | undefined = undefined
164
+ if (props.getSubRows) {
165
+ const children = props.getSubRows(item, idx)
166
+ if (children && children.length > 0) {
167
+ subRowsList = buildRowTree(children, parentDepth + 1)
168
+ }
169
+ } else if (Array.isArray((item as any)?.children) && (item as any).children.length > 0) {
170
+ subRowsList = buildRowTree((item as any).children, parentDepth + 1)
171
+ }
172
+
173
+ const rowObj: TableRow<T> = {
174
+ id: rId,
175
+ original: item,
176
+ index: idx,
177
+ depth: parentDepth,
178
+ getValue: (key: string) => getNestedValue(item as AnyData, key),
179
+ getIsSelected: () => !!currentRowSelection()[rId],
180
+ toggleSelected: (val?: boolean) => {
181
+ const nextVal = val !== undefined ? val : !currentRowSelection()[rId]
182
+ updateRowSelection({ ...currentRowSelection(), [rId]: nextVal })
183
+ },
184
+ getIsExpanded: () => !!currentExpanded()[rId],
185
+ toggleExpanded: (val?: boolean) => {
186
+ const nextVal = val !== undefined ? val : !currentExpanded()[rId]
187
+ updateExpanded({ ...currentExpanded(), [rId]: nextVal })
188
+ },
189
+ getToggleExpandedHandler: () => (e?: Event) => {
190
+ e?.stopPropagation?.()
191
+ const nextVal = !currentExpanded()[rId]
192
+ updateExpanded({ ...currentExpanded(), [rId]: nextVal })
193
+ },
194
+ getToggleSelectedHandler: () => (e?: Event) => {
195
+ e?.stopPropagation?.()
196
+ const nextVal = !currentRowSelection()[rId]
197
+ updateRowSelection({ ...currentRowSelection(), [rId]: nextVal })
198
+ },
199
+ getIsPinned: () => {
200
+ const pin = currentRowPinning()
201
+ if (pin.top?.includes(rId)) return "top"
202
+ if (pin.bottom?.includes(rId)) return "bottom"
203
+ return false
204
+ },
205
+ pin: (position: boolean | "top" | "bottom") => {
206
+ const current = currentRowPinning()
207
+ const nextTop = (current.top || []).filter((id) => id !== rId)
208
+ const nextBottom = (current.bottom || []).filter((id) => id !== rId)
209
+ if (position === "top") nextTop.push(rId)
210
+ else if (position === "bottom") nextBottom.push(rId)
211
+ updateRowPinning({ top: nextTop, bottom: nextBottom })
212
+ },
213
+ getIsGrouped: () => false,
214
+ getCanExpand: () => !!(subRowsList && subRowsList.length > 0) || !!props.expandedContent,
215
+ ...(subRowsList ? { subRows: subRowsList } : {})
216
+ }
217
+ return rowObj
218
+ })
219
+ }
220
+
221
+ // ─── Filter pipeline ─────────────────────────────────────────────────────────
222
+ const filteredRows = createMemo(() => {
223
+ let rows = buildRowTree(props.data ?? [])
224
+ const q = currentGlobalFilter().toLowerCase().trim()
225
+ if (q) {
226
+ rows = rows.filter((r) => {
227
+ if (typeof r.original !== "object" || !r.original) {
228
+ return String(r.original).toLowerCase().includes(q)
229
+ }
230
+ return Object.values(r.original as AnyData).some((val) =>
231
+ String(val ?? "")
232
+ .toLowerCase()
233
+ .includes(q)
234
+ )
235
+ })
236
+ }
237
+
238
+ const colFilters = currentColumnFilters()
239
+ if (colFilters && colFilters.length > 0) {
240
+ for (const filter of colFilters) {
241
+ if (
242
+ !filter.id ||
243
+ filter.value === undefined ||
244
+ filter.value === null ||
245
+ filter.value === ""
246
+ )
247
+ continue
248
+ const fVal = String(filter.value).toLowerCase()
249
+ rows = rows.filter((r) =>
250
+ String(r.getValue(filter.id) ?? "")
251
+ .toLowerCase()
252
+ .includes(fVal)
253
+ )
254
+ }
255
+ }
256
+ return rows
257
+ })
258
+
259
+ // ─── Sort pipeline ───────────────────────────────────────────────────────────
260
+ const sortedRows = createMemo(() => {
261
+ const sorts = currentSorting()
262
+ if (!sorts || sorts.length === 0) return filteredRows()
263
+
264
+ return [...filteredRows()].sort((a, b) => {
265
+ for (const { id, desc } of sorts) {
266
+ const valA = a.getValue(id)
267
+ const valB = b.getValue(id)
268
+ if (valA === valB) continue
269
+ if (valA === undefined || valA === null) return 1
270
+ if (valB === undefined || valB === null) return -1
271
+ let cmp: number
272
+ if (typeof valA === "number" && typeof valB === "number") {
273
+ cmp = valA - valB
274
+ } else {
275
+ cmp = String(valA).localeCompare(String(valB))
276
+ }
277
+ return desc ? -cmp : cmp
278
+ }
279
+ return 0
280
+ })
281
+ })
282
+
283
+ // ─── Grouping pipeline ───────────────────────────────────────────────────────
284
+ type GroupedItem = { type: "group"; group: TableGroup<T> } | { type: "row"; row: TableRow<T> }
285
+
286
+ const groupedItems = createMemo<GroupedItem[]>(() => {
287
+ const sRows = sortedRows()
288
+ const grp = props.grouping
289
+ if (!grp || grp.length === 0) {
290
+ return sRows.map((r) => ({ type: "row", row: r }))
291
+ }
292
+
293
+ const groupColId = grp[0]!
294
+ const groupMap = new Map<string, TableRow<T>[]>()
295
+ const groupOrder: string[] = []
296
+
297
+ for (const row of sRows) {
298
+ const val = String(row.getValue(groupColId) ?? "")
299
+ if (!groupMap.has(val)) {
300
+ groupMap.set(val, [])
301
+ groupOrder.push(val)
302
+ }
303
+ groupMap.get(val)!.push(row)
304
+ }
305
+
306
+ const result: GroupedItem[] = []
307
+ for (const val of groupOrder) {
308
+ const rows = groupMap.get(val)!
309
+ const group: TableGroup<T> = {
310
+ id: `group-${groupColId}-${val}`,
311
+ columnId: groupColId,
312
+ value: val,
313
+ rows
314
+ }
315
+ result.push({ type: "group", group })
316
+ for (const row of rows) {
317
+ result.push({ type: "row", row })
318
+ }
319
+ }
320
+ return result
321
+ })
322
+
323
+ // ─── Row Pinning ─────────────────────────────────────────────────────────────
324
+ const pinnedItems = createMemo<GroupedItem[]>(() => {
325
+ const items = groupedItems()
326
+ const pin = currentRowPinning()
327
+ if ((!pin.top || pin.top.length === 0) && (!pin.bottom || pin.bottom.length === 0)) {
328
+ return items
329
+ }
330
+
331
+ const topItems: GroupedItem[] = []
332
+ const midItems: GroupedItem[] = []
333
+ const botItems: GroupedItem[] = []
334
+
335
+ for (const item of items) {
336
+ if (item.type === "row") {
337
+ if (pin.top?.includes(item.row.id)) topItems.push(item)
338
+ else if (pin.bottom?.includes(item.row.id)) botItems.push(item)
339
+ else midItems.push(item)
340
+ } else {
341
+ midItems.push(item)
342
+ }
343
+ }
344
+ return [...topItems, ...midItems, ...botItems]
345
+ })
346
+
347
+ // ─── Pagination ──────────────────────────────────────────────────────────────
348
+ const paginatedItems = createMemo<GroupedItem[]>(() => {
349
+ const items = pinnedItems()
350
+ const pag = currentPagination()
351
+ if (!pag) return items
352
+
353
+ const { pageIndex, pageSize } = pag
354
+ const rowItems = items.filter((i) => i.type === "row") as { type: "row"; row: TableRow<T> }[]
355
+ const pageRows = rowItems.slice(pageIndex * pageSize, (pageIndex + 1) * pageSize)
356
+ const pageRowIds = new Set(pageRows.map((i) => i.row.id))
357
+
358
+ if (props.grouping && props.grouping.length > 0) {
359
+ const result: GroupedItem[] = []
360
+ for (const item of items) {
361
+ if (item.type === "group") {
362
+ if (item.group.rows.some((r) => pageRowIds.has(r.id))) result.push(item)
363
+ } else if (pageRowIds.has(item.row.id)) {
364
+ result.push(item)
365
+ }
366
+ }
367
+ return result
368
+ }
369
+ return pageRows
370
+ })
371
+
372
+ // ─── Column offsets ──────────────────────────────────────────────────────────
373
+ const columnOffsets = createMemo(() => {
374
+ const cols = resolvedColumns()
375
+ const pin = currentColumnPinning()
376
+ const left: Record<string, number> = {}
377
+ const right: Record<string, number> = {}
378
+
379
+ if (pin.left && pin.left.length > 0) {
380
+ let offset = 0
381
+ for (const colId of pin.left) {
382
+ left[colId] = offset
383
+ const col = cols.find((c) => (c.id || c.accessorKey) === colId)
384
+ offset += typeof col?.size === "number" ? col.size : 150
385
+ }
386
+ }
387
+
388
+ if (pin.right && pin.right.length > 0) {
389
+ let offset = 0
390
+ for (const colId of [...pin.right].reverse()) {
391
+ right[colId] = offset
392
+ const col = cols.find((c) => (c.id || c.accessorKey) === colId)
393
+ offset += typeof col?.size === "number" ? col.size : 150
394
+ }
395
+ }
396
+
397
+ return { left, right }
398
+ })
399
+
400
+ const toggleSort = (colId: string, isMulti = false) => {
401
+ const current = currentSorting()
402
+ const existing = current.find((s) => s.id === colId)
403
+
404
+ let next: SortingState
405
+ if (!existing) {
406
+ const newItem = { id: colId, desc: false }
407
+ next = isMulti ? [...current, newItem] : [newItem]
408
+ } else if (!existing.desc) {
409
+ const updated = { id: colId, desc: true }
410
+ next = isMulti ? current.map((s) => (s.id === colId ? updated : s)) : [updated]
411
+ } else {
412
+ next = isMulti ? current.filter((s) => s.id !== colId) : []
413
+ }
414
+ updateSorting(next)
415
+ }
416
+
417
+ // ─── Expose TableApi ─────────────────────────────────────────────────────────
418
+ const tableApi: TableApi<T> = {
419
+ get tableRef() {
420
+ return tableElementRef
421
+ },
422
+ getFilteredRows: () => filteredRows(),
423
+ getSortedRows: () => sortedRows(),
424
+ getSelectedRows: () => filteredRows().filter((r) => r.getIsSelected()),
425
+ getAllColumns: () => resolvedColumns(),
426
+ getColumn: (id: string) => resolvedColumns().find((c) => (c.id || c.accessorKey) === id),
427
+ setGlobalFilter: (val: string) => updateGlobalFilter(val),
428
+ setColumnFilter: (id: string, val: any) => {
429
+ const current = currentColumnFilters().filter((f) => f.id !== id)
430
+ if (val !== undefined && val !== null && val !== "") {
431
+ current.push({ id, value: val })
432
+ }
433
+ updateColumnFilters(current)
434
+ },
435
+ setColumnVisibility: (id: string, visible: boolean) => {
436
+ updateColumnVisibility({ ...currentColumnVisibility(), [id]: visible })
437
+ },
438
+ setColumnPinning: (pinning: TableColumnPinningState) => updateColumnPinning(pinning),
439
+ setRowPinning: (pinning: TableRowPinningState) => updateRowPinning(pinning),
440
+ setSorting: (st: SortingState) => updateSorting(st),
441
+ setPagination: (pag: TablePaginationState) => updatePagination(pag),
442
+ getState: () => ({
443
+ sorting: currentSorting(),
444
+ globalFilter: currentGlobalFilter(),
445
+ columnFilters: currentColumnFilters(),
446
+ columnVisibility: currentColumnVisibility(),
447
+ columnPinning: currentColumnPinning(),
448
+ rowSelection: currentRowSelection(),
449
+ expanded: currentExpanded(),
450
+ rowPinning: currentRowPinning(),
451
+ pagination: currentPagination() || { pageIndex: 0, pageSize: 10 }
452
+ })
453
+ }
454
+
455
+ if (typeof props.ref === "function") {
456
+ props.ref(tableApi)
457
+ }
458
+
459
+ // ─── Theme Resolution ────────────────────────────────────────────────────────
460
+ const classes = createMemo(() =>
461
+ tableTheme({
462
+ sticky: props.sticky,
463
+ loading: props.loading,
464
+ loadingColor: props.loadingColor ?? "primary",
465
+ loadingAnimation: props.loadingAnimation ?? "carousel",
466
+ striped: props.striped,
467
+ bordered: props.bordered,
468
+ hoverable: props.hoverable,
469
+ ui: props.ui,
470
+ class: props.class,
471
+ externalScroll: false
472
+ })
473
+ )
474
+
475
+ const showToolbar = createMemo(
476
+ () => props.searchable || props.showColumnsToggle || !!props.toolbar
477
+ )
478
+
479
+ const columnDropdownItems = createMemo(() =>
480
+ resolvedColumns()
481
+ .filter((col) => col.enableHiding !== false)
482
+ .map((col) => {
483
+ const colId = (col.id || col.accessorKey || "") as string
484
+ const rawLabel = typeof col.header === "string" ? col.header : colId
485
+ return {
486
+ id: colId,
487
+ label: rawLabel.charAt(0).toUpperCase() + rawLabel.slice(1),
488
+ type: "checkbox" as const,
489
+ checked: currentColumnVisibility()[colId] !== false,
490
+ onUpdateChecked: (checked: boolean) => {
491
+ tableApi.setColumnVisibility(colId, checked)
492
+ }
493
+ }
494
+ })
495
+ )
496
+
497
+ const hasFooters = createMemo(() =>
498
+ visibleColumns().some((col) => typeof col.footer === "function" || col.footer)
499
+ )
500
+
501
+ return (
502
+ <div class={classes().root} data-slot="root">
503
+ <Show when={showToolbar()}>
504
+ <div
505
+ class="flex items-center gap-3 px-3.5 py-3 border-b border-default bg-muted/20 overflow-x-auto"
506
+ data-slot="toolbar"
507
+ >
508
+ <Show when={props.searchable}>
509
+ <RLSInput
510
+ type="search"
511
+ placeholder={props.searchPlaceholder ?? "Filter table..."}
512
+ leadingIcon="i-lucide-search"
513
+ class="max-w-xs min-w-[10ch] w-full"
514
+ value={currentGlobalFilter()}
515
+ onInput={(e: any) => updateGlobalFilter(e.target.value)}
516
+ />
517
+ </Show>
518
+
519
+ {props.toolbar?.()}
520
+
521
+ <Show when={props.showColumnsToggle}>
522
+ <div class="ml-auto shrink-0">
523
+ <RLSDropdownMenu
524
+ label="Columns"
525
+ icon="i-lucide-columns"
526
+ items={columnDropdownItems()}
527
+ content={{ align: "end" }}
528
+ size="sm"
529
+ color="neutral"
530
+ />
531
+ </div>
532
+ </Show>
533
+ </div>
534
+ </Show>
535
+
536
+ <div class="overflow-x-auto w-full">
537
+ <table ref={(el) => (tableElementRef = el)} class={classes().base} data-slot="base">
538
+ <Show when={props.caption || props.captionContent}>
539
+ <caption class={classes().caption} data-slot="caption">
540
+ {props.captionContent ? props.captionContent() : props.caption}
541
+ </caption>
542
+ </Show>
543
+
544
+ {/* ── thead ── */}
545
+ <Show when={visibleColumns().length > 0}>
546
+ <thead class={classes().thead} data-slot="thead">
547
+ <tr class={classes().tr} data-slot="tr">
548
+ <For each={visibleColumns()}>
549
+ {(col) => {
550
+ const colId = () => (col.id || col.accessorKey || "") as string
551
+ const pinned = () =>
552
+ colId() in columnOffsets().left || colId() in columnOffsets().right
553
+ const pinStyle = () => {
554
+ if (colId() in columnOffsets().left) {
555
+ return `left: ${columnOffsets().left[colId()]}px;`
556
+ }
557
+ if (colId() in columnOffsets().right) {
558
+ return `right: ${columnOffsets().right[colId()]}px;`
559
+ }
560
+ return ""
561
+ }
562
+ const metaThClass = () =>
563
+ typeof col.meta?.class?.th === "function"
564
+ ? col.meta.class.th(col)
565
+ : col.meta?.class?.th || ""
566
+ const metaThStyle = () =>
567
+ typeof col.meta?.style?.th === "function"
568
+ ? col.meta.style.th(col)
569
+ : col.meta?.style?.th || ""
570
+ const canSort = () =>
571
+ col.enableSorting !== false &&
572
+ !!(col.accessorKey || col.id) &&
573
+ colId() !== "actions" &&
574
+ colId() !== "select"
575
+
576
+ const activeSort = () => currentSorting().find((s) => s.id === colId())
577
+ const sortDir = () =>
578
+ activeSort() ? (activeSort()!.desc ? "desc" : "asc") : "none"
579
+
580
+ const sizeStyle = () =>
581
+ col.size
582
+ ? `width: ${typeof col.size === "number" ? `${col.size}px` : col.size};`
583
+ : ""
584
+
585
+ return (
586
+ <th
587
+ scope="col"
588
+ class={`${classes().th} ${pinned() ? "sticky bg-default z-10" : ""} ${metaThClass()}`}
589
+ style={`${sizeStyle()} ${pinStyle()} ${metaThStyle()}`}
590
+ data-col-id={colId()}
591
+ data-slot="th"
592
+ >
593
+ <Show
594
+ when={canSort()}
595
+ fallback={
596
+ typeof col.header === "function"
597
+ ? col.header({ column: col, table: tableApi })
598
+ : col.header || colId()
599
+ }
600
+ >
601
+ <button
602
+ type="button"
603
+ class="inline-flex items-center gap-1.5 hover:text-highlighted focus:outline-none transition-colors cursor-pointer group -mx-1 px-1 py-0.5 rounded"
604
+ onClick={(e) => toggleSort(colId(), e.shiftKey)}
605
+ aria-label={`Sort by ${colId()}`}
606
+ >
607
+ <span>
608
+ {typeof col.header === "function"
609
+ ? col.header({ column: col, table: tableApi })
610
+ : col.header || colId()}
611
+ </span>
612
+ <span
613
+ class={`size-3.5 transition-all shrink-0 ${
614
+ sortDir() === "asc"
615
+ ? "i-lucide-arrow-up-narrow-wide text-primary"
616
+ : sortDir() === "desc"
617
+ ? "i-lucide-arrow-down-wide-narrow text-primary"
618
+ : "i-lucide-arrow-up-down opacity-40 group-hover:opacity-100"
619
+ }`}
620
+ />
621
+ </button>
622
+ </Show>
623
+ </th>
624
+ )
625
+ }}
626
+ </For>
627
+ </tr>
628
+ </thead>
629
+ </Show>
630
+
631
+ {/* ── tbody ── */}
632
+ <tbody class={classes().tbody} data-slot="tbody">
633
+ {props.bodyTop?.()}
634
+
635
+ <Show
636
+ when={!props.loading}
637
+ fallback={
638
+ <tr>
639
+ <td
640
+ colSpan={Math.max(1, visibleColumns().length)}
641
+ class={classes().loading}
642
+ data-slot="loading"
643
+ >
644
+ <Show
645
+ when={props.loadingContent}
646
+ fallback={
647
+ <div class="flex items-center justify-center gap-2 text-muted py-4">
648
+ <RLSIcon
649
+ name="i-lucide-loader-2"
650
+ class="size-5 animate-spin text-primary"
651
+ />
652
+ <span>Loading...</span>
653
+ </div>
654
+ }
655
+ >
656
+ {props.loadingContent?.()}
657
+ </Show>
658
+ </td>
659
+ </tr>
660
+ }
661
+ >
662
+ <Show
663
+ when={paginatedItems().length > 0}
664
+ fallback={
665
+ <tr>
666
+ <td
667
+ colSpan={Math.max(1, visibleColumns().length)}
668
+ class={classes().empty}
669
+ data-slot="empty"
670
+ >
671
+ <Show
672
+ when={props.emptyContent}
673
+ fallback={
674
+ <div class="flex flex-col items-center justify-center py-6 gap-2 text-muted">
675
+ <RLSIcon name="i-lucide-inbox" class="size-8 opacity-40" />
676
+ <p class="text-sm font-medium">{props.empty ?? "No data available"}</p>
677
+ </div>
678
+ }
679
+ >
680
+ {props.emptyContent?.()}
681
+ </Show>
682
+ </td>
683
+ </tr>
684
+ }
685
+ >
686
+ <For each={paginatedItems()}>
687
+ {(item) => {
688
+ if (item.type === "group") {
689
+ const { group } = item
690
+ return (
691
+ <tr class={classes().trGroup} data-slot="tr-group">
692
+ <td
693
+ colSpan={Math.max(1, visibleColumns().length)}
694
+ class={classes().tdGroup}
695
+ data-slot="td-group"
696
+ >
697
+ <Show
698
+ when={props.groupHeader}
699
+ fallback={
700
+ <span class="flex items-center gap-2">
701
+ <span class="i-lucide-layers size-3.5 text-primary opacity-70" />
702
+ <span class="text-highlighted">{String(group.value)}</span>
703
+ <span class="ml-auto text-muted font-normal normal-case">
704
+ {group.rows.length} {group.rows.length === 1 ? "row" : "rows"}
705
+ </span>
706
+ </span>
707
+ }
708
+ >
709
+ {props.groupHeader?.(group, visibleColumns().length)}
710
+ </Show>
711
+ </td>
712
+ </tr>
713
+ )
714
+ }
715
+
716
+ const { row } = item
717
+ const trMetaClass = () =>
718
+ typeof props.meta?.class?.tr === "function"
719
+ ? props.meta.class.tr(row)
720
+ : props.meta?.class?.tr || ""
721
+ const trMetaStyle = () =>
722
+ typeof props.meta?.style?.tr === "function"
723
+ ? props.meta.style.tr(row)
724
+ : props.meta?.style?.tr || ""
725
+
726
+ return (
727
+ <>
728
+ <tr
729
+ class={`${classes().tr} ${trMetaClass()}`}
730
+ style={trMetaStyle() || undefined}
731
+ data-slot="tr"
732
+ data-row-id={row.id}
733
+ data-selected={row.getIsSelected() ? "true" : undefined}
734
+ data-expanded={row.getIsExpanded() ? "true" : undefined}
735
+ data-pinned={row.getIsPinned() || undefined}
736
+ onClick={(e) => {
737
+ const target = e.target as HTMLElement
738
+ if (target.closest("a, button, input, select, textarea, [role=button]"))
739
+ return
740
+ props.onSelect?.(e, row)
741
+ }}
742
+ onMouseEnter={(e) => props.onHover?.(e, row)}
743
+ onMouseLeave={(e) => props.onHover?.(e, null)}
744
+ onContextMenu={(e) => props.onContextmenu?.(e, row)}
745
+ >
746
+ <For each={visibleColumns()}>
747
+ {(col) => {
748
+ const colId = () => (col.id || col.accessorKey || "") as string
749
+ const accessorKey = () => (col.accessorKey || col.id || "") as string
750
+ const cellValue = () =>
751
+ accessorKey() ? row.getValue(accessorKey()) : undefined
752
+ const pinned = () =>
753
+ colId() in columnOffsets().left || colId() in columnOffsets().right
754
+
755
+ const metaTdClass = () =>
756
+ typeof col.meta?.class?.td === "function"
757
+ ? col.meta.class.td(row)
758
+ : col.meta?.class?.td || ""
759
+ const metaTdStyle = () =>
760
+ typeof col.meta?.style?.td === "function"
761
+ ? col.meta.style.td(row)
762
+ : col.meta?.style?.td || ""
763
+ const colSpanVal = () =>
764
+ typeof col.meta?.colspan?.td === "function"
765
+ ? col.meta.colspan.td(row)
766
+ : col.meta?.colspan?.td
767
+ const rowSpanVal = () =>
768
+ typeof col.meta?.rowspan?.td === "function"
769
+ ? col.meta.rowspan.td(row)
770
+ : col.meta?.rowspan?.td
771
+
772
+ const pinStyle = () => {
773
+ if (colId() in columnOffsets().left) {
774
+ return `left: ${columnOffsets().left[colId()]}px;`
775
+ }
776
+ if (colId() in columnOffsets().right) {
777
+ return `right: ${columnOffsets().right[colId()]}px;`
778
+ }
779
+ return ""
780
+ }
781
+
782
+ return (
783
+ <td
784
+ class={`${classes().td} ${pinned() ? "sticky bg-default z-10" : ""} ${metaTdClass()}`}
785
+ style={`${pinStyle()} ${metaTdStyle()}`}
786
+ colSpan={colSpanVal()}
787
+ rowSpan={rowSpanVal()}
788
+ data-slot="td"
789
+ data-col-id={colId()}
790
+ >
791
+ {typeof col.cell === "function"
792
+ ? col.cell({
793
+ row,
794
+ getValue: cellValue,
795
+ renderValue: cellValue,
796
+ column: col,
797
+ table: tableApi
798
+ })
799
+ : cellValue() !== undefined && cellValue() !== null
800
+ ? String(cellValue())
801
+ : "—"}
802
+ </td>
803
+ )
804
+ }}
805
+ </For>
806
+ </tr>
807
+
808
+ {/* Expanded sub-row content */}
809
+ <Show when={row.getIsExpanded() && props.expandedContent}>
810
+ <tr class={classes().trExpanded} data-slot="tr-expanded">
811
+ <td
812
+ colSpan={Math.max(1, visibleColumns().length)}
813
+ class={classes().tdExpanded}
814
+ data-slot="td-expanded"
815
+ >
816
+ {props.expandedContent?.(row)}
817
+ </td>
818
+ </tr>
819
+ </Show>
820
+
821
+ {/* Tree / Sub-rows */}
822
+ <Show when={row.getIsExpanded() && row.subRows && row.subRows.length > 0}>
823
+ <For each={row.subRows}>
824
+ {(subRow) => (
825
+ <tr
826
+ class={classes().tr}
827
+ data-slot="tr"
828
+ data-row-id={subRow.id}
829
+ data-depth={subRow.depth}
830
+ >
831
+ <For each={visibleColumns()}>
832
+ {(col) => {
833
+ const colId = () => (col.id || col.accessorKey || "") as string
834
+ const accessorKey = () =>
835
+ (col.accessorKey || col.id || "") as string
836
+ const cellValue = () =>
837
+ accessorKey() ? subRow.getValue(accessorKey()) : undefined
838
+ return (
839
+ <td class={classes().td} data-slot="td" data-col-id={colId()}>
840
+ {typeof col.cell === "function"
841
+ ? col.cell({
842
+ row: subRow,
843
+ getValue: cellValue,
844
+ renderValue: cellValue,
845
+ column: col,
846
+ table: tableApi
847
+ })
848
+ : cellValue() !== undefined && cellValue() !== null
849
+ ? String(cellValue())
850
+ : "—"}
851
+ </td>
852
+ )
853
+ }}
854
+ </For>
855
+ </tr>
856
+ )}
857
+ </For>
858
+ </Show>
859
+ </>
860
+ )
861
+ }}
862
+ </For>
863
+ </Show>
864
+ </Show>
865
+
866
+ {props.bodyBottom?.()}
867
+ </tbody>
868
+
869
+ {/* ── tfoot ── */}
870
+ <Show when={hasFooters()}>
871
+ <tfoot class={classes().tfoot} data-slot="tfoot">
872
+ <tr class={classes().tr} data-slot="tr">
873
+ <For each={visibleColumns()}>
874
+ {(col) => {
875
+ return (
876
+ <td class={classes().td} data-slot="td">
877
+ {typeof col.footer === "function"
878
+ ? col.footer({ column: col, table: tableApi })
879
+ : col.footer || ""}
880
+ </td>
881
+ )
882
+ }}
883
+ </For>
884
+ </tr>
885
+ </tfoot>
886
+ </Show>
887
+ </table>
888
+ </div>
889
+ </div>
890
+ )
891
+ }
892
+
893
+ export default RLSTable