Skip to content

feat(table): make the wrap argument work with data tables#2018

Merged
markdumay merged 12 commits into
mainfrom
feat/table-wrap-datatables
Jul 14, 2026
Merged

feat(table): make the wrap argument work with data tables#2018
markdumay merged 12 commits into
mainfrom
feat/table-wrap-datatables

Conversation

@markdumay

Copy link
Copy Markdown
Collaborator

The table shortcode's wrap argument moves a wide table's last column onto a full-width row of its own below the site's main breakpoint. It could not be combined with sortable / paginate / searchable — mod-utils documented the incompatibility explicitly. This makes them work together, and rewrites the plain path to render one table instead of two.

The problem

wrap rendered the table twice: a regular copy shown at or above the main breakpoint, and a pre-split "wrapped" copy shown below it. Both copies carried the .data-table class, so simple-datatables initialised on both — and the wrapped copy's ragged DOM (N−1 header cells, alternating N−1 / 1-cell rows) cannot be represented by its uniform row model.

The change

Plain wrapped table → one table carrying both layouts. The last <th> and <td> get d-none d-{bp}-table-cell, the other cells get table-border-bottom-wrap, and an extra <tr class="d-{bp}-none"> with <td colspan="N-1"> follows each data row. Still pure CSS and static HTML — it works with JavaScript disabled, which is why it stayed a render hook.

Wrapped data table → rendered uniformly and marked data-table-wrap / data-table-wrap-breakpoint. mod-simple-datatables (gethinode/mod-simple-datatables#277) then builds the wrapped layout in the browser via simple-datatables' tableRender hook, which rewrites the virtual DOM only — so sorting, searching and paging keep operating on an unmodified row model.

Beyond enabling the combination, this parses the Markdown once instead of twice, halves the markup of a wrapped table, and stops Flexsearch indexing every wrapped table twice.

Things worth knowing in review

  • Striping had to be recomputed. Two rows per record means Bootstrap's .table-striped (tr:nth-of-type(odd), which counts hidden rows too) would stripe every record instead of alternating. _table.scss now stripes per record pair (4n + 1, plus 4n + 2 below the breakpoint where the second row is visible).
  • d-{bp}-table-cell is safelisted. render-table.html is its only emitter in the theme, and on the eager-PostCSS path PurgeCSS reads the previous build's hugo_stats.json. Without the safelist entry the class is purged on a site's first build after upgrade, hiding the wrapped column at every width. Added to both postcss configs — exampleSite/ has its own tracked copy that had drifted.
  • xs guard. The old code emitted d-xs-none, which Bootstrap never generates. wrap is now a no-op at xs.
  • table-wrap is an internal marker the templates own; callers can no longer inject it and misfire the striping override.

Release ordering

The intermediate state is safe in both directions: with the currently vendored module, a wrapped data table simply renders as a normal unwrapped data table (data-table-wrap is an unread attribute). mod-simple-datatables needs to ship and be re-vendored before wrapped data tables work on a real site.

Verification

exampleSite/content/en/table-demo.md is the fixture. Verified in a real browser: wrapped data tables initialise with zero console errors, the pager counts records rather than rendered rows, sorting keeps each description with its record, search still matches the wrapped column, and sort/page/search survive a breakpoint crossing. The plain path was verified with JavaScript disabled at both widths, with striping confirmed per record via computed box-shadow.

Known gap, not addressed

layouts/_shortcodes/table.html never forwards filter / filter-col to the partial, so the category filter is unreachable from Markdown even though data/structures/table.yml declares both. Pre-existing; worth a follow-up to wire them through or drop them.

🤖 Generated with Claude Code

markdumay and others added 10 commits July 14, 2026 08:11
Design to make the table `wrap` argument compatible with data tables:
- Hinode renders a single table (one RenderString) for both wrap paths
- Plain tables carry both layouts via responsive display utilities
- Data tables get the wrapped layout from a simple-datatables tableRender
  hook, gated on matchMedia and re-rendered with dt.update(true)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Six tasks across hinode, mod-simple-datatables and mod-utils, each ending in an
independently verifiable deliverable.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Exercises a plain wrapped table with striping, a wrapped data table, and a
two-column wrapped table. Regression target for the wrap rework.
A workspace use directive alone does not override mod-simple-datatables, so a
replacements line is needed too. Both files must be reverted before merge.
A wrapped table will render as a single table serving both widths, so the
border suppression has to be scoped to the breakpoint rather than applied
unconditionally, and Bootstrap's odd-row striping has to be recomputed per
record pair. Safelist both wrap classes: on a data table they are only ever
added by JavaScript.
A wrapped table was rendered twice - once regular, once wrapped - and both
copies were emitted into the page. Render it once instead: a plain table now
carries both layouts and switches with display utilities, and a data table is
rendered uniformly and marked with data-table-wrap so simple-datatables can
apply the wrapped layout itself.

This halves the markup of a wrapped table, parses the Markdown once instead of
twice, and stops Flexsearch indexing every wrapped table twice.
A wrapped data table gets its layout built in the browser, so the `d-none` that hides the
wrapped column's heading is emitted by JavaScript alone. On a site whose only wrapped tables
are data tables the class never reaches hugo_stats.json and PurgeCSS would strip it, leaving
the heading visible below the breakpoint.

It survives today only incidentally, because the navbar chrome happens to emit it. Safelist it
explicitly alongside table-wrap and table-border-bottom-wrap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mod-simple-datatables declares integration = "optional", so Hinode only loads
its JavaScript for pages that list it in frontmatter. Without this the fixture's
data table was inert - no DataTable instance, no pager - so the wrapped
data-table path was never actually exercised.
…p marker

`d-{breakpoint}-table-cell` is emitted only by render-table.html, for a plain wrapped table.
PurgeCSS is fed the previous build's hugo_stats.json whenever PostCSS is not deferred to Hugo's
post-process phase (`hugo server`, or any non-production build with style.purge enabled). A site
whose committed stats predate its first wrapped table therefore lost the rule: the wrapped
column's cells kept only `d-none` and stayed hidden at every width, while the colspan rows showed
at every width. Safelist the utility for every breakpoint infix.

Correct the group's comment while here. `d-none` is emitted by a dozen core layouts and can never
actually be purged, so it is listed only to record the dependency, not because JavaScript adds it.
The exampleSite carries its own PostCSS config and had never received the wrap safelist at all, so
apply the group there too.

`table-wrap` is an internal marker that keys the wrap-specific striping rules, but the partial
handed ownership back to the caller. `class="table-wrap"` without `wrap` striped a uniform table by
record pairs, and a `{.table-wrap}` attribute block on a sortable table made the render hook split
its rows server-side - the very layout simple-datatables must never be given. Filter the marker out
of the caller's class list, and flag a data table through the page store so the render hook ignores
the attribute there.
@netlify

netlify Bot commented Jul 14, 2026

Copy link
Copy Markdown

Deploy Preview for gethinode-demo ready!

Name Link
🔨 Latest commit cd34f66
🔍 Latest deploy log https://app.netlify.com/projects/gethinode-demo/deploys/6a5615db0c8a2900089fe123
😎 Deploy Preview https://deploy-preview-2018--gethinode-demo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Picks up the corrected wrap argument comment, which no longer claims that wrap
is incompatible with data tables. The exampleSite resolves the same version
through the committed workspace, so its own pin needs no change.
@markdumay

Copy link
Copy Markdown
Collaborator Author

Rebased onto the released mod-utils v6.4.2 (cfdacb71), which carries the corrected wrap argument comment from gethinode/mod-utils#344. exampleSite/go.mod is deliberately untouched — it resolves the same version through the committed workspace, and running hugo mod get there would expand its whole transitive graph.

@markdumay markdumay merged commit eef072e into main Jul 14, 2026
16 checks passed
@markdumay

Copy link
Copy Markdown
Collaborator Author

🎉 This PR is included in version 3.0.6 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@markdumay markdumay deleted the feat/table-wrap-datatables branch July 14, 2026 11:04
markdumay added a commit that referenced this pull request Jul 14, 2026
The wrap argument now combines with sortable, paginate and searchable. Hinode
renders a wrapped data table uniformly and marks it with data-table-wrap;
mod-simple-datatables v4.1.0 applies the wrapped layout below the main
breakpoint through simple-datatables' tableRender hook, so sorting, searching
and paging keep operating on an unmodified row model.

Plain wrapped tables are now rendered once rather than twice, which halves their
markup, parses the Markdown once, and stops Flexsearch indexing them twice.

The work shipped across v3.0.6 and this release; see #2018 and #2020. Recorded
as a feature so the changelog reflects the new capability.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant