96 lines
2.9 KiB
Dart
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),
|
|
),
|
|
);
|
|
}
|
|
}
|