import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_layout_grid/flutter_layout_grid.dart'; import '../../theming/app_theme.dart'; import 'pm_document_view.dart'; import 'pm_node.dart'; /// Renders a ProseMirror table. Flutter's built-in `Table` cannot span cells, /// so `flutter_layout_grid` places each cell explicitly, honouring /// colspan/rowspan via a simple HTML-style occupancy scan. class PmTableView extends StatelessWidget { final PmTable node; static const double _minColumnWidth = 140; const PmTableView({required this.node, super.key}); @override Widget build(BuildContext context) { final rows = node.children.whereType().toList(); if (rows.isEmpty) return const SizedBox.shrink(); final occupied = >{}; final placements = []; var columnCount = 0; for (var r = 0; r < rows.length; r++) { var col = 0; for (final cell in rows[r].children.whereType()) { while (occupied[r]?.contains(col) ?? false) { col++; } placements.add( GridPlacement( columnStart: col, columnSpan: cell.colspan, rowStart: r, rowSpan: cell.rowspan, child: _cell(context, cell), ), ); for (var dr = 0; dr < cell.rowspan; dr++) { final set = occupied[r + dr] ??= {}; for (var dc = 0; dc < cell.colspan; dc++) { set.add(col + dc); } } col += cell.colspan; columnCount = max(columnCount, col); } } if (columnCount == 0) return const SizedBox.shrink(); return LayoutBuilder( builder: (context, constraints) { final available = constraints.maxWidth.isFinite ? constraints.maxWidth : _minColumnWidth * columnCount; final columnWidth = max(_minColumnWidth, available / columnCount); final totalWidth = columnWidth * columnCount; final grid = SizedBox( width: totalWidth, child: LayoutGrid( columnSizes: List.filled(columnCount, fixed(columnWidth)), rowSizes: List.filled(rows.length, auto), children: placements, ), ); if (totalWidth <= available) return grid; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: grid, ); }, ); } Widget _cell(BuildContext context, PmTableCell cell) { final theme = Theme.of(context); return DecoratedBox( decoration: BoxDecoration( color: cell.header ? theme.colorScheme.surfaceContainerHighest : null, border: Border.all(color: theme.colorScheme.outlineVariant), ), child: Padding( padding: const EdgeInsets.all(AppSpacing.sm), child: pmBlocks(cell.children, gap: AppSpacing.xs), ), ); } }