@navette-js/tables 0.0.0-stage → 1.0.0-beta.2

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.
@@ -0,0 +1,790 @@
1
+ <%# Table: header, toolbar, filters, content (rows or layout cards), summaries and pagination. %>
2
+ <% const selectPageBinding = `
3
+ const recordsOnPage = getRecordsOnPage()
4
+
5
+ if (recordsOnPage.length && areRecordsSelected(recordsOnPage)) {
6
+ $el.checked = true
7
+ $el.indeterminate = false
8
+
9
+ return 'checked'
10
+ }
11
+
12
+ $el.checked = false
13
+ $el.indeterminate =
14
+ recordsOnPage.length && areRecordsPartiallySelected(recordsOnPage)
15
+
16
+ return null
17
+ ` %>
18
+ <div x-data="filamentTable({
19
+ areGroupsCollapsedByDefault: <%- grouping.collapsedByDefault ? 'true' : 'false' %>,
20
+ canTrackDeselectedRecords: true,
21
+ currentSelectionLivewireProperty: null,
22
+ maxSelectableRecords: null,
23
+ reorderMessage: <%- js(t('filament-tables::table.reordering.moved')) %>,
24
+ selectsCurrentPageOnly: false,
25
+ })" class="fi-ta">
26
+ <input type="hidden" value="<%- allCount %>" x-ref="allSelectableRecordsCount" />
27
+
28
+ <% if (reorder) { %><span wire:ignore x-ref="tableReorderStatus" role="status" aria-live="polite" aria-atomic="true" class="fi-sr-only"></span><% } %>
29
+
30
+ <div class="fi-ta-ctn<%- contentLayout ? ' fi-ta-ctn-with-content-layout' : '' %> fi-ta-ctn-with-footer fi-ta-ctn-with-header">
31
+ <div class="fi-ta-main">
32
+ <%# The toolbar is always visible only when it has content (search, filters, columns); otherwise only while records are selected. %>
33
+ <% const filtersAbove = filtersForm !== null && filtersLayout === 'above-content-collapsible'; %>
34
+ <% const toolbarVisible = reorder !== null || groups.length > 0 || isSearchable || filtersForm !== null || hasColumnManager; %>
35
+ <div x-show="<%- heading !== null || headerActions.length || toolbarVisible ? 'true' : 'false' %> || false || (getSelectedRecordsCount() && <%- bulkActions.length ? 1 : 0 %>)" wire:key="<%= id %>.table.header.visible" class="fi-ta-header-ctn">
36
+ <%- hook('tables::header.before') %>
37
+ <% if (heading !== null || headerActions.length) { %>
38
+ <div class="<%- cls(['fi-ta-header', { 'fi-ta-header-adaptive-actions-position': headerActions.length }]) %>">
39
+ <% if (heading !== null) { %>
40
+ <div>
41
+ <h2 class="fi-ta-header-heading">
42
+ <%= heading %>
43
+ </h2>
44
+ </div>
45
+ <% } %>
46
+ <% if (headerActions.length) { %>
47
+ <div class="fi-ta-actions fi-align-start fi-wrapped">
48
+ <%- headerActions.join('') %>
49
+ </div>
50
+ <% } %>
51
+ </div>
52
+ <% } %>
53
+ <%- hook('tables::header.after') %>
54
+ <% if (filtersAbove) { %>
55
+ <%# Collapsible filters above the content. %>
56
+ <div x-bind:class="{ 'fi-open': areFiltersOpen }" class="fi-ta-filters-above-content-ctn">
57
+ <div class="fi-ta-filters" x-cloak="x-cloak" x-show="areFiltersOpen">
58
+ <div class="fi-ta-filters-header">
59
+ <h2 class="fi-ta-filters-heading">
60
+ <%= t('filament-tables::table.filters.heading') %>
61
+ </h2>
62
+
63
+ <div>
64
+ <button type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="<%- cls(['fi-ac-link-action', 'fi-link', 'fi-size-md', ...dangerLinkClasses]) %>" data-table-filter-action="reset" wire:click="resetTableFiltersForm">
65
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'resetTableFiltersForm' })) %>
66
+ <span class="fi-link-label">
67
+ <%= t('filament-tables::table.filters.actions.reset.label') %>
68
+ </span>
69
+ </button>
70
+ </div>
71
+ </div>
72
+
73
+ <%- filtersForm %>
74
+
75
+ <div class="fi-ta-filters-actions-ctn">
76
+ <button type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="<%- cls(['fi-ac-btn-action', 'fi-btn', 'fi-size-md', ...primaryButtonClasses]) %>" data-table-filter-action="apply" wire:click="applyTableFilters">
77
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'applyTableFilters' })) %>
78
+ <%= t('filament-tables::table.filters.actions.apply.label') %>
79
+ </button>
80
+ </div>
81
+ </div>
82
+
83
+ <span x-on:click="areFiltersOpen = ! areFiltersOpen" class="fi-ta-filters-trigger-action-ctn">
84
+ <button title="<%= t('filament-tables::table.actions.filter.label') %>" aria-label="<%= t('filament-tables::table.actions.filter.label') %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="fi-force-enabled fi-ac-icon-btn-action fi-icon-btn fi-size-md">
85
+ <%- icon('m-funnel') %>
86
+ <div class="fi-icon-btn-badge-ctn">
87
+ <span class="<%- cls([...badgeClasses, 'fi-badge', 'fi-size-xs']) %>">
88
+ <%- activeFiltersCount %>
89
+ </span>
90
+ </div>
91
+ </button>
92
+ </span>
93
+ </div>
94
+ <% } %>
95
+ <%- hook('tables::toolbar.before') %>
96
+ <div<% if (!toolbarVisible) { %> x-cloak<% } %> x-show="<%- toolbarVisible ? 'true' : 'false' %> || false || (getSelectedRecordsCount() && <%- bulkActions.length ? 1 : 0 %>)" wire:key="<%= id %>.table.header-toolbar.<%- toolbarVisible ? 'visible' : 'selection' %>" class="fi-ta-header-toolbar">
97
+ <%- hook('tables::toolbar.start') %>
98
+ <div class="fi-ta-actions fi-align-start fi-wrapped">
99
+ <%- hook('tables::toolbar.reorder-trigger.before') %>
100
+ <% if (reorder) { %>
101
+ <% const reorderLabel = t(reorder.active ? 'filament-tables::table.actions.disable_reordering.label' : 'filament-tables::table.actions.enable_reordering.label') %>
102
+ <button title="<%= reorderLabel %>" aria-label="<%= reorderLabel %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" wire:click="toggleTableReordering" class="fi-force-enabled fi-ac-icon-btn-action fi-icon-btn fi-size-md">
103
+ <%- icon(reorder.active ? 'm-check' : 'm-arrows-up-down', { attributes: attrs({ 'wire:loading.remove.delay.default': '1', 'wire:target': 'toggleTableReordering' }) }) %>
104
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'toggleTableReordering' })) %>
105
+ </button>
106
+ <% } %>
107
+ <%- hook('tables::toolbar.reorder-trigger.after') %>
108
+ <% if (bulkActions.length) { %>
109
+ <div x-data="filamentDropdown" x-cloak="x-cloak" x-show="getSelectedRecordsCount()" class="fi-dropdown">
110
+ <div x-on:mousedown="if ($event.button === 0) toggle($event)" class="fi-dropdown-trigger">
111
+ <button title="<%= t('filament-tables::table.actions.open_bulk_actions.label') %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="fi-ac-btn-group fi-icon-btn fi-size-md">
112
+ <%- icon('m-ellipsis-vertical') %>
113
+ </button>
114
+ <button type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="fi-ac-btn-group fi-btn fi-size-md fi-labeled-from-sm">
115
+ <%- icon('m-ellipsis-vertical') %>
116
+ <%= t('filament-tables::table.actions.open_bulk_actions.label') %>
117
+ </button>
118
+ </div>
119
+
120
+ <div x-cloak x-float.placement.bottom-start.flip.offset="{ offset: 8 }" x-ref="panel" x-transition:enter-start="fi-opacity-0" x-transition:leave-end="fi-opacity-0" role="menu" class="fi-dropdown-panel">
121
+ <div class="fi-dropdown-list" role="group">
122
+ <%- bulkActions.join('') %>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ <% } %>
127
+ <%- hook('tables::toolbar.grouping-selector.before') %>
128
+ <% if (groups.length) { %>
129
+ <% const groupLabel = t('filament-tables::table.grouping.fields.group.label'); const directionLabel = t('filament-tables::table.grouping.fields.direction.label'); %>
130
+ <div x-data="{
131
+ grouping: $wire.$entangle('tableGrouping', true),
132
+ group: null,
133
+ direction: null,
134
+ }" x-init="
135
+ if (grouping) {
136
+ ;[group, direction] = grouping.split(':')
137
+ direction ??= 'asc'
138
+ }
139
+
140
+ $watch('grouping', function () {
141
+ if (! grouping) {
142
+ group = null
143
+ direction = null
144
+
145
+ return
146
+ }
147
+
148
+ ;[group, direction] = grouping.split(':')
149
+ direction ??= 'asc'
150
+ })
151
+
152
+ $watch('direction', function () {
153
+ grouping = group ? `${group}:${direction}` : null
154
+ })
155
+
156
+ $watch('group', function (newGroup, oldGroup) {
157
+ if (! newGroup) {
158
+ direction = null
159
+ grouping = group ? `${group}:${direction}` : null
160
+
161
+ return
162
+ }
163
+
164
+ if (oldGroup) {
165
+ grouping = group ? `${group}:${direction}` : null
166
+
167
+ return
168
+ }
169
+
170
+ direction ??= 'asc'
171
+ grouping = group ? `${group}:${direction}` : null
172
+ })
173
+ " class="fi-ta-grouping-settings">
174
+ <div x-data="filamentDropdown({ isMenu: false })" class="<%- cls(['fi-dropdown', { 'sm:fi-hidden': !grouping.inDropdownOnDesktop }]) %>" wire:key="<%= id %>.table.grouping">
175
+ <div x-on:mousedown="if ($event.button === 0) toggle($event)" class="fi-dropdown-trigger">
176
+ <button title="<%= t('filament-tables::table.actions.group.label') %>" aria-label="<%= t('filament-tables::table.actions.group.label') %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="fi-force-enabled fi-ac-icon-btn-action fi-icon-btn fi-size-md">
177
+ <%- icon('m-rectangle-stack') %>
178
+ </button>
179
+ </div>
180
+ <div x-cloak x-float.placement.bottom-start.flip.shift.offset="{ offset: 8, }" x-ref="panel" x-transition:enter-start="fi-opacity-0" x-transition:leave-end="fi-opacity-0" wire:ignore.self wire:key="<%= id %>.table.grouping.panel" class="fi-dropdown-panel fi-width-xs" style="">
181
+ <div class="fi-ta-grouping-settings-fields">
182
+ <label>
183
+ <span><%= groupLabel %></span>
184
+ <div class="fi-input-wrp"><div class="fi-input-wrp-content-ctn"><select class="fi-select-input" x-model="group" x-on:change="resetCollapsedGroups()"><option value="">-</option><% for (const option of groups) { %><option value="<%= option.id %>"><%= option.label %></option><% } %></select></div></div>
185
+ </label>
186
+ <% if (!grouping.directionHidden) { %>
187
+ <label x-cloak x-show="group">
188
+ <span><%= directionLabel %></span>
189
+ <div class="fi-input-wrp"><div class="fi-input-wrp-content-ctn"><select class="fi-select-input" x-model="direction"><option value="asc"><%= t('filament-tables::table.grouping.fields.direction.options.asc') %></option><option value="desc"><%= t('filament-tables::table.grouping.fields.direction.options.desc') %></option></select></div></div>
190
+ </label>
191
+ <% } %>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ <% if (!grouping.inDropdownOnDesktop) { %>
196
+ <div class="fi-ta-grouping-settings-fields">
197
+ <label>
198
+ <div class="fi-input-wrp"><div x-on:click="if (! $event.target.closest('.fi-input-wrp-actions')) $dispatch('focus-input')" class="fi-input-wrp-prefix fi-input-wrp-prefix-has-content fi-input-wrp-prefix-has-label"><span class="fi-input-wrp-label"><%= groupLabel %></span></div><div class="fi-input-wrp-content-ctn"><select class="fi-select-input" x-model="group" x-on:change="resetCollapsedGroups()"><option value="">-</option><% for (const option of groups) { %><option value="<%= option.id %>"><%= option.label %></option><% } %></select></div></div>
199
+ </label>
200
+ <% if (!grouping.directionHidden) { %>
201
+ <label x-cloak x-show="group">
202
+ <span class="fi-sr-only"><%= directionLabel %></span>
203
+ <div class="fi-input-wrp"><div class="fi-input-wrp-content-ctn"><select class="fi-select-input" x-model="direction"><option value="asc"><%= t('filament-tables::table.grouping.fields.direction.options.asc') %></option><option value="desc"><%= t('filament-tables::table.grouping.fields.direction.options.desc') %></option></select></div></div>
204
+ </label>
205
+ <% } %>
206
+ </div>
207
+ <% } %>
208
+ </div>
209
+ <% } %>
210
+ <%- hook('tables::toolbar.grouping-selector.after') %>
211
+ </div>
212
+
213
+ <% if (toolbarVisible) { %><div>
214
+ <%- hook('tables::toolbar.search.before') %>
215
+ <% if (isSearchable) { %><div x-id="['input']" class="fi-ta-search-field">
216
+ <label x-bind:for="$id('input')" class="fi-sr-only">
217
+ <%= t('filament-tables::table.fields.search.label') %>
218
+ </label>
219
+
220
+ <div class="fi-input-wrp">
221
+ <div x-on:click="if (! $event.target.closest('.fi-input-wrp-actions')) $dispatch('focus-input')" class="fi-input-wrp-prefix fi-input-wrp-prefix-has-content fi-inline">
222
+ <%- icon('m-magnifying-glass', { attributes: attrs({ 'wire:loading.remove.delay.default': '1', 'wire:target': 'tableSearch' }) }) %>
223
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'tableSearch' })) %>
224
+ </div>
225
+
226
+ <div class="fi-input-wrp-content-ctn">
227
+ <input class="fi-input fi-input-has-inline-prefix" autocomplete="off" maxlength="1000" placeholder="<%= t('filament-tables::table.fields.search.placeholder') %>" type="search" wire:key="<%= id %>.table.tableSearch.field.input" wire:model.live.debounce.500ms="tableSearch" x-bind:id="$id('input')" x-on:keyup="if ($event.key === 'Enter') { $wire.$refresh() }" />
228
+ </div>
229
+ </div>
230
+ </div><% } %>
231
+ <%- hook('tables::toolbar.search.after') %>
232
+
233
+ <% if (filtersForm !== null && !filtersAbove) { %>
234
+ <div x-data="filamentDropdown({ isMenu: false })" class="fi-dropdown fi-ta-filters-dropdown" wire:key="<%= id %>.table.filters">
235
+ <div x-on:mousedown="if ($event.button === 0) toggle($event)" class="fi-dropdown-trigger">
236
+ <button title="<%= t('filament-tables::table.actions.filter.label') %>" aria-label="<%= t('filament-tables::table.actions.filter.label') %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="fi-force-enabled fi-ac-icon-btn-action fi-icon-btn fi-size-md">
237
+ <%- icon('m-funnel') %>
238
+ <div class="fi-icon-btn-badge-ctn">
239
+ <span class="<%- cls([...badgeClasses, 'fi-badge', 'fi-size-xs']) %>">
240
+ <%- activeFiltersCount %>
241
+ </span>
242
+ </div>
243
+ </button>
244
+ </div>
245
+
246
+ <div x-cloak x-float.placement.bottom-end.shift.offset="{ offset: 8, }" x-ref="panel" x-transition:enter-start="fi-opacity-0" x-transition:leave-end="fi-opacity-0" wire:ignore.self wire:key="<%= id %>.table.filters.panel" class="fi-dropdown-panel fi-width-xs" style="">
247
+ <div class="fi-ta-filters" x-data="filamentTableFilters" x-on:click="handleActionClick($event)" tabindex="-1">
248
+ <div class="fi-ta-filters-header">
249
+ <h2 class="fi-ta-filters-heading">
250
+ <%= t('filament-tables::table.filters.heading') %>
251
+ </h2>
252
+
253
+ <div>
254
+ <button type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="<%- cls(['fi-ac-link-action', 'fi-link', 'fi-size-md', ...dangerLinkClasses]) %>" data-table-filter-action="reset" wire:click="resetTableFiltersForm">
255
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'resetTableFiltersForm' })) %>
256
+ <span class="fi-link-label">
257
+ <%= t('filament-tables::table.filters.actions.reset.label') %>
258
+ </span>
259
+ </button>
260
+ </div>
261
+ </div>
262
+
263
+ <%- filtersForm %>
264
+
265
+ <div class="fi-ta-filters-actions-ctn">
266
+ <button type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" class="<%- cls(['fi-ac-btn-action', 'fi-btn', 'fi-size-md', ...primaryButtonClasses]) %>" data-table-filter-action="apply" wire:click="applyTableFilters">
267
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'applyTableFilters' })) %>
268
+ <%= t('filament-tables::table.filters.actions.apply.label') %>
269
+ </button>
270
+ </div>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ <% } %>
275
+
276
+ <%# These two hook slots only exist when there is a filters trigger or a column manager. %>
277
+ <% if ((filtersForm !== null && !filtersAbove) || hasColumnManager) { %><%- hook('tables::toolbar.toggle-column-trigger.before') %><% } %>
278
+ <% if (hasColumnManager) { %>
279
+ <%- view('filament-tables::column-manager', { id, dangerLinkClasses, primaryButtonClasses }) %>
280
+ <% } %>
281
+ <% if ((filtersForm !== null && !filtersAbove) || hasColumnManager) { %><%- hook('tables::toolbar.toggle-column-trigger.after') %><% } %>
282
+ </div><% } %>
283
+ <%- hook('tables::toolbar.end') %>
284
+ </div>
285
+ <%- hook('tables::toolbar.after') %>
286
+
287
+ </div>
288
+
289
+ <% if (reorder?.active) { %>
290
+ <div x-cloak role="status" aria-live="polite" wire:key="<%= id %>.table.reorder.indicator" class="fi-ta-reorder-indicator">
291
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'reorderTable,moveTableRecord' })) %>
292
+ <%= t('filament-tables::table.reorder_indicator') %>
293
+ </div>
294
+ <% } %>
295
+ <% if (bulkActions.length) { %>
296
+ <div x-cloak role="status" aria-live="polite" aria-atomic="true" x-bind:hidden="! getSelectedRecordsCount()" x-show="getSelectedRecordsCount()" wire:key="<%= id %>.table.selection.indicator" class="fi-ta-selection-indicator">
297
+ <div>
298
+ <%- loadingIndicator(attrs({ 'x-show': 'isLoading' })) %>
299
+
300
+ <span x-text="
301
+ window.pluralize(<%= js(t('filament-tables::table.selection_indicator.selected_count')) %>, getSelectedRecordsCount(), {
302
+ count: new Intl.NumberFormat(<%= js('en') %>).format(getSelectedRecordsCount()),
303
+ })
304
+ "></span>
305
+ </div>
306
+
307
+ <div>
308
+ <%- hook('tables::selection.indicator.actions.before') %>
309
+ <div class="fi-ta-selection-indicator-actions-ctn">
310
+ <button class="<%- cls([...linkClasses, 'fi-link', 'fi-size-md']) %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" x-on:click="selectAllRecords" x-show="canSelectAllRecords()" wire:key="<%= id %>table.selection.indicator.actions.select-all.<%- allCount %>.<%- page.page %>">
311
+ <span class="fi-link-label">
312
+ <%= t('filament-tables::table.selection_indicator.actions.select_all.label', { count: number(allCount) }) %>
313
+ </span>
314
+ </button>
315
+ <button class="<%- cls([...dangerLinkClasses, 'fi-link', 'fi-size-md']) %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" x-on:click="deselectAllRecords">
316
+ <span class="fi-link-label">
317
+ <%= t('filament-tables::table.selection_indicator.actions.deselect_all.label') %>
318
+ </span>
319
+ </button>
320
+ </div>
321
+ <%- hook('tables::selection.indicator.actions.after') %>
322
+ </div>
323
+ </div>
324
+ <% } %>
325
+
326
+ <%# A `tables::filter.indicators` hook that renders something replaces the default indicators. %>
327
+ <% const filterIndicatorsView = filterIndicators.length ? hook('tables::filter.indicators', { filterIndicators }) : ''; %>
328
+ <% if (filterIndicatorsView.trim() !== '') { %><%- filterIndicatorsView %><% } else if (filterIndicators.length) { %>
329
+ <%# Active filter indicators. %>
330
+ <div class="fi-ta-filter-indicators">
331
+ <div>
332
+ <span class="fi-ta-filter-indicators-label">
333
+ <%= t('filament-tables::table.filters.indicator') %>
334
+ </span>
335
+
336
+ <div class="fi-ta-filter-indicators-badges-ctn" role="list">
337
+ <% for (const indicator of filterIndicators) { %>
338
+ <% const removal = 'removeTableFilter(' + js(indicator.filter) + ', ' + js(indicator.field) + ')'; %>
339
+ <span class="<%- cls([...indicatorBadgeClasses, 'fi-badge', 'fi-size-md']) %>" role="listitem">
340
+ <span class="fi-badge-label-ctn">
341
+ <span class="fi-badge-label">
342
+ <%= indicator.label %>
343
+ </span>
344
+ </span>
345
+
346
+ <button data-prevent-disabled-clicks type="button" class="fi-badge-delete-btn" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" wire:target="removeTableFilter" wire:click="<%= removal %>">
347
+ <span aria-hidden="true" wire:loading.remove.delay.default="" wire:target="<%= removal %>" class="fi-badge-delete-btn-icon"></span>
348
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': removal }), 'xs') %>
349
+ <span class="fi-sr-only">
350
+ <%= t('filament-tables::table.filters.actions.remove.label') %>
351
+ </span>
352
+ </button>
353
+ </span>
354
+ <% } %>
355
+ </div>
356
+ </div>
357
+
358
+ <button x-tooltip="{
359
+ content: <%= js(t('filament-tables::table.filters.actions.remove_all.tooltip')) %>,
360
+ theme: $store.theme,
361
+ allowHTML: false,
362
+ }" aria-label="<%= t('filament-tables::table.filters.actions.remove_all.label') %>" type="button" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" wire:target="removeTableFilters,removeTableFilter" class="fi-ac-icon-btn-action fi-icon-btn fi-size-sm" wire:click="removeTableFilters">
363
+ <%- icon('m-x-mark', { attributes: attrs({ 'wire:loading.remove.delay.default': '1', 'wire:target': 'removeTableFilters' }) }) %>
364
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'removeTableFilters' })) %>
365
+ </button>
366
+ </div>
367
+ <% } %>
368
+
369
+ <%- hook('tables::content.before', { table, records: page.records }) %>
370
+ <div wire:loading.attr="aria-busy" wire:target="<%- targets %>" class="fi-ta-content-ctn fi-fixed-positioning-context">
371
+ <div role="status" aria-live="polite" aria-atomic="true" class="fi-sr-only">
372
+ <%= tc('filament-tables::table.result_count', resultsCount) %>
373
+ </div>
374
+
375
+ <div wire:loading.delay.default.block wire:target="<%- targets %>" role="status" aria-live="polite" data-testid="table-loading-state" class="fi-sr-only">
376
+ <%= t('filament-tables::table.loading') %>
377
+ </div>
378
+
379
+ <% if (contentLayout) { %>
380
+ <%# Column layout (Split / Stack): one card per record, sorting through a select. %>
381
+ <% if (rows.length) { %>
382
+ <% if (selectable || columns.some((c) => c.sortable)) { %>
383
+ <div class="fi-ta-content-header">
384
+ <% if (selectable) { %>
385
+ <input aria-label="<%= t('filament-tables::table.fields.bulk_select_page.label') %>" type="checkbox" x-bind:checked="<%= selectPageBinding %>" x-on:click="toggleSelectRecordsOnPage" wire:key="<%= id %>.table.bulk-select-page.checkbox.<%- randomKey() %>" wire:loading.attr="disabled" wire:target="<%- targets %>" class="fi-ta-page-checkbox fi-checkbox-input" />
386
+ <% } %>
387
+ <% if (columns.some((c) => c.sortable)) { %>
388
+ <div x-data="{
389
+ sort: $wire.$entangle('tableSort', true),
390
+ column: null,
391
+ direction: null,
392
+ }" x-init="
393
+ if (sort) {
394
+ ;[column, direction] = sort.split(':')
395
+ direction ??= 'asc'
396
+ }
397
+
398
+ $watch('sort', function () {
399
+ if (! sort) {
400
+ return
401
+ }
402
+
403
+ ;[column, direction] = sort.split(':')
404
+ direction ??= 'asc'
405
+ })
406
+
407
+ $watch('direction', function () {
408
+ sort = column ? `${column}:${direction}` : null
409
+ })
410
+
411
+ $watch('column', function (newColumn, oldColumn) {
412
+ if (! newColumn) {
413
+ direction = null
414
+ sort = column ? `${column}:${direction}` : null
415
+
416
+ return
417
+ }
418
+
419
+ if (oldColumn) {
420
+ sort = column ? `${column}:${direction}` : null
421
+
422
+ return
423
+ }
424
+
425
+ direction = 'asc'
426
+ sort = column ? `${column}:${direction}` : null
427
+ })
428
+ " class="fi-ta-sorting-settings">
429
+ <label>
430
+ <div class="fi-input-wrp">
431
+ <div x-on:click="if (! $event.target.closest('.fi-input-wrp-actions')) $dispatch('focus-input')" class="fi-input-wrp-prefix fi-input-wrp-prefix-has-content fi-input-wrp-prefix-has-label">
432
+ <span class="fi-input-wrp-label">
433
+ <%= t('filament-tables::table.sorting.fields.column.label') %>
434
+ </span>
435
+ </div>
436
+
437
+ <div class="fi-input-wrp-content-ctn">
438
+ <select class="fi-select-input" x-model="column">
439
+ <option value="">-</option>
440
+ <% for (const column of columns.filter((c) => c.sortable)) { %>
441
+ <option value="<%= column.name %>"><%= column.label %></option>
442
+ <% } %>
443
+ </select>
444
+ </div>
445
+ </div>
446
+ </label>
447
+
448
+ <label x-cloak x-show="column">
449
+ <span class="fi-sr-only">
450
+ <%= t('filament-tables::table.sorting.fields.direction.label') %>
451
+ </span>
452
+
453
+ <div class="fi-input-wrp">
454
+ <div class="fi-input-wrp-content-ctn">
455
+ <select class="fi-select-input" x-model="direction">
456
+ <option value="asc"><%= t('filament-tables::table.sorting.fields.direction.options.asc') %></option>
457
+ <option value="desc"><%= t('filament-tables::table.sorting.fields.direction.options.desc') %></option>
458
+ </select>
459
+ </div>
460
+ </div>
461
+ </label>
462
+ </div>
463
+ <% } %>
464
+ </div>
465
+ <% } %>
466
+ <div aria-label="<%= heading ?? table.getPluralModelLabel() %>" role="list" class="fi-ta-content">
467
+ <% for (const row of rows) { %>
468
+ <div role="listitem" wire:key="<%= id %>.table.records.<%= row.key %>" class="<%- cls(['fi-ta-record', { 'fi-clickable': row.url || row.mountExpression, 'fi-ta-record-with-content-prefix': selectable }, row.classes]) %>" x-bind:class="{
469
+ 'fi-selected': <%- selectable ? 'true' : 'false' %> && isRecordSelected(<%= js(row.key) %>),
470
+ }">
471
+ <% if (selectable) { %>
472
+ <input aria-label="<%= t('filament-tables::table.fields.bulk_select_record.label', { key: row.key }) %>" type="checkbox" value="<%= row.key %>" x-on:click="toggleSelectedRecord(<%= js(row.key) %>)" x-bind:checked="isRecordSelected(<%= js(row.key) %>) ? 'checked' : null" data-group="<%= row.groupKey ?? '' %>" wire:loading.attr="disabled" wire:target="<%- targets %>" class="fi-ta-record-checkbox fi-checkbox-input" />
473
+ <% } %>
474
+ <div class="fi-ta-record-content-ctn">
475
+ <div>
476
+ <% if (row.url) { %>
477
+ <a href="<%= row.url %>" x-on:click="if (! ($event.altKey || $event.ctrlKey || $event.metaKey || $event.shiftKey)) { $event.preventDefault(); Alpine.navigate($el.getAttribute('href')) }" class="fi-ta-record-content"><%- row.contentHtml %></a>
478
+ <% } else if (row.mountExpression) { %>
479
+ <button type="button" wire:click="<%= row.mountExpression %>" wire:loading.attr="disabled" wire:target="<%= row.mountExpression %>" class="fi-ta-record-content"><%- row.contentHtml %></button>
480
+ <% } else { %>
481
+ <div class="fi-ta-record-content"><%- row.contentHtml %></div>
482
+ <% } %>
483
+ </div>
484
+ <% if (hasRowActions) { %>
485
+ <div class="fi-ta-actions fi-wrapped sm:fi-not-wrapped fi-align-start md:fi-align-end">
486
+ <%- row.actionsHtml ?? '' %>
487
+ </div>
488
+ <% } %>
489
+ </div>
490
+ </div>
491
+ <% } %>
492
+ </div>
493
+ <% } %>
494
+ <% } else { %>
495
+ <table aria-label="<%= heading ?? table.getPluralModelLabel() %>" class="<%- cls(['fi-ta-table', { 'fi-ta-table-stacked-on-mobile': table.isStackedOnMobile() }]) %>">
496
+ <thead>
497
+ <% if (table.isStackedOnMobile()) { %>
498
+ <tr class="fi-ta-table-stacked-header-row">
499
+ <td colspan="100%" class="fi-ta-table-stacked-header-cell">
500
+ <%# Stacked sort controls: only when some columns are sortable. %>
501
+ <% if (columns.some((c) => c.sortable)) { %>
502
+ <div x-data="{
503
+ sort: $wire.$entangle('tableSort', true),
504
+ column: null,
505
+ direction: null,
506
+ }" x-init="
507
+ if (sort) {
508
+ ;[column, direction] = sort.split(':')
509
+ direction ??= 'asc'
510
+ }
511
+
512
+ $watch('sort', function () {
513
+ if (! sort) {
514
+ return
515
+ }
516
+
517
+ ;[column, direction] = sort.split(':')
518
+ direction ??= 'asc'
519
+ })
520
+
521
+ $watch('direction', function () {
522
+ sort = column ? `${column}:${direction}` : null
523
+ })
524
+
525
+ $watch('column', function (newColumn, oldColumn) {
526
+ if (! newColumn) {
527
+ direction = null
528
+ sort = column ? `${column}:${direction}` : null
529
+
530
+ return
531
+ }
532
+
533
+ if (oldColumn) {
534
+ sort = column ? `${column}:${direction}` : null
535
+
536
+ return
537
+ }
538
+
539
+ direction = 'asc'
540
+ sort = column ? `${column}:${direction}` : null
541
+ })
542
+ " class="fi-ta-table-stacked-sorting">
543
+ <label>
544
+ <div class="fi-input-wrp">
545
+ <div x-on:click="if (! $event.target.closest('.fi-input-wrp-actions')) $dispatch('focus-input')" class="fi-input-wrp-prefix fi-input-wrp-prefix-has-content fi-input-wrp-prefix-has-label">
546
+ <span class="fi-input-wrp-label">
547
+ <%= t('filament-tables::table.sorting.fields.column.label') %>
548
+ </span>
549
+ </div>
550
+
551
+ <div class="fi-input-wrp-content-ctn">
552
+ <select class="fi-select-input" x-model="column">
553
+ <option value="">-</option>
554
+ <% for (const column of columns.filter((c) => c.sortable)) { %>
555
+ <option value="<%= column.name %>"><%= column.label %></option>
556
+ <% } %>
557
+ </select>
558
+ </div>
559
+ </div>
560
+ </label>
561
+
562
+ <label x-cloak x-show="column">
563
+ <span class="fi-sr-only">
564
+ <%= t('filament-tables::table.sorting.fields.direction.label') %>
565
+ </span>
566
+
567
+ <div class="fi-input-wrp">
568
+ <div class="fi-input-wrp-content-ctn">
569
+ <select class="fi-select-input" x-model="direction">
570
+ <option value="asc"><%= t('filament-tables::table.sorting.fields.direction.options.asc') %></option>
571
+ <option value="desc"><%= t('filament-tables::table.sorting.fields.direction.options.desc') %></option>
572
+ </select>
573
+ </div>
574
+ </div>
575
+ </label>
576
+ </div>
577
+ <% } %>
578
+
579
+ <input aria-label="<%= t('filament-tables::table.fields.bulk_select_page.label') %>" type="checkbox" x-bind:checked="<%= selectPageBinding %>" x-on:click="toggleSelectRecordsOnPage" wire:key="<%= id %>.table.bulk-select-page.checkbox.stacked.<%- randomKey() %>" wire:loading.attr="disabled" wire:target="<%- targets %>" class="fi-ta-page-checkbox fi-checkbox-input" />
580
+ </td>
581
+ </tr>
582
+ <% } %>
583
+
584
+ <% if (headerGroups) { %>
585
+ <tr class="fi-ta-table-head-groups-row">
586
+ <% if (rows.length) { %><th></th><% } %>
587
+ <% for (const group of headerGroups) { %>
588
+ <% if (group.label === null) { %><th></th><% } else { %><th colspan="<%- group.span %>" scope="colgroup" class="fi-ta-header-group-cell"><%= group.label %></th><% } %>
589
+ <% } %>
590
+ <% if (rows.length) { %><th></th><% } %>
591
+ </tr>
592
+ <% } %>
593
+
594
+ <tr>
595
+ <% if (rows.length && reorder?.active) { %><th></th><% } %>
596
+ <% if (rows.length && !reorder?.active && selectable) { %>
597
+ <th scope="col" class="fi-ta-cell fi-ta-selection-cell">
598
+ <input aria-label="<%= t('filament-tables::table.fields.bulk_select_page.label') %>" type="checkbox" x-bind:checked="<%= selectPageBinding %>" x-on:click="toggleSelectRecordsOnPage" wire:key="<%= id %>.table.bulk-select-page.checkbox.<%- randomKey() %>" wire:loading.attr="disabled" wire:target="<%- targets %>" class="fi-ta-page-checkbox fi-checkbox-input" />
599
+ </th>
600
+ <% } %>
601
+
602
+ <%# A `tables::header.cell` hook that renders something replaces the column's header cell. %>
603
+ <% const hasHeaderCellHook = hasHook('tables::header.cell'); %>
604
+ <% for (const [columnIndex, column] of columns.entries()) { %>
605
+ <% const headerCellView = hasHeaderCellHook ? hook('tables::header.cell', { column: visibleColumns[columnIndex], isReordering }) : ''; %>
606
+ <% if (headerCellView.trim() !== '') { %><%- headerCellView %><% continue; } %>
607
+ <th<% if (column.sortable) { %> aria-sort="<%- column.sort %>"<% } %> scope="col" class="<%- cls(['fi-ta-header-cell', 'fi-ta-header-cell-' + column.cssName, { 'fi-grouped': column.grouped }, column.alignment && column.alignment !== 'start' ? 'fi-align-' + column.alignment : null]) %>">
608
+ <% if (column.sortable) { %>
609
+ <button type="button" data-prevent-disabled-clicks wire:click="sortTable(<%= js(column.name) %>)" wire:loading.attr="aria-disabled" wire:loading.class="fi-disabled" wire:target="<%- targets %>" class="fi-ta-header-cell-sort-btn">
610
+ <%= column.label %>
611
+ <%- icon(column.sort === 'ascending' ? 'm-chevron-up' : 'm-chevron-down', { attributes: attrs({ 'wire:loading.remove.delay.default': '1', 'wire:target': 'sortTable(' + js(column.name) + ')' }) }) %>
612
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'sortTable(' + js(column.name) + ')' })) %>
613
+ </button>
614
+ <% } else { %>
615
+ <%= column.label %>
616
+ <% } %>
617
+ </th>
618
+ <% } %>
619
+
620
+ <% if (rows.length && !reorder?.active && hasRowActions) { %><th aria-label="<%= tc('filament-tables::table.columns.actions.label', 2) %>" scope="col" class="fi-ta-actions-header-cell fi-ta-empty-header-cell"></th><% } %>
621
+ </tr>
622
+ </thead>
623
+
624
+ <% if (columns.some((c) => c.individuallySearchable)) { %>
625
+ <tbody>
626
+ <tr class="fi-ta-row fi-ta-row-not-reorderable fi-ta-individual-search-row">
627
+ <% if (rows.length) { %><td></td><% } %>
628
+ <% for (const column of columns) { %>
629
+ <td class="<%- cls(['fi-ta-cell', column.individuallySearchable ? 'fi-ta-individual-search-cell fi-ta-individual-search-cell-' + column.cssName : null]) %>">
630
+ <% if (column.individuallySearchable) { %>
631
+ <div x-id="['input']" class="fi-ta-search-field">
632
+ <label x-bind:for="$id('input')" class="fi-ta-cell-label">
633
+ <%= column.label %>
634
+ </label>
635
+
636
+ <div class="fi-input-wrp">
637
+ <div x-on:click="if (! $event.target.closest('.fi-input-wrp-actions')) $dispatch('focus-input')" class="fi-input-wrp-prefix fi-input-wrp-prefix-has-content fi-inline">
638
+ <%- icon('m-magnifying-glass', { attributes: attrs({ 'wire:loading.remove.delay.default': '1', 'wire:target': 'tableColumnSearches.' + column.name }) }) %>
639
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': 'tableColumnSearches.' + column.name })) %>
640
+ </div>
641
+
642
+ <div class="fi-input-wrp-content-ctn">
643
+ <input class="fi-input fi-input-has-inline-prefix" autocomplete="off" maxlength="1000" placeholder="<%= t('filament-tables::table.fields.search.placeholder') %>" type="search" wire:key="<%= id %>.table.tableColumnSearches.<%= column.name %>.field.input" wire:model.live.debounce.500ms="tableColumnSearches.<%= column.name %>" x-bind:id="$id('input')" x-on:keyup="if ($event.key === 'Enter') { $wire.$refresh() }" />
644
+ </div>
645
+ </div>
646
+ </div>
647
+ <% } %>
648
+ </td>
649
+ <% } %>
650
+ <% if (rows.length) { %><td></td><% } %>
651
+ </tr>
652
+ </tbody>
653
+ <% } %>
654
+
655
+ <% if (rows.length) { %>
656
+ <tbody<% if (reorder) { %> x-ref="tableReorderRecords" x-on:start.stop="startTableReorder($event)" x-on:end.stop="reorderTableRecords($event)" x-sortable data-sortable-animation-duration="300"<% } %> class="fi-ta-records">
657
+ <% for (const row of rows) { %>
658
+ <%- row.groupSummaryHtml %>
659
+ <% if (grouping.groupsOnly) continue; %>
660
+ <% if (row.groupHeader) { const group = row.groupHeader; const groupTag = heading ? 'h4' : 'h3'; %>
661
+ <tr<% if (group.ariaHidden) { %> aria-hidden="true"<% } %> class="<%- cls(['fi-ta-row fi-ta-group-header-row', { 'fi-ta-group-header-row-with-selection': selectable }]) %>">
662
+ <% if (selectable) { %>
663
+ <td class="fi-ta-cell fi-ta-group-selection-cell">
664
+ <input aria-label="<%= t('filament-tables::table.fields.bulk_select_group.label', { title: group.title ?? '' }) %>" type="checkbox" data-group-selectable-record-keys="<%= JSON.stringify(group.keys) %>" x-on:click="toggleSelectRecords(JSON.parse($el.dataset.groupSelectableRecordKeys))" x-bind:checked="
665
+ const recordsInGroup = JSON.parse($el.dataset.groupSelectableRecordKeys)
666
+
667
+ if (recordsInGroup.length && areRecordsSelected(recordsInGroup)) {
668
+ $el.checked = true
669
+ $el.indeterminate = false
670
+
671
+ return 'checked'
672
+ }
673
+
674
+ $el.checked = false
675
+ $el.indeterminate =
676
+ recordsInGroup.length && areRecordsPartiallySelected(recordsInGroup)
677
+
678
+ return null
679
+ " wire:key="<%= id %>.table.bulk_select_group.checkbox.<%= page.page %>" wire:loading.attr="disabled" wire:target="<%= targets %>" class="fi-ta-group-checkbox fi-checkbox-input" />
680
+ </td>
681
+ <% } %>
682
+ <td colspan="<%= visibleColumns.length + (hasRowActions ? 1 : 0) %>" class="fi-ta-group-header-cell">
683
+ <div<% if (group.collapsible) { %> x-on:click="toggleCollapseGroup(<%= js(group.title) %>)" x-bind:class="isGroupCollapsed(<%= js(group.title) %>) ? 'fi-collapsed' : null"<% } %> class="<%- cls(['fi-ta-group-header', { 'fi-collapsible': group.collapsible }]) %>">
684
+ <div>
685
+ <<%- groupTag %> class="fi-ta-group-heading"><% if (group.label) { %><%= group.label %>: <% } %><%= group.title ?? '' %></<%- groupTag %>>
686
+ <% if (group.description) { %><p class="fi-ta-group-description"><%= group.description %></p><% } %>
687
+ </div>
688
+ <% if (group.collapsible) { %>
689
+ <button aria-label="<%= (group.label ? group.label + ': ' : '') + (group.title ?? '') %>" x-bind:aria-expanded="! isGroupCollapsed(<%= js(group.title) %>)" type="button" class="fi-icon-btn fi-size-sm"><%- icon('m-chevron-up', { size: 'sm' }) %></button>
690
+ <% } %>
691
+ </div>
692
+ </td>
693
+ </tr>
694
+ <% } %>
695
+ <tr wire:key="<%= id %>.table.records.<%= row.key %>" x-bind:class="{
696
+ <% if (row.collapsedExpression) { %> 'fi-collapsed': <%= row.collapsedExpression %>,
697
+ <% } %> 'fi-selected': <%- selectable ? 'true' : 'false' %> && isRecordSelected(<%= js(row.key) %>),
698
+ }"<% if (reorder?.active) { %> x-sortable-item="<%= row.key %>" x-sortable-handle<% } %> class="<%- cls(['fi-ta-row', { 'fi-clickable': row.url || row.mountExpression || row.wasClickable }, row.classes]) %>">
699
+ <% if (reorder?.active) { %>
700
+ <td class="fi-ta-cell">
701
+ <button aria-hidden="true" tabindex="-1" type="button" class="fi-ta-reorder-handle fi-icon-btn"><%- icon('m-bars-2') %></button>
702
+ <span id="<%= row.reorder.id %>" data-table-reorder-label="<%= row.reorder.label %>" class="fi-sr-only"><%= t('filament-tables::table.reordering.position', { label: row.reorder.label, position: row.reorder.position, count: rows.length }) %></span>
703
+ <div class="fi-sr-only">
704
+ <% for (const direction of ['up', 'down']) { %>
705
+ <button type="button" aria-describedby="<%= row.reorder.id %>"<% if ((direction === 'up' && row.reorder.position === 1) || (direction === 'down' && row.reorder.position === rows.length)) { %> disabled<% } %> tabindex="-1" data-table-reorder-direction="<%- direction %>" x-bind:aria-disabled="tableReorderOperation !== null || $el.disabled" x-on:click.stop="moveTableRecord($event, <%= js(row.key) %>, '<%- direction %>')"><%= t('filament-tables::table.actions.move_record_' + direction + '.label', { label: row.reorder.label }) %></button>
706
+ <% } %>
707
+ </div>
708
+ </td>
709
+ <% } else if (selectable) { %>
710
+ <td class="fi-ta-cell fi-ta-selection-cell">
711
+ <input aria-label="<%= t('filament-tables::table.fields.bulk_select_record.label', { key: row.key }) %>" type="checkbox" value="<%= row.key %>" x-on:click="toggleSelectedRecord(<%= js(row.key) %>)" x-bind:checked="isRecordSelected(<%= js(row.key) %>) ? 'checked' : null" data-group="<%= row.groupKey ?? '' %>" wire:loading.attr="disabled" wire:target="<%- targets %>" class="fi-ta-record-checkbox fi-checkbox-input" />
712
+ </td>
713
+ <% } %><%# end selection cell %>
714
+
715
+ <% for (const cell of row.cells) { %>
716
+ <td wire:key="<%= id %>.table.record.<%= row.key %>.column.<%= cell.name %>" class="<%- cls(['fi-ta-cell', 'fi-ta-cell-' + cell.cssName, cell.alignment && cell.alignment !== 'start' ? 'fi-align-' + cell.alignment : null]) %>">
717
+ <% if (cell.url) { %>
718
+ <% if (table.isStackedOnMobile()) { %><div class="fi-ta-cell-label"><%= cell.label %></div><div class="fi-ta-cell-content"><% } %>
719
+ <a href="<%= cell.url %>" x-on:click="if (! ($event.altKey || $event.ctrlKey || $event.metaKey || $event.shiftKey)) { $event.preventDefault(); Alpine.navigate($el.getAttribute('href')) }" class="fi-ta-col fi-ta-col-has-column-url">
720
+ <%- cell.html %>
721
+ </a>
722
+ <% if (table.isStackedOnMobile()) { %></div><% } %>
723
+ <% } else if (row.mountExpression && !cell.plain) { %>
724
+ <% if (table.isStackedOnMobile()) { %><div class="fi-ta-cell-label"><%= cell.label %></div><div class="fi-ta-cell-content"><% } %>
725
+ <button type="button" wire:click.prevent.stop="<%= row.mountExpression %>" wire:loading.attr="disabled" wire:target="<%= row.mountExpression %>" class="fi-ta-col">
726
+ <%- cell.html %>
727
+ </button>
728
+ <% if (table.isStackedOnMobile()) { %></div><% } %>
729
+ <% } else { %>
730
+ <%# The content wrapper only exists in stacked mode. %>
731
+ <% if (table.isStackedOnMobile()) { %><div class="fi-ta-cell-label"><%= cell.label %></div><div class="fi-ta-cell-content"><% } %>
732
+ <% if (row.url && !cell.plain) { %>
733
+ <a href="<%= row.url %>" x-on:click="if (! ($event.altKey || $event.ctrlKey || $event.metaKey || $event.shiftKey)) { $event.preventDefault(); Alpine.navigate($el.getAttribute('href')) }" class="fi-ta-col">
734
+ <%- cell.html %>
735
+ </a>
736
+ <% } else { %>
737
+ <div class="fi-ta-col"><%- cell.html %></div>
738
+ <% } %>
739
+ <% if (table.isStackedOnMobile()) { %></div><% } %>
740
+ <% } %>
741
+ </td>
742
+ <% } %>
743
+
744
+ <% if (!reorder?.active && hasRowActions) { %>
745
+ <td class="fi-ta-cell">
746
+ <div class="fi-ta-actions ">
747
+ <%- row.actionsHtml ?? '' %>
748
+ <% for (const action of row.actions) { %>
749
+ <a href="<%= action.url %>" wire:navigate wire:key="<%= id %>.actions.<%= action.key %>" class="<%- cls(['fi-ac-link-action', 'fi-link', 'fi-size-sm', ...linkClasses]) %>">
750
+ <%- icon(action.icon, { size: 'sm' }) %>
751
+ <span class="fi-link-label">
752
+ <%= action.label %>
753
+ </span>
754
+ </a>
755
+ <% } %>
756
+ </div>
757
+ </td>
758
+ <% } %><%# end actions cell %>
759
+ </tr>
760
+ <% } %>
761
+ <%# Summaries (already rendered and escaped rows): the last group's, then the table's. %>
762
+ <%- trailingGroupSummary %>
763
+ <%- summaryHtml %>
764
+ </tbody>
765
+ <% } %>
766
+ </table>
767
+ <% } %><%# end table / cards %>
768
+ </div>
769
+
770
+ <%- hook('tables::content.after', { table, records: page.records }) %>
771
+ <% if (rows.length) { %>
772
+ <%- pagination %>
773
+ <% } else { %>
774
+ <div class="fi-ta-empty-state" role="status">
775
+ <div class="fi-ta-empty-state-content">
776
+ <div class="fi-ta-empty-state-icon-bg">
777
+ <%- icon('o-x-mark', { size: 'lg', attributes: attrs({ 'wire:loading.remove.delay.default': '', 'wire:target': targets }) }) %>
778
+ <%- loadingIndicator(attrs({ 'wire:loading.delay.default': '', 'wire:target': targets }), 'lg') %>
779
+ </div>
780
+
781
+ <h2 class="fi-ta-empty-state-heading">
782
+ <%= t('filament-tables::table.empty.heading', { model: table.getPluralModelLabel() }) %>
783
+ </h2>
784
+ </div>
785
+ </div>
786
+ <% } %>
787
+ </div>
788
+ </div>
789
+ <%- afterContainer %>
790
+ </div>