PluginProbe
Tableberg – Simple Gutenberg Table Block / 1.1.5
Tableberg – Simple Gutenberg Table Block v1.1.5
1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.5 1.0.4 1.0.3 1.0.2 1.0.1 trunk 0.0.2 0.2.1 0.3.2 0.3.3 0.4.1 0.5.0 0.5.1 0.5.2 0.5.3 0.5.4 0.5.5 0.5.6 0.5.7 All 42 releases
← All changes | renderer/Table/TableRenderer.php +236 -69 1.1.11.1.5 View file →
@@ -6,21 +6,8 @@
6 6 use Tableberg\Renderer\Cell\CellRenderContext;
7 7 use Tableberg\Renderer\Attrs\StringAttr;
8 8
9 9 class TableRenderer {
10 - /**
11 - * @return bool
12 - */
13 - private function is_pro_active() {
14 - if (!function_exists('tp_fs')) {
15 - return false;
16 - }
17 -
18 - $fs = call_user_func('tp_fs');
19 -
20 - return is_object($fs) && $fs->can_use_premium_code();
21 - }
22 -
23 10 private function has_visible_border($border) {
24 11 $trimmed_border = trim($border);
25 12
26 13 if ($trimmed_border === '') {
@@ -65,9 +52,8 @@
65 52 );
66 53 }
67 54
68 55 $attrs = TableAttrs::from_array($attributes);
69 - $isProActive = $this->is_pro_active();
70 56
71 57 $rows = $attrs->table->rows->value();
72 58 $cols = $attrs->table->cols->value();
73 59
@@ -72,10 +58,30 @@
72 58 $cols = $attrs->table->cols->value();
73 59
74 60 $headerEnabled = $attrs->table->headerEnabled->value();
75 61 $footerEnabled = $attrs->table->footerEnabled->value();
76 - $stickyHeader = $attrs->table->stickyHeader->value();
77 - $stickyFirstCol = $attrs->table->stickyFirstCol->value();
62 +
63 + // Sticky header/first column are pro features: free never enables
64 + // them on its own, and pro reads the raw table attrs to decide.
65 + $stickySettings = apply_filters(
66 + 'tableberg/table_sticky_settings',
67 + ['stickyHeader' => false, 'stickyFirstCol' => false],
68 + $attrs->attrs
69 + );
70 + $stickyHeader = !empty($stickySettings['stickyHeader']);
71 + $stickyFirstCol = !empty($stickySettings['stickyFirstCol']);
72 +
73 + // Row-only/column-only is Pro-owned. Free's empty default preserves
74 + // the attribute without enabling its editor or frontend behavior.
75 + $innerBorderType = apply_filters(
76 + 'tableberg/inner_border_type',
77 + '',
78 + $attrs->attrs
79 + );
80 + if (!in_array($innerBorderType, ['row', 'col'], true)) {
81 + $innerBorderType = '';
82 + }
83 +
78 84 $caption = $attrs->table->caption;
79 85 $tableWidth = trim($attrs->table->tableWidth->asAttr());
80 86 $tableAlignment = $attrs->table->tableAlignment->asAttr();
81 87 $cellSpacingHorizontal = trim($attrs->table->cellSpacing->horizontal->asAttr());
@@ -84,8 +90,24 @@
84 90 $tableBorderRight = trim($attrs->table->tableBorder->right->asAttr());
85 91 $tableBorderBottom = trim($attrs->table->tableBorder->bottom->asAttr());
86 92 $tableBorderLeft = trim($attrs->table->tableBorder->left->asAttr());
87 93 $fixedColumnWidths = $attrs->table->fixedColumnWidths->value();
94 + $tableRadius = [
95 + 'topLeft' => $attrs->cellDefaults->styles->borderRadius->topLeft->asAttr(),
96 + 'topRight' => $attrs->cellDefaults->styles->borderRadius->topRight->asAttr(),
97 + 'bottomRight' => $attrs->cellDefaults->styles->borderRadius->bottomRight->asAttr(),
98 + 'bottomLeft' => $attrs->cellDefaults->styles->borderRadius->bottomLeft->asAttr(),
99 + ];
100 + $isZeroRadius = function ($value) {
101 + $trimmed = trim((string) $value);
102 + return $trimmed === '' || preg_match('/^0(?:\.0+)?[a-z%]*$/i', $trimmed) === 1;
103 + };
104 + $hasRoundedCorners = count(array_filter(
105 + $tableRadius,
106 + function ($value) use ($isZeroRadius) {
107 + return !$isZeroRadius($value);
108 + }
109 + )) > 0;
88 110
89 111 $isHorizontalSpacingZero = $cellSpacingHorizontal === '0';
90 112 $isVerticalSpacingZero = $cellSpacingVertical === '0';
91 113 $hasCellSpacing = !$isHorizontalSpacingZero || !$isVerticalSpacingZero;
@@ -111,8 +133,11 @@
111 133 );
112 134
113 135 $tableStyles = [
114 136 'border-collapse: ' . ($hasCellSpacing ? 'separate' : 'collapse'),
137 + // Neutralize theme-level table borders. Tableberg owns its
138 + // borders through the table/cell controls and rounded wrapper.
139 + 'border: 0',
115 140 ];
116 141
117 142 if ($hasCellSpacing) {
118 143 $tableStyles[] = "border-spacing: {$cellSpacingHorizontal} {$cellSpacingVertical}";
@@ -124,21 +149,21 @@
124 149 } else {
125 150 $tableStyles[] = 'width: 100%';
126 151 }
127 152
128 - if ($tableBorderTop !== '') {
153 + if (!$hasRoundedCorners && $tableBorderTop !== '') {
129 154 $tableStyles[] = "border-top: {$tableBorderTop}";
130 155 }
131 156
132 - if ($tableBorderRight !== '') {
157 + if (!$hasRoundedCorners && $tableBorderRight !== '') {
133 158 $tableStyles[] = "border-right: {$tableBorderRight}";
134 159 }
135 160
136 - if ($tableBorderBottom !== '') {
161 + if (!$hasRoundedCorners && $tableBorderBottom !== '') {
137 162 $tableStyles[] = "border-bottom: {$tableBorderBottom}";
138 163 }
139 164
140 - if ($tableBorderLeft !== '') {
165 + if (!$hasRoundedCorners && $tableBorderLeft !== '') {
141 166 $tableStyles[] = "border-left: {$tableBorderLeft}";
142 167 }
143 168
144 169 $tableStyleAttr = "style='" . implode('; ', $tableStyles) . ";'";
@@ -174,18 +199,18 @@
174 199 }
175 200
176 201 $tableClassAttr = implode(' ', $tableClasses);
177 202
178 - $sortableColumns = [];
179 -
180 - if ($isProActive) {
181 - foreach ($attrs->columns as $column => $columnConfig) {
182 - if ($columnConfig->sortable === null) {
183 - continue;
184 - }
185 -
186 - $sortableColumns[$column] = $columnConfig->sortable->asAttr();
187 - }
203 + // Column sorting is a pro feature: free never marks a column
204 + // sortable on its own, and pro reads the table's raw columns to
205 + // decide which ones are.
206 + $sortableColumns = apply_filters(
207 + 'tableberg/sortable_columns',
208 + [],
209 + $attrs->attrs
210 + );
211 + if (!is_array($sortableColumns)) {
212 + $sortableColumns = [];
188 213 }
189 214
190 215 $paginationPageSize = (int) $attrs->table->pagination->pageSize->value();
191 216 if ($paginationPageSize < 1) {
@@ -192,28 +217,39 @@
192 217 $paginationPageSize = 1;
193 218 }
194 219
195 220 $paginationConfig = [
196 - 'enabled' => $isProActive && $attrs->table->pagination->enabled->value(),
221 + 'enabled' => apply_filters('tableberg/pagination_enabled', false, $attrs->attrs),
197 222 'pageSize' => $paginationPageSize,
198 223 'showPageNumbers' => $attrs->table->pagination->showPageNumbers->value(),
199 224 'showPrevNext' => $attrs->table->pagination->showPrevNext->value(),
200 225 ];
201 226
202 - $searchEnabledAsStr = (
203 - $isProActive && $attrs->table->search->enabled->value()
204 - ) ? 'true' : 'false';
205 - $searchPlaceholder = $isProActive
206 - ? $attrs->table->search->placeholder->asAttr()
207 - : '';
208 - $searchPosition = $isProActive
209 - ? $attrs->table->search->position->asAttr()
210 - : 'left';
211 - $searchHighlightColor = $isProActive
212 - ? $attrs->table->search->highlightColor->asAttr()
213 - : '';
227 + $searchSettings = apply_filters(
228 + 'tableberg/table_search_settings',
229 + [
230 + 'enabled' => false,
231 + 'placeholder' => '',
232 + 'position' => 'left',
233 + 'highlightColor' => '',
234 + ],
235 + $attrs->attrs
236 + );
237 + $searchEnabledAsStr = !empty($searchSettings['enabled']) ? 'true' : 'false';
238 + $searchPlaceholder = (string) ($searchSettings['placeholder'] ?? '');
239 + $searchPosition = (string) ($searchSettings['position'] ?? 'left');
240 + $searchHighlightColor = (string) ($searchSettings['highlightColor'] ?? '');
214 241 $responsiveDataAttrs = $this->buildResponsiveDataAttrs($attrs, $rows, $cols);
215 242
243 + // Horizontal cell-element layout (and the wrap toggle that only
244 + // matters with it) is a pro feature; free's default is always the
245 + // vertical stack. Grouped in one filter since they're one decision.
246 + $cellLayout = apply_filters(
247 + 'tableberg/cell_default_layout',
248 + ['orientation' => 'vertical', 'wrap' => 'nowrap'],
249 + $attrs->attrs
250 + );
251 +
216 252 $globalCellStyles = [
217 253 'padding' => [
218 254 'top' => $attrs->cellDefaults->styles->padding->top->asAttr(),
219 255 'right' => $attrs->cellDefaults->styles->padding->right->asAttr(),
@@ -219,11 +255,11 @@
219 255 'right' => $attrs->cellDefaults->styles->padding->right->asAttr(),
220 256 'bottom' => $attrs->cellDefaults->styles->padding->bottom->asAttr(),
221 257 'left' => $attrs->cellDefaults->styles->padding->left->asAttr(),
222 258 ],
223 - 'orientation' => $attrs->cellDefaults->styles->orientation->asAttr(),
259 + 'orientation' => $cellLayout['orientation'] === 'horizontal' ? 'horizontal' : 'vertical',
224 260 'elementGap' => $attrs->cellDefaults->styles->elementGap->asAttr(),
225 - 'wrap' => $attrs->cellDefaults->styles->wrap->asAttr(),
261 + 'wrap' => $cellLayout['wrap'] === 'wrap' ? 'wrap' : 'nowrap',
226 262 'verticalAlign' => $attrs->cellDefaults->styles->verticalAlign->asAttr(),
227 263 'backgroundColor' => $attrs->cellDefaults->styles->backgroundColor->asAttr(),
228 264 'border' => [
229 265 'top' => $attrs->cellDefaults->styles->border->top->asAttr(),
@@ -238,13 +274,18 @@
238 274 'bottomLeft' => $attrs->cellDefaults->styles->borderRadius->bottomLeft->asAttr(),
239 275 ],
240 276 ];
241 277
242 - // The table border radius rounds the whole table's outer corners.
243 - // border-radius on collapsed-border tables/cells is ignored by
244 - // browsers, so it is rendered on the wrapper (with overflow:hidden)
245 - // and removed from the individual cells here.
246 - $tableRadius = $globalCellStyles['borderRadius'];
278 + // A collapsed table cannot reliably round its own explicit border.
279 + // The wrapper owns only the table border and clips the cell grid to
280 + // it. Cell borders stay on cells; copying them to the wrapper would
281 + // add an outline that the user did not configure as a table border.
282 + $tableOuterBorder = [
283 + 'top' => $tableBorderTop,
284 + 'right' => $tableBorderRight,
285 + 'bottom' => $tableBorderBottom,
286 + 'left' => $tableBorderLeft,
287 + ];
247 288 $globalCellStyles['borderRadius'] = [
248 289 'topLeft' => '',
249 290 'topRight' => '',
250 291 'bottomRight' => '',
@@ -258,9 +299,47 @@
258 299
259 300 for ($row = 0; $row < $rows; $row++) {
260 301 $cellsHtml = '';
261 302 $rowHeight = $this->getRowHeight($row, $attrs->rows);
303 + $rowStyles = $this->getRowStyles($row, $attrs->rows);
304 + $rowBackgroundColor = isset($rowStyles['backgroundColor']) && is_string($rowStyles['backgroundColor'])
305 + ? $rowStyles['backgroundColor']
306 + : '';
262 307
308 + $cellStylesForRow = $globalCellStyles;
309 +
310 + // Header/footer/even/odd are table-wide defaults, resolved per
311 + // row here the same way `cell/index.tsx`'s
312 + // `useRowBackgroundStyleKey` does in the editor — header/footer
313 + // don't consume a slot in the even/odd count.
314 + $isHeaderRow = $headerEnabled && $row === 0;
315 + $isFooterRow = $footerEnabled && $rows > 0 && $row === $rows - 1;
316 +
317 + if ($isHeaderRow) {
318 + $rowPositionBackground = $attrs->cellDefaults->styles->headerBackgroundColor->asAttr();
319 + } elseif ($isFooterRow) {
320 + $rowPositionBackground = $attrs->cellDefaults->styles->footerBackgroundColor->asAttr();
321 + } else {
322 + $dataRowPosition = $headerEnabled ? $row - 1 : $row;
323 + $rowPositionBackground = ($dataRowPosition % 2 === 0)
324 + ? $attrs->cellDefaults->styles->oddRowBackgroundColor->asAttr()
325 + : $attrs->cellDefaults->styles->evenRowBackgroundColor->asAttr();
326 + }
327 +
328 + if ($rowPositionBackground !== '') {
329 + $cellStylesForRow['backgroundColor'] = $rowPositionBackground;
330 + }
331 +
332 + // A row with its own background would otherwise never show
333 + // through: every cell paints its own background over the
334 + // `<tr>`'s, and cells fall back to the table-wide default
335 + // whenever they carry no colour of their own. So for this row's
336 + // cells, that fallback is cleared — a cell/column colour on top
337 + // of it (applied below by the pro filter) still wins either way.
338 + if ($rowBackgroundColor !== '') {
339 + $cellStylesForRow['backgroundColor'] = '';
340 + }
341 +
263 342 for ($col = 0; $col < $cols; $col++) {
264 343 if (isset($hiddenBySpan[$row][$col])) {
265 344 continue;
266 345 }
@@ -313,9 +392,9 @@
313 392 $col,
314 393 $rowSpan,
315 394 $colSpan,
316 395 $tag,
317 - $globalCellStyles,
396 + $cellStylesForRow,
318 397 $this->getCellElements($cell),
319 398 $this->getCellStyleOverride($cell),
320 399 $this->getCellRibbon($cell),
321 400 $sortableType,
@@ -322,14 +401,40 @@
322 401 $columnWidth,
323 402 $rowHeight,
324 403 $stickyHeader,
325 404 $stickyFirstCol,
326 - $this->getCellClassName($cell)
405 + $this->getCellClassName($cell),
406 + $cell instanceof CellData ? $cell->attrs : [],
407 + $this->isCellEmpty($cell),
408 + $innerBorderType,
409 + $rows,
410 + $cols
327 411 )
328 412 );
329 413 }
330 414
331 - $rowsHtml .= "<tr data-tableberg-row='{$row}'>$cellsHtml</tr>";
415 + $rowStyleParts = [];
416 + if ($rowBackgroundColor !== '') {
417 + $rowStyleParts[] = 'background-color:' . esc_attr($rowBackgroundColor);
418 + }
419 +
420 + $rowBorder = isset($rowStyles['border']) && is_array($rowStyles['border'])
421 + ? $rowStyles['border']
422 + : [];
423 + foreach (['top', 'right', 'bottom', 'left'] as $side) {
424 + $value = isset($rowBorder[$side]) && is_string($rowBorder[$side])
425 + ? $rowBorder[$side]
426 + : '';
427 + if ($value !== '') {
428 + $rowStyleParts[] = "border-{$side}:" . esc_attr($value);
429 + }
430 + }
431 +
432 + $rowStyleAttr = !empty($rowStyleParts)
433 + ? ' style="' . implode(';', $rowStyleParts) . '"'
434 + : '';
435 +
436 + $rowsHtml .= "<tr data-tableberg-row='{$row}'$rowStyleAttr>$cellsHtml</tr>";
332 437 }
333 438
334 439 $sortingBoolAsStr = !empty($sortableColumns) ? 'true' : 'false';
335 440 $headerBoolAsStr = $headerEnabled ? 'true' : 'false';
@@ -341,27 +446,29 @@
341 446 'sortable' => $sortType,
342 447 ];
343 448 }
344 449
345 - $columnsJson = json_encode($columnsData);
450 + $columnsJson = wp_json_encode($columnsData);
346 451 if (!is_string($columnsJson)) {
347 452 $columnsJson = '{}';
348 453 }
454 + $columnsJson = esc_attr($columnsJson);
349 455
350 - $paginationJson = json_encode($paginationConfig);
456 + $paginationJson = wp_json_encode($paginationConfig);
351 457 if (!is_string($paginationJson)) {
352 458 $paginationJson = '{}';
353 459 }
460 + $paginationJson = esc_attr($paginationJson);
354 461
462 + $searchPlaceholderAttr = esc_attr($searchPlaceholder);
463 + $searchPositionAttr = esc_attr($searchPosition);
464 + $searchHighlightColorAttr = esc_attr($searchHighlightColor);
465 +
355 466 $wrapperClass = trim("tableberg-table-wrapper {$wrapperAlignmentClass}");
356 467
357 - // Round the whole table's outer corners by clipping the wrapper.
468 + // Round the whole table's outer corners on the wrapper.
358 469 // Zero radii are skipped so a table without any actual rounding does
359 470 // not carry pointless border-radius declarations.
360 - $isZeroRadius = function ($value) {
361 - $trimmed = trim((string) $value);
362 - return $trimmed === '' || preg_match('/^0(?:\.0+)?[a-z%]*$/i', $trimmed) === 1;
363 - };
364 471 $wrapperStyles = [];
365 472 if (!$isZeroRadius($tableRadius['topLeft'])) {
366 473 $wrapperStyles[] = 'border-top-left-radius:' . $tableRadius['topLeft'];
367 474 }
@@ -374,8 +481,24 @@
374 481 if (!$isZeroRadius($tableRadius['bottomLeft'])) {
375 482 $wrapperStyles[] = 'border-bottom-left-radius:' . $tableRadius['bottomLeft'];
376 483 }
377 484
485 + if (!empty($wrapperStyles)) {
486 + if ($tableOuterBorder['top'] !== '') {
487 + $wrapperStyles[] = 'border-top:' . $tableOuterBorder['top'];
488 + }
489 + if ($tableOuterBorder['right'] !== '') {
490 + $wrapperStyles[] = 'border-right:' . $tableOuterBorder['right'];
491 + }
492 + if ($tableOuterBorder['bottom'] !== '') {
493 + $wrapperStyles[] = 'border-bottom:' . $tableOuterBorder['bottom'];
494 + }
495 + if ($tableOuterBorder['left'] !== '') {
496 + $wrapperStyles[] = 'border-left:' . $tableOuterBorder['left'];
497 + }
498 + $wrapperStyles[] = 'box-sizing:border-box';
499 + }
500 +
378 501 // A table wider than its wrapper must scroll inside the wrapper
379 502 // instead of pushing the whole page sideways, so the wrapper is a
380 503 // scroll container by default.
381 504 //
@@ -387,10 +510,8 @@
387 510 // scrolling wins and the header sticks within the wrapper.
388 511 //
389 512 // A rounded wrapper always needs the clip, so it opts in regardless.
390 513 $pageStickyHeader = $stickyHeader && !$stickyFirstCol;
391 - $hasRoundedCorners = !empty($wrapperStyles);
392 -
393 514 if ($hasRoundedCorners || !$pageStickyHeader) {
394 515 // `auto`, not `hidden`: both clip to the rounded corners, but
395 516 // `hidden` would also swallow a table wider than the wrapper.
396 517 // Inline, so it also wins over the `.tableberg-scroll-x` rule.
@@ -450,11 +571,11 @@
450 571 data-tableberg-sortable='$sortingBoolAsStr'
451 572 data-tableberg-columns='$columnsJson'
452 573 data-tableberg-pagination='$paginationJson'
453 574 data-tableberg-search-enabled='$searchEnabledAsStr'
454 - data-tableberg-search-placeholder='$searchPlaceholder'
455 - data-tableberg-search-position='$searchPosition'
456 - data-tableberg-search-highlight-color='$searchHighlightColor'
575 + data-tableberg-search-placeholder='$searchPlaceholderAttr'
576 + data-tableberg-search-position='$searchPositionAttr'
577 + data-tableberg-search-highlight-color='$searchHighlightColorAttr'
457 578 data-tableberg-header='$headerBoolAsStr'
458 579 data-tableberg-footer='$footerBoolAsStr'
459 580 {$responsiveDataAttrs}
460 581 >
@@ -526,8 +647,15 @@
526 647 $mobileMaxWidth = max(1, (int) $mobile->maxWidth->value());
527 648 $tabletStackCount = max(1, (int) $tablet->stackCount->value());
528 649 $mobileStackCount = max(1, (int) $mobile->stackCount->value());
529 650
651 + // Repeating the first column in every stack row is a pro option. Only
652 + // the licensed pro plugin turns this filter on, so the saved value is
653 + // ignored without a valid licence.
654 + $proActive = (bool) apply_filters('tableberg/is_pro_runtime_active', false);
655 + $tabletRepeatFirstCol = $proActive ? $tablet->repeatFirstCol->asAttr() : '';
656 + $mobileRepeatFirstCol = $proActive ? $mobile->repeatFirstCol->asAttr() : '';
657 +
530 658 return "
531 659 data-tableberg-responsive='true'
532 660 data-tableberg-rows='{$rows}'
533 661 data-tableberg-cols='{$cols}'
@@ -535,15 +663,15 @@
535 663 data-tableberg-tablet-width='{$tabletMaxWidth}'
536 664 data-tableberg-tablet-mode='{$tablet->mode->asAttr()}'
537 665 data-tableberg-tablet-transpose='{$tablet->transpose->asAttr()}'
538 666 data-tableberg-tablet-count='{$tabletStackCount}'
539 - data-tableberg-tablet-repeat-first-col='{$tablet->repeatFirstCol->asAttr()}'
667 + data-tableberg-tablet-repeat-first-col='{$tabletRepeatFirstCol}'
540 668 data-tableberg-mobile-enabled='{$mobile->enabled->asAttr()}'
541 669 data-tableberg-mobile-width='{$mobileMaxWidth}'
542 670 data-tableberg-mobile-mode='{$mobile->mode->asAttr()}'
543 671 data-tableberg-mobile-transpose='{$mobile->transpose->asAttr()}'
544 672 data-tableberg-mobile-count='{$mobileStackCount}'
545 - data-tableberg-mobile-repeat-first-col='{$mobile->repeatFirstCol->asAttr()}'
673 + data-tableberg-mobile-repeat-first-col='{$mobileRepeatFirstCol}'
546 674 ";
547 675 }
548 676
549 677 private function getCell($row, $col, $cells) {
@@ -556,13 +684,31 @@
556 684 return $cells[$key];
557 685 }
558 686
559 687 private function getCellElements($cell) {
688 + if ($this->isCellEmpty($cell)) {
689 + return [];
690 + }
691 +
692 + return $cell instanceof CellData && is_array($cell->elements)
693 + ? $cell->elements
694 + : [];
695 + }
696 +
697 + /**
698 + * "Empty cell" is a pro feature: the cell renders as if it were a bare,
699 + * unstyled table cell — no content, no background, no border. Free's
700 + * default is to always show the cell as configured.
701 + *
702 + * @param mixed $cell
703 + * @return bool
704 + */
705 + private function isCellEmpty($cell) {
560 706 if (!$cell instanceof CellData) {
561 - return [];
707 + return false;
562 708 }
563 709
564 - return is_array($cell->elements) ? $cell->elements : [];
710 + return (bool) apply_filters('tableberg/cell_is_empty', false, $cell->attrs);
565 711 }
566 712
567 713 private function getCellStyleOverride($cell) {
568 714 if (!$cell instanceof CellData || !is_array($cell->styles)) {
@@ -640,6 +786,27 @@
640 786 }
641 787
642 788 $height = $rowConfig->height->asAttr();
643 789 return $height === '' ? null : $height;
790 + }
791 +
792 + /**
793 + * Pro styling of a row (e.g. background colour). One filter carries
794 + * every pro row style, mirroring `tableberg/cell_styles`, so a new pro
795 + * style needs no change here.
796 + *
797 + * @param int $row
798 + * @param array<int|string, RowConfig> $rowConfigs
799 + * @return array<string, string>
800 + */
801 + private function getRowStyles($row, $rowConfigs) {
802 + $rowAttrs = (
803 + is_array($rowConfigs) &&
804 + array_key_exists($row, $rowConfigs) &&
805 + $rowConfigs[$row] instanceof RowConfig
806 + ) ? $rowConfigs[$row]->attrs : [];
807 +
808 + $styles = apply_filters('tableberg/row_styles', [], $rowAttrs);
809 +
810 + return is_array($styles) ? $styles : [];
644 811 }
645 812 }