cells -> elements). Adapt it to the v3 attrs shape so the
// renderer below stays format-agnostic.
$version = is_array($attributes) && isset($attributes['version']) && is_numeric($attributes['version'])
? (int) $attributes['version']
: 0;
if (
$version >= 4 &&
is_object($block) &&
isset($block->parsed_block['innerBlocks']) &&
is_array($block->parsed_block['innerBlocks'])
) {
$attributes = InnerBlocksAttrsAdapter::to_attrs(
$attributes,
$block->parsed_block['innerBlocks']
);
}
$attrs = TableAttrs::from_array($attributes);
$rows = $attrs->table->rows->value();
$cols = $attrs->table->cols->value();
$headerEnabled = $attrs->table->headerEnabled->value();
$footerEnabled = $attrs->table->footerEnabled->value();
// Sticky header/first column are pro features: free never enables
// them on its own, and pro reads the raw table attrs to decide.
$stickySettings = apply_filters(
'tableberg/table_sticky_settings',
['stickyHeader' => false, 'stickyFirstCol' => false],
$attrs->attrs
);
$stickyHeader = !empty($stickySettings['stickyHeader']);
$stickyFirstCol = !empty($stickySettings['stickyFirstCol']);
// Row-only/column-only is Pro-owned. Free's empty default preserves
// the attribute without enabling its editor or frontend behavior.
$innerBorderType = apply_filters(
'tableberg/inner_border_type',
'',
$attrs->attrs
);
if (!in_array($innerBorderType, ['row', 'col'], true)) {
$innerBorderType = '';
}
$caption = $attrs->table->caption;
$tableWidth = trim($attrs->table->tableWidth->asAttr());
$tableAlignment = $attrs->table->tableAlignment->asAttr();
$cellSpacingHorizontal = trim($attrs->table->cellSpacing->horizontal->asAttr());
$cellSpacingVertical = trim($attrs->table->cellSpacing->vertical->asAttr());
$tableBorderTop = trim($attrs->table->tableBorder->top->asAttr());
$tableBorderRight = trim($attrs->table->tableBorder->right->asAttr());
$tableBorderBottom = trim($attrs->table->tableBorder->bottom->asAttr());
$tableBorderLeft = trim($attrs->table->tableBorder->left->asAttr());
$fixedColumnWidths = $attrs->table->fixedColumnWidths->value();
$tableRadius = [
'topLeft' => $attrs->cellDefaults->styles->borderRadius->topLeft->asAttr(),
'topRight' => $attrs->cellDefaults->styles->borderRadius->topRight->asAttr(),
'bottomRight' => $attrs->cellDefaults->styles->borderRadius->bottomRight->asAttr(),
'bottomLeft' => $attrs->cellDefaults->styles->borderRadius->bottomLeft->asAttr(),
];
$isZeroRadius = function ($value) {
$trimmed = trim((string) $value);
return $trimmed === '' || preg_match('/^0(?:\.0+)?[a-z%]*$/i', $trimmed) === 1;
};
$hasRoundedCorners = count(array_filter(
$tableRadius,
function ($value) use ($isZeroRadius) {
return !$isZeroRadius($value);
}
)) > 0;
$isHorizontalSpacingZero = $cellSpacingHorizontal === '0';
$isVerticalSpacingZero = $cellSpacingVertical === '0';
$hasCellSpacing = !$isHorizontalSpacingZero || !$isVerticalSpacingZero;
$isWideWidth = $tableWidth === 'wide';
$isFullWidth = $tableWidth === 'full';
$canApplyCustomWidth =
$tableWidth !== '' &&
!$isWideWidth &&
!$isFullWidth &&
$tableWidth !== 'auto';
if (!$canApplyCustomWidth) {
$tableWidth = '';
}
$wrapperAlignmentClass = $this->getWrapperAlignmentClass(
$isWideWidth,
$isFullWidth,
$canApplyCustomWidth,
$tableAlignment
);
$tableStyles = [
'border-collapse: ' . ($hasCellSpacing ? 'separate' : 'collapse'),
// Neutralize theme-level table borders. Tableberg owns its
// borders through the table/cell controls and rounded wrapper.
'border: 0',
];
if ($hasCellSpacing) {
$tableStyles[] = "border-spacing: {$cellSpacingHorizontal} {$cellSpacingVertical}";
}
if ($tableWidth !== '') {
$tableStyles[] = "width: {$tableWidth}";
$tableStyles[] = "max-width: {$tableWidth}";
} else {
$tableStyles[] = 'width: 100%';
}
if (!$hasRoundedCorners && $tableBorderTop !== '') {
$tableStyles[] = "border-top: {$tableBorderTop}";
}
if (!$hasRoundedCorners && $tableBorderRight !== '') {
$tableStyles[] = "border-right: {$tableBorderRight}";
}
if (!$hasRoundedCorners && $tableBorderBottom !== '') {
$tableStyles[] = "border-bottom: {$tableBorderBottom}";
}
if (!$hasRoundedCorners && $tableBorderLeft !== '') {
$tableStyles[] = "border-left: {$tableBorderLeft}";
}
$tableStyleAttr = "style='" . implode('; ', $tableStyles) . ";'";
$tableClasses = ['wp-block-tableberg'];
if ($this->has_visible_border($tableBorderTop)) {
$tableClasses[] = 'tableberg-has-table-border-top';
}
if ($this->has_visible_border($tableBorderRight)) {
$tableClasses[] = 'tableberg-has-table-border-right';
}
if ($this->has_visible_border($tableBorderBottom)) {
$tableClasses[] = 'tableberg-has-table-border-bottom';
}
if ($this->has_visible_border($tableBorderLeft)) {
$tableClasses[] = 'tableberg-has-table-border-left';
}
if ($hasCellSpacing) {
$tableClasses[] = 'tableberg-has-cell-spacing';
if ($isHorizontalSpacingZero) {
$tableClasses[] = 'tableberg-cell-spacing-horizontal-zero';
}
if ($isVerticalSpacingZero) {
$tableClasses[] = 'tableberg-cell-spacing-vertical-zero';
}
}
$tableClassAttr = implode(' ', $tableClasses);
// Column sorting is a pro feature: free never marks a column
// sortable on its own, and pro reads the table's raw columns to
// decide which ones are.
$sortableColumns = apply_filters(
'tableberg/sortable_columns',
[],
$attrs->attrs
);
if (!is_array($sortableColumns)) {
$sortableColumns = [];
}
$paginationPageSize = (int) $attrs->table->pagination->pageSize->value();
if ($paginationPageSize < 1) {
$paginationPageSize = 1;
}
$paginationConfig = [
'enabled' => apply_filters('tableberg/pagination_enabled', false, $attrs->attrs),
'pageSize' => $paginationPageSize,
'showPageNumbers' => $attrs->table->pagination->showPageNumbers->value(),
'showPrevNext' => $attrs->table->pagination->showPrevNext->value(),
];
$searchSettings = apply_filters(
'tableberg/table_search_settings',
[
'enabled' => false,
'placeholder' => '',
'position' => 'left',
'highlightColor' => '',
],
$attrs->attrs
);
$searchEnabledAsStr = !empty($searchSettings['enabled']) ? 'true' : 'false';
$searchPlaceholder = (string) ($searchSettings['placeholder'] ?? '');
$searchPosition = (string) ($searchSettings['position'] ?? 'left');
$searchHighlightColor = (string) ($searchSettings['highlightColor'] ?? '');
$responsiveDataAttrs = $this->buildResponsiveDataAttrs($attrs, $rows, $cols);
// Horizontal cell-element layout (and the wrap toggle that only
// matters with it) is a pro feature; free's default is always the
// vertical stack. Grouped in one filter since they're one decision.
$cellLayout = apply_filters(
'tableberg/cell_default_layout',
['orientation' => 'vertical', 'wrap' => 'nowrap'],
$attrs->attrs
);
$globalCellStyles = [
'padding' => [
'top' => $attrs->cellDefaults->styles->padding->top->asAttr(),
'right' => $attrs->cellDefaults->styles->padding->right->asAttr(),
'bottom' => $attrs->cellDefaults->styles->padding->bottom->asAttr(),
'left' => $attrs->cellDefaults->styles->padding->left->asAttr(),
],
'orientation' => $cellLayout['orientation'] === 'horizontal' ? 'horizontal' : 'vertical',
'elementGap' => $attrs->cellDefaults->styles->elementGap->asAttr(),
'wrap' => $cellLayout['wrap'] === 'wrap' ? 'wrap' : 'nowrap',
'verticalAlign' => $attrs->cellDefaults->styles->verticalAlign->asAttr(),
'backgroundColor' => $attrs->cellDefaults->styles->backgroundColor->asAttr(),
'border' => [
'top' => $attrs->cellDefaults->styles->border->top->asAttr(),
'right' => $attrs->cellDefaults->styles->border->right->asAttr(),
'bottom' => $attrs->cellDefaults->styles->border->bottom->asAttr(),
'left' => $attrs->cellDefaults->styles->border->left->asAttr(),
],
'borderRadius' => [
'topLeft' => $attrs->cellDefaults->styles->borderRadius->topLeft->asAttr(),
'topRight' => $attrs->cellDefaults->styles->borderRadius->topRight->asAttr(),
'bottomRight' => $attrs->cellDefaults->styles->borderRadius->bottomRight->asAttr(),
'bottomLeft' => $attrs->cellDefaults->styles->borderRadius->bottomLeft->asAttr(),
],
];
// A collapsed table cannot reliably round its own explicit border.
// The wrapper owns only the table border and clips the cell grid to
// it. Cell borders stay on cells; copying them to the wrapper would
// add an outline that the user did not configure as a table border.
$tableOuterBorder = [
'top' => $tableBorderTop,
'right' => $tableBorderRight,
'bottom' => $tableBorderBottom,
'left' => $tableBorderLeft,
];
$globalCellStyles['borderRadius'] = [
'topLeft' => '',
'topRight' => '',
'bottomRight' => '',
'bottomLeft' => '',
];
$cellRenderer = new CellRenderer();
$hiddenBySpan = [];
$rowsHtml = '';
for ($row = 0; $row < $rows; $row++) {
$cellsHtml = '';
$rowHeight = $this->getRowHeight($row, $attrs->rows);
$rowStyles = $this->getRowStyles($row, $attrs->rows);
$rowBackgroundColor = isset($rowStyles['backgroundColor']) && is_string($rowStyles['backgroundColor'])
? $rowStyles['backgroundColor']
: '';
$cellStylesForRow = $globalCellStyles;
// Header/footer/even/odd are table-wide defaults, resolved per
// row here the same way `cell/index.tsx`'s
// `useRowBackgroundStyleKey` does in the editor — header/footer
// don't consume a slot in the even/odd count.
$isHeaderRow = $headerEnabled && $row === 0;
$isFooterRow = $footerEnabled && $rows > 0 && $row === $rows - 1;
if ($isHeaderRow) {
$rowPositionBackground = $attrs->cellDefaults->styles->headerBackgroundColor->asAttr();
} elseif ($isFooterRow) {
$rowPositionBackground = $attrs->cellDefaults->styles->footerBackgroundColor->asAttr();
} else {
$dataRowPosition = $headerEnabled ? $row - 1 : $row;
$rowPositionBackground = ($dataRowPosition % 2 === 0)
? $attrs->cellDefaults->styles->oddRowBackgroundColor->asAttr()
: $attrs->cellDefaults->styles->evenRowBackgroundColor->asAttr();
}
if ($rowPositionBackground !== '') {
$cellStylesForRow['backgroundColor'] = $rowPositionBackground;
}
// A row with its own background would otherwise never show
// through: every cell paints its own background over the
// `
`'s, and cells fall back to the table-wide default
// whenever they carry no colour of their own. So for this row's
// cells, that fallback is cleared — a cell/column colour on top
// of it (applied below by the pro filter) still wins either way.
if ($rowBackgroundColor !== '') {
$cellStylesForRow['backgroundColor'] = '';
}
for ($col = 0; $col < $cols; $col++) {
if (isset($hiddenBySpan[$row][$col])) {
continue;
}
$cell = $this->getCell($row, $col, $attrs->cells);
$span = $this->getCellSpan($cell);
$rowSpan = is_numeric($span['rowSpan']) ? (int) $span['rowSpan'] : 1;
$colSpan = is_numeric($span['colSpan']) ? (int) $span['colSpan'] : 1;
if ($rowSpan > 1 || $colSpan > 1) {
for ($rowOffset = 0; $rowOffset < $rowSpan; $rowOffset++) {
for ($colOffset = 0; $colOffset < $colSpan; $colOffset++) {
if ($rowOffset === 0 && $colOffset === 0) {
continue;
}
$hiddenRow = $row + $rowOffset;
$hiddenCol = $col + $colOffset;
$hiddenBySpan[$hiddenRow][$hiddenCol] = true;
}
}
}
$isHeaderRowCell = $headerEnabled && $row === 0;
$isFooterCell = $footerEnabled && $rows > 0 && $row === $rows - 1;
$tag = ($isHeaderRowCell || $isFooterCell) ? 'th' : 'td';
$sortableType = (
$isHeaderRowCell &&
array_key_exists($col, $sortableColumns)
) ? $sortableColumns[$col] : null;
$columnWidth = null;
if ($colSpan === 1) {
if ($fixedColumnWidths && $cols > 0) {
$columnWidth = (string) (100 / $cols) . '%';
} else {
$columnWidth = $this->getColumnWidth(
$col,
$attrs->columns
);
}
}
$cellsHtml .= $cellRenderer->render(
CellRenderContext::create(
$row,
$col,
$rowSpan,
$colSpan,
$tag,
$cellStylesForRow,
$this->getCellElements($cell),
$this->getCellStyleOverride($cell),
$this->getCellRibbon($cell),
$sortableType,
$columnWidth,
$rowHeight,
$stickyHeader,
$stickyFirstCol,
$this->getCellClassName($cell),
$cell instanceof CellData ? $cell->attrs : [],
$this->isCellEmpty($cell),
$innerBorderType,
$rows,
$cols
)
);
}
$rowStyleParts = [];
if ($rowBackgroundColor !== '') {
$rowStyleParts[] = 'background-color:' . esc_attr($rowBackgroundColor);
}
$rowBorder = isset($rowStyles['border']) && is_array($rowStyles['border'])
? $rowStyles['border']
: [];
foreach (['top', 'right', 'bottom', 'left'] as $side) {
$value = isset($rowBorder[$side]) && is_string($rowBorder[$side])
? $rowBorder[$side]
: '';
if ($value !== '') {
$rowStyleParts[] = "border-{$side}:" . esc_attr($value);
}
}
$rowStyleAttr = !empty($rowStyleParts)
? ' style="' . implode(';', $rowStyleParts) . '"'
: '';
$rowsHtml .= "
$cellsHtml
";
}
$sortingBoolAsStr = !empty($sortableColumns) ? 'true' : 'false';
$headerBoolAsStr = $headerEnabled ? 'true' : 'false';
$footerBoolAsStr = $footerEnabled ? 'true' : 'false';
$columnsData = [];
foreach ($sortableColumns as $column => $sortType) {
$columnsData[$column] = [
'sortable' => $sortType,
];
}
$columnsJson = wp_json_encode($columnsData);
if (!is_string($columnsJson)) {
$columnsJson = '{}';
}
$columnsJson = esc_attr($columnsJson);
$paginationJson = wp_json_encode($paginationConfig);
if (!is_string($paginationJson)) {
$paginationJson = '{}';
}
$paginationJson = esc_attr($paginationJson);
$searchPlaceholderAttr = esc_attr($searchPlaceholder);
$searchPositionAttr = esc_attr($searchPosition);
$searchHighlightColorAttr = esc_attr($searchHighlightColor);
$wrapperClass = trim("tableberg-table-wrapper {$wrapperAlignmentClass}");
// Round the whole table's outer corners on the wrapper.
// Zero radii are skipped so a table without any actual rounding does
// not carry pointless border-radius declarations.
$wrapperStyles = [];
if (!$isZeroRadius($tableRadius['topLeft'])) {
$wrapperStyles[] = 'border-top-left-radius:' . $tableRadius['topLeft'];
}
if (!$isZeroRadius($tableRadius['topRight'])) {
$wrapperStyles[] = 'border-top-right-radius:' . $tableRadius['topRight'];
}
if (!$isZeroRadius($tableRadius['bottomRight'])) {
$wrapperStyles[] = 'border-bottom-right-radius:' . $tableRadius['bottomRight'];
}
if (!$isZeroRadius($tableRadius['bottomLeft'])) {
$wrapperStyles[] = 'border-bottom-left-radius:' . $tableRadius['bottomLeft'];
}
if (!empty($wrapperStyles)) {
if ($tableOuterBorder['top'] !== '') {
$wrapperStyles[] = 'border-top:' . $tableOuterBorder['top'];
}
if ($tableOuterBorder['right'] !== '') {
$wrapperStyles[] = 'border-right:' . $tableOuterBorder['right'];
}
if ($tableOuterBorder['bottom'] !== '') {
$wrapperStyles[] = 'border-bottom:' . $tableOuterBorder['bottom'];
}
if ($tableOuterBorder['left'] !== '') {
$wrapperStyles[] = 'border-left:' . $tableOuterBorder['left'];
}
$wrapperStyles[] = 'box-sizing:border-box';
}
// A table wider than its wrapper must scroll inside the wrapper
// instead of pushing the whole page sideways, so the wrapper is a
// scroll container by default.
//
// The one exception is a header that sticks to the page: a scroll
// container of its own would capture it and it would never stick.
// (CSS cannot do both — with overflow-x set, overflow-y can no longer
// be visible.) A sticky first column, on the other hand, only works
// *because* of the scroll container, so when both are enabled the
// scrolling wins and the header sticks within the wrapper.
//
// A rounded wrapper always needs the clip, so it opts in regardless.
$pageStickyHeader = $stickyHeader && !$stickyFirstCol;
if ($hasRoundedCorners || !$pageStickyHeader) {
// `auto`, not `hidden`: both clip to the rounded corners, but
// `hidden` would also swallow a table wider than the wrapper.
// Inline, so it also wins over the `.tableberg-scroll-x` rule.
$wrapperStyles[] = 'overflow:auto';
}
$wrapperStyleAttr = !empty($wrapperStyles)
? "style='" . implode(';', $wrapperStyles) . "'"
: '';
$figureAlignmentClass = '';
if ($isWideWidth) {
$figureAlignmentClass = 'alignwide';
} elseif ($isFullWidth) {
$figureAlignmentClass = 'alignfull';
}
$figureClass = trim(
implode(' ', array_filter([
'wp-block-tableberg',
$attrs->table->className->asAttr(),
$figureAlignmentClass,
]))
);
$figureStyles = [];
$spacingSides = [
'margin-top' => $attrs->table->margin->top,
'margin-right' => $attrs->table->margin->right,
'margin-bottom' => $attrs->table->margin->bottom,
'margin-left' => $attrs->table->margin->left,
'padding-top' => $attrs->table->padding->top,
'padding-right' => $attrs->table->padding->right,
'padding-bottom' => $attrs->table->padding->bottom,
'padding-left' => $attrs->table->padding->left,
];
foreach ($spacingSides as $prop => $sideAttr) {
if ($sideAttr->isNotEmpty()) {
$figureStyles[] = $prop . ': ' . $sideAttr->asAttr();
}
}
$figureStyleAttr = !empty($figureStyles)
? "style='" . implode('; ', $figureStyles) . ";'"
: '';
$captionHtml = '';
if ($caption->isNotEmpty()) {
$captionHtml = "{$caption->asHtml()}";
}
$html =
"
{$rowsHtml}
{$captionHtml}
";
return $html;
}
/**
* @param bool $isWideWidth
* @param bool $isFullWidth
* @param bool $canApplyCustomWidth
* @param string $alignment
* @return string
*/
private function getWrapperAlignmentClass(
$isWideWidth,
$isFullWidth,
$canApplyCustomWidth,
$alignment
) {
if ($isWideWidth) {
return 'alignwide';
}
if ($isFullWidth) {
return 'alignfull';
}
if (!$canApplyCustomWidth) {
return '';
}
if ($alignment === 'left' || $alignment === 'right') {
return 'justify-table-' . $alignment;
}
if ($alignment === 'center') {
return 'justify-table-center';
}
return '';
}
/**
* @param TableAttrs $attrs
* @param int $rows
* @param int $cols
* @return string
*/
private function buildResponsiveDataAttrs($attrs, $rows, $cols) {
$tablet = $attrs->table->responsive->tablet;
$mobile = $attrs->table->responsive->mobile;
$tabletEnabled = $tablet->enabled->value();
$mobileEnabled = $mobile->enabled->value();
if (!$tabletEnabled && !$mobileEnabled) {
return '';
}
$tabletMaxWidth = max(1, (int) $tablet->maxWidth->value());
$mobileMaxWidth = max(1, (int) $mobile->maxWidth->value());
$tabletStackCount = max(1, (int) $tablet->stackCount->value());
$mobileStackCount = max(1, (int) $mobile->stackCount->value());
// Repeating the first column in every stack row is a pro option. Only
// the licensed pro plugin turns this filter on, so the saved value is
// ignored without a valid licence.
$proActive = (bool) apply_filters('tableberg/is_pro_runtime_active', false);
$tabletRepeatFirstCol = $proActive ? $tablet->repeatFirstCol->asAttr() : '';
$mobileRepeatFirstCol = $proActive ? $mobile->repeatFirstCol->asAttr() : '';
return "
data-tableberg-responsive='true'
data-tableberg-rows='{$rows}'
data-tableberg-cols='{$cols}'
data-tableberg-tablet-enabled='{$tablet->enabled->asAttr()}'
data-tableberg-tablet-width='{$tabletMaxWidth}'
data-tableberg-tablet-mode='{$tablet->mode->asAttr()}'
data-tableberg-tablet-transpose='{$tablet->transpose->asAttr()}'
data-tableberg-tablet-count='{$tabletStackCount}'
data-tableberg-tablet-repeat-first-col='{$tabletRepeatFirstCol}'
data-tableberg-mobile-enabled='{$mobile->enabled->asAttr()}'
data-tableberg-mobile-width='{$mobileMaxWidth}'
data-tableberg-mobile-mode='{$mobile->mode->asAttr()}'
data-tableberg-mobile-transpose='{$mobile->transpose->asAttr()}'
data-tableberg-mobile-count='{$mobileStackCount}'
data-tableberg-mobile-repeat-first-col='{$mobileRepeatFirstCol}'
";
}
private function getCell($row, $col, $cells) {
$key = $row . ',' . $col;
if (!is_array($cells) || !array_key_exists($key, $cells)) {
return null;
}
return $cells[$key];
}
private function getCellElements($cell) {
if ($this->isCellEmpty($cell)) {
return [];
}
return $cell instanceof CellData && is_array($cell->elements)
? $cell->elements
: [];
}
/**
* "Empty cell" is a pro feature: the cell renders as if it were a bare,
* unstyled table cell — no content, no background, no border. Free's
* default is to always show the cell as configured.
*
* @param mixed $cell
* @return bool
*/
private function isCellEmpty($cell) {
if (!$cell instanceof CellData) {
return false;
}
return (bool) apply_filters('tableberg/cell_is_empty', false, $cell->attrs);
}
private function getCellStyleOverride($cell) {
if (!$cell instanceof CellData || !is_array($cell->styles)) {
return null;
}
return StringAttr::fromNestedArray($cell->styles);
}
private function getCellRibbon($cell) {
if (!$cell instanceof CellData || !is_array($cell->ribbon)) {
return null;
}
return $cell->ribbon;
}
/**
* @param CellData|null $cell
* @return string|null
*/
private function getCellClassName($cell) {
if (!$cell instanceof CellData || !$cell->className instanceof StringAttr) {
return null;
}
$className = $cell->className->asAttr();
return $className === '' ? null : $className;
}
private function getCellSpan($cell) {
$span = ['rowSpan' => 1, 'colSpan' => 1];
if ($cell instanceof CellData && $cell->span instanceof Span) {
$span['rowSpan'] = $cell->span->rowSpan->value();
$span['colSpan'] = $cell->span->colSpan->value();
}
return $span;
}
/**
* @param int $column
* @param array $columns
* @return string|null
*/
private function getColumnWidth($column, $columns) {
if (!is_array($columns) || !array_key_exists($column, $columns)) {
return null;
}
$columnConfig = $columns[$column];
if (!$columnConfig instanceof ColumnConfig || $columnConfig->width === null) {
return null;
}
$width = $columnConfig->width->asAttr();
return $width === '' ? null : $width;
}
/**
* @param int $row
* @param array $rowConfigs
* @return string|null
*/
private function getRowHeight($row, $rowConfigs) {
if (!is_array($rowConfigs) || !array_key_exists($row, $rowConfigs)) {
return null;
}
$rowConfig = $rowConfigs[$row];
if (!$rowConfig instanceof RowConfig || $rowConfig->height === null) {
return null;
}
$height = $rowConfig->height->asAttr();
return $height === '' ? null : $height;
}
/**
* Pro styling of a row (e.g. background colour). One filter carries
* every pro row style, mirroring `tableberg/cell_styles`, so a new pro
* style needs no change here.
*
* @param int $row
* @param array $rowConfigs
* @return array
*/
private function getRowStyles($row, $rowConfigs) {
$rowAttrs = (
is_array($rowConfigs) &&
array_key_exists($row, $rowConfigs) &&
$rowConfigs[$row] instanceof RowConfig
) ? $rowConfigs[$row]->attrs : [];
$styles = apply_filters('tableberg/row_styles', [], $rowAttrs);
return is_array($styles) ? $styles : [];
}
}