UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

78 lines (77 loc) 4.25 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { createMarkupRule } from "../MarkupRule.js"; import { createBlockRegExp, LINE_SPACE_REGEXP } from "../util/regexp.js"; // Constants. const _SPACE = `${LINE_SPACE_REGEXP}*`; // Run of line whitespace (never crosses a newline). const _CELL = `${_SPACE}:?-+:?${_SPACE}`; // Delimiter-row cell: one or more dashes with optional `:` alignment markers. const _DELIMITER_SOURCE = `${_SPACE}\\|?(?:${_CELL}\\|)+(?:${_CELL})?${_SPACE}`; // Delimiter row: pipe-separated dash cells. const _DELIMITER = new RegExp(`^${_DELIMITER_SOURCE}$`, "u"); // Tests whether a single line is a delimiter row. const _ROW = "[^\\n]*\\|[^\\n]*"; // Any line containing at least one pipe. const _SPLIT = /(?<!\\)\|/; // Splits a row into cells on unescaped pipes. /** * Table. * - Markdown-style pipe table: a header row, a `|---|` delimiter row, then body rows. * - Cells are pipe-separated; outer pipes are optional and whitespace around cells is trimmed. * - Extra `|---|` delimiter rows split the table into sections: the first section becomes `<thead>`, the last becomes `<tfoot>` (only when there are three or more sections), and every section in between becomes its own `<tbody>`. * - Column count and per-column alignment (`:--` left, `--:` right, `:-:` centered) come from the first delimiter row; ragged rows are padded or truncated to that count. * - Cell content is rendered as inline markup; write `\|` for a literal pipe inside a cell. */ export const TABLE_RULE = createMarkupRule(createBlockRegExp(`(?<table>${_ROW}\\n${_DELIMITER_SOURCE}(?:\\n${_ROW})*)`), (key, { table }, parser) => { const lines = table.split("\n"); // Column count and alignment come from the first delimiter row — always line 1, guaranteed by `TABLE_REGEXP`. const aligns = _splitRow(lines[1] ?? "").map(_getAlign); // Split lines into sections at delimiter rows. Line 0 is the header and is never treated as a delimiter. const sections = []; let section = [lines[0] ?? ""]; for (let i = 1; i < lines.length; i++) { const line = lines[i] ?? ""; if (_DELIMITER.test(line)) { sections.push(section); section = []; } else { section.push(line); } } sections.push(section); // Build the table with explicit loops — markup elements are static and positional, so the loop index is the natural key. const last = sections.length - 1; const body = []; for (let s = 0; s < sections.length; s++) { // First section is `<thead>`; the last is `<tfoot>` with 3+ sections; sections in between are each a `<tbody>`. const Section = s === 0 ? "thead" : s === last && last >= 2 ? "tfoot" : "tbody"; const Cell = s === 0 ? "th" : "td"; const rowLines = sections[s] ?? []; const rows = []; for (let r = 0; r < rowLines.length; r++) { const values = _splitRow(rowLines[r] ?? ""); const cells = []; for (let c = 0; c < aligns.length; c++) { cells.push(_jsx(Cell, { align: aligns[c], children: parser.parse(values[c] ?? "", "inline") }, c)); } rows.push(_jsx("tr", { children: cells }, r)); } body.push(_jsx(Section, { children: rows }, s)); } // Scrollable region pattern: focusable, labelled <figure> wraps the table so keyboard users can arrow-scroll wide columns. return (_jsx("figure", { tabIndex: 0, role: "region", "aria-label": "Scrollable region", children: _jsx("table", { children: body }) }, key)); }, ["block"]); /** Split a table row into trimmed cell strings, honouring `\|` escaped pipes. */ function _splitRow(row) { let line = row.trim(); if (line.startsWith("|")) line = line.slice(1); if (line.endsWith("|")) line = line.slice(0, -1); return line.split(_SPLIT).map(cell => cell.trim().replaceAll("\\|", "|")); } /** Get the alignment of a delimiter-row cell, or `undefined` for the default (left). */ function _getAlign(cell) { const start = cell.startsWith(":"); const end = cell.endsWith(":"); if (start && end) return "center"; if (end) return "right"; return undefined; }