Files
Client/lib/widget/prosemirror/pm_table_view.dart
T

96 lines
2.9 KiB
Dart

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<PmTableRow>().toList();
if (rows.isEmpty) return const SizedBox.shrink();
final occupied = <int, Set<int>>{};
final placements = <Widget>[];
var columnCount = 0;
for (var r = 0; r < rows.length; r++) {
var col = 0;
for (final cell in rows[r].children.whereType<PmTableCell>()) {
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] ??= <int>{};
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),
),
);
}
}