Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
7fefdfb
feat(grid)!: replace frozen panes with pinning and sticky docking
ghiscoding Sep 17, 2026
8faa2f0
chore: fix cypress failures
ghiscoding Sep 17, 2026
0b58e87
chore: colspan and grouping should spread left to right over pinning
ghiscoding Sep 18, 2026
0406629
chore: part 1 fixes of audit review
ghiscoding Sep 18, 2026
6974967
chore: part 2 fixes of audit review
ghiscoding Sep 18, 2026
82af224
chore: part 3 fixes of audit review
ghiscoding Sep 18, 2026
3f018a2
chore: part 4 final pass fixes of audit review
ghiscoding Sep 18, 2026
8851bb9
chore: fix cypress failing tests
ghiscoding Sep 18, 2026
5611dd2
chore: fix cypress failing tests
ghiscoding Sep 18, 2026
71e2acf
chore: fix cypress failing tests
ghiscoding Sep 18, 2026
461d557
fix(grid): drop DOM references reflectively in destroy(true)
6pac-ai Sep 18, 2026
98b28c8
refactor(grid): remove slickgrid-universal plumbing that is inert in …
6pac-ai Sep 18, 2026
579b651
fix(grid): resolve getCellFromPoint through the rendered docking layout
6pac-ai Sep 18, 2026
9310336
fix(grid): keep every scrolling row reachable above a bottom-pinned band
6pac-ai Sep 18, 2026
05bd193
fix(grid): mirror cell CSS class changes onto cross-band colspan frag…
6pac-ai Sep 18, 2026
7116e94
fix(grid): announce chrome cells before docking activation empties them
6pac-ai Sep 18, 2026
5bc9e21
fix(grid): virtualize the centre cells of docked rows horizontally
6pac-ai Sep 18, 2026
15f74ab
fix(grid): forward native chrome scrolls as deltas in proxy mode
6pac-ai Sep 18, 2026
b991eca
fix(grid): map column reorder slots to the DOM band of each header
6pac-ai Sep 18, 2026
a4ca2e7
test(examples): keep the header-menu demo command wide enough for the…
6pac-ai Sep 18, 2026
85d0534
docs: document pinning/sticky docking for this repository and add the…
6pac-ai Sep 18, 2026
8bac8e7
fix(grid): row references by id, null pinning removal, mirrored botto…
6pac-ai Sep 20, 2026
6b607e3
fix(grid): restore 6pac API contracts and make setColumns() report re…
6pac-ai Sep 20, 2026
4d624f4
refactor(grid): drop the fork's keyboard focus routing, keep the base…
6pac-ai Sep 20, 2026
ef7382f
perf(grid): take the docking hot paths off O(n²) lookups and layout t…
6pac-ai Sep 20, 2026
68b6ca6
refactor(grid): one Utils.replaceList() helper for the setOptions() l…
6pac-ai Sep 20, 2026
72f84ca
refactor(grid): drop unused public methods and the duplicated top panel
6pac-ai Sep 20, 2026
eb54342
docs(grid): condense narrative docking comments to their intent
6pac-ai Sep 20, 2026
0283c80
test: restore the assertions the pinning rewrite weakened
6pac-ai Sep 20, 2026
9d21700
fix(examples): follow the data with pinned rows, and drop the last fr…
6pac-ai Sep 20, 2026
f8acf05
refactor(examples): keep the dev server's CSP needs out of the CSP ex…
6pac-ai Sep 20, 2026
93c5b23
docs: record the row reference forms, the API restorations and the RT…
6pac-ai Sep 20, 2026
53e06a5
test: rename the last frozen-named spec to match the example it drives
6pac-ai Sep 20, 2026
97c976f
fix(grid): clip a cross-band colspan instead of painting it over the …
6pac-ai Sep 22, 2026
1c5b5e0
fix(grid): follow a docked centre column that is resized past the vie…
6pac-ai Sep 22, 2026
ea90c98
test: stop forcing clicks that the runner's own pre-click scroll was …
6pac-ai Sep 22, 2026
a4ad004
fix(grid): correct four docking geometry measurements
6pac-ai Sep 22, 2026
a0e74ef
perf(grid): publish the docking scroll offset once instead of per ele…
6pac-ai Sep 22, 2026
5f31cc2
perf(grid): measure a chrome cell's box once per class signature
6pac-ai Sep 22, 2026
c62a02d
fix(grid): drop the colspan host fallback and let auto header height …
6pac-ai Sep 22, 2026
01ccc14
fix(examples): survive a column-picker hide from the pre-header
6pac-ai Sep 22, 2026
d517c05
fix(grid): draw a cross-band colspan as one cell at the boundary
6pac-ai Sep 23, 2026
3cb40e1
refactor(grid): drop the pre-proxy horizontal scroll path
6pac-ai Sep 23, 2026
9e498fa
refactor(grid): raise the pinning rejection alert from one place
6pac-ai Sep 23, 2026
e672f2f
test(grid): assert the docking invariant the single scroll path actua…
6pac-ai Sep 24, 2026
d17e104
refactor(grid): drop three redundancies found by the LOC audit
6pac-ai Sep 24, 2026
fc96bca
perf(core): reconcile two-sided sticky columns in one pass
6pac-ai Sep 24, 2026
b742390
fix(grid): stop the proxy scrollbar painting over the last row after …
6pac-ai Sep 24, 2026
42a7104
fix(grid): reveal Firefox/Linux scrollbar beside docked rows
ghiscoding Sep 24, 2026
d3d6d7e
feat(grid): support pinned and sticky columns in right-to-left grids
6pac-ai Sep 22, 2026
5da524f
feat(grid): cover a cross-band colspan in RTL and pin the direction t…
6pac-ai Sep 24, 2026
4c0f178
fix(grid): render docked rows beneath Firefox scrollbar
ghiscoding Sep 25, 2026
6eef761
refactor(grid): place sticky and centre chrome through one branch
6pac-ai Sep 25, 2026
441ead1
fix(grid): never render docked rows without their scroll owner
6pac-ai Sep 25, 2026
c0704fe
refactor(grid): remove the sticky reparenting renderer
6pac-ai Sep 25, 2026
116e7b3
test(grid): assert the sticky docked-row overlay, not the clipped one
6pac-ai Sep 25, 2026
096fe23
fix(grid): keep docked sticky cells opaque on unstriped rows
6pac-ai Sep 25, 2026
baabece
fix(grid): mask scrollbar strip in docked headers
ghiscoding Sep 26, 2026
3854c36
fix(grid): align sticky columns during resize
ghiscoding Sep 26, 2026
7d9555c
fix(grid): keep the grid menu button above the header scrollbar cover
6pac-ai Sep 26, 2026
e97ba48
fix(grid): draw pinning separators on the inner edge in right-to-left…
6pac-ai Sep 28, 2026
63d0f1e
chore: add few missing example links
ghiscoding Sep 28, 2026
b8a4bf7
chore(tests): remove invalid test
ghiscoding Sep 28, 2026
30d04a6
Merge branch 'next-v6' into feat/pinning-sticky
6pac-ai Sep 29, 2026
dab6839
docs: replace the frozen-pane pages with pinning and sticky docking
6pac-ai Sep 29, 2026
0b46fe2
fix(grid): keep the pinned band's header separator when a sticky colu…
6pac-ai Sep 29, 2026
8d1f9c8
fix(grid): keep a pinned column in its band whatever its sticky flag …
6pac-ai Sep 29, 2026
ef03184
perf(grid): remove dead scroll-time transform writes and a quadratic …
6pac-ai Sep 29, 2026
24f7d25
fix(grid): align the pinning option contracts with Slickgrid-Universal
6pac-ai Sep 30, 2026
030ed46
fix(grid): re-place docked chrome on resize and apply rtl before the …
6pac-ai Sep 30, 2026
d03d4d0
test(grid): measure docked chrome against the body cells in the contr…
6pac-ai Sep 30, 2026
fdb7b21
fix(grid): keep pinned band geometry after restoring pinning and whil…
6pac-ai Oct 2, 2026
ee393e9
style(grid): simplify pinned docking rules
6pac-ai Oct 2, 2026
3d29ae4
Merge branch 'next-v6' into feat/pinning-sticky
6pac-ai Oct 2, 2026
336ddf5
perf(grid): skip the docked geometry refresh when only the row count …
6pac-ai Oct 2, 2026
4e41ffa
perf(grid): cache unresolved pinned and sticky row references
6pac-ai Oct 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .agents/plans/pinning-sticky-progress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Single-viewport pinning/stickiness — implementation status

Last updated: 2026-09-18.

This file records the state of the pinning/sticky docking rewrite **in this repository** (the
flat 6pac/SlickGrid tree). It was originally an implementation log from the slickgrid-universal
fork; everything that only applied there (framework demos, unit-test counts, Grid State/Service
plumbing, Header Menu commands, locale strings, migration guides) has been removed. Treat
`src/`, `cypress/e2e/` and `docs/pinning-sticky.md` as the source of truth.

## Goal

Replace the multi-pane frozen-column/row architecture with a single-viewport docking model:

- one live body viewport with one native vertical scrollbar; ordinary grids scroll horizontally
through the viewport, docking grids through one dedicated horizontal scrollbar;
- one virtualized DOM row per data row, each with stable left/centre/right cell regions;
- permanent pinning and scroll-activated stickiness resolved by the same internal controller;
- vertical and horizontal virtualization preserved for large datasets;
- an intentional major-version breaking change: no compatibility with the pane renderer.

## Implemented architecture

- `src/slick.core.ts` — `DockingController`, a DOM-free resolver for column and row bands
(permanent pins, sticky activation from natural geometry, viewport-percentage budgets,
`conveyor`/`clamp` overflow, revision counters). Exported as `Slick.DockingController`.
- `src/slick.grid.ts` — single viewport/canvas, per-row regions, header/header-row/footer
regions (`display: contents` wrappers inside the existing roots), the docking overlay for
pinned/sticky rows, the proxy horizontal scrollbar, transforms for chrome and pinned regions,
cross-band colspan host + fragments, docking-aware hit-testing (`getCellFromPoint`), runtime
API (`setColumnPinning`, `setColumnStickiness`, `getPinnedColumns`, `validateColumnPinning`)
and option handling (`pinning`, `stickyRows`, `docking`, `invalidColumnPinning*`).
- `src/styles/_slick-docking.scss` — region layout, overlay stacking, separators, sticky cues.
- `src/models/docking.interface.ts` — public option and layout types.

The `-1000px` header offset and `HEADER_WIDTH_SLACK` are gone; header, grouped-header and body
coordinates share one coordinate system. The legacy `frozen*` options, `.slick-pane*` DOM and
the right/bottom pane elements no longer exist.

## Public surface

See `docs/pinning-sticky.md` for the option semantics, runtime API, selectors and the migration
table. Key rules:

- numeric column references are indexes (shorthands count visible columns); string references
are column ids;
- numeric row references are indexes; string references are dataset ids via the DataView;
- sticky state is never serialized; permanent pinning is what applications persist;
- `setOptions` replaces the pinning/sticky arrays atomically; `setOptions({ pinning: undefined })`
removes docking and tears the proxy scrollbar and chrome regions down again.

## Verification

- `npm run build:prod` (type-check, lint, bundles, CSS, types) must pass.
- Browser coverage (Cypress): `example-pinning-*`, `example-sticky-financial-report`,
`example-colspan` (pinned colspans), `example-variable-row-height-*`, `example-auto-scroll-when-dragging`
(pinned drag auto-scroll), `example11-autoheight`, and the self-hosted `quirk-pinning-*` /
`quirk-sticky-*` harnesses (row boundary, empty configs, bottom hit-testing and cleanup,
bottom-pin reachability, docked-row cell virtualization, hit-testing geometry, chrome scroll
forwarding, lazy activation destroy events, sticky column reorder, destroy references).
- CI runs on Linux/Chrome; menu-alignment specs are geometry sensitive and are also checked on
Windows font metrics.

## Resolved during review (2026-09-18)

- Column shorthands/arrays resolve to indexes only and over visible columns; numeric ids no
longer collide with index references.
- Row references match by index or string id; the id→index cache is cleared on row invalidation;
the DataView id property is honoured.
- Sticky-row thresholds account for the permanent top band and no longer subtract the bottom band
twice; `conveyor` keeps the newest candidates on every edge.
- `autoHeight` grids size the container once (no header-height band); the vertical wheel is only
intercepted on docking grids; Ctrl/Meta+drag multi-selection follows the selection model again;
`absBox()`/editor positions are document-relative again.
- `getCellFromPoint()` resolves through the rendered layout (bands, overlay, non-contiguous
pins, unrendered rows); `CellRangeSelector` prefers the event target.
- Bottom-pinned rows keep every scrolling row (including the add-new row) reachable.
- Docked rows virtualize their centre cells horizontally; chrome cells fire their destroy events
on lazy activation/deactivation; native chrome scrolls are forwarded as deltas; reordering
works while a sticky column is docked; cell CSS classes are mirrored onto colspan fragments;
`destroy(true)` drops element references reflectively.
- slickgrid-universal-only options and code paths were removed; the legacy `frozen*` options are
gone from the types; the header-menu demo command is "Column Pinning".

## Known limitations and follow-ups

- A colspan host that starts in a pinned band paints across the boundary and stays with its band
while the centre scrolls (centre cells that scroll under it are covered). Clipping the host and
letting the fragment carry the text is the alternative if this is not the wanted look.
- Sticky columns in RTL are not covered by browser tests.
- Sticky group headers (a grouped header that stays visible as a unit) are not supported.
- Focus sinks live outside the grid container (`tabIndex -1`); keyboard routing (Shift+Tab into
header-row filters, F6 to the header) came from the fork and was removed during the audit; the
base focus sinks and `navigatePrev()` handle Tab and Shift+Tab again
with `tabIndex="0"` that the plugins here do not produce.
- Fast vertical-scroll blanking is a separate virtual-rendering task.
- Per-scroll work on row-docking grids (`syncDockedRowContainers` on every vertical scroll,
per-row custom-property writes on horizontal scroll) can be reduced further.

## Resume checklist

1. Read `docs/pinning-sticky.md`, then the relevant `src/` code and the specs listed above.
2. Preserve the invariants: one native horizontal and one native vertical scroll owner, one row
node with three regions, one shared `DockingController`, no legacy compatibility branches.
3. Add a self-hosted `quirk-*` spec for any regression fixed; run `npm run build:prod` and the
affected Cypress specs before committing.
15 changes: 15 additions & 0 deletions .agents/skills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Repository AI Skills

Portable project skills live under `.agents/skills/<skill-name>/SKILL.md` and can be discovered
by Cursor and other Agent Skills-compatible tools.

Available portable skills

- [`pinning-sticky/SKILL.md`](pinning-sticky/SKILL.md) — configuration and maintenance guidance
for permanent pinning and sticky docking.

New cross-agent skills should use the portable structure above.

Project implementation plans live under `.agents/plans/`:

- [`pinning-sticky-progress.md`](../plans/pinning-sticky-progress.md)
82 changes: 82 additions & 0 deletions .agents/skills/pinning-sticky/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
name: pinning-sticky
description: Configure, document, review, or change SlickGrid permanent pinning and scroll-activated sticky docking.
---

# Pinning and Sticky Docking

Use this skill when configuring, documenting, reviewing, or changing SlickGrid permanent pinning
or scroll-activated sticky docking. The user-facing reference is `docs/pinning-sticky.md`; keep
it and this file in agreement.

## Repository layout

This repository is the flat SlickGrid source tree. Use `src/` for library code, `examples/` for
demos and `cypress/e2e/` for browser tests. There is no unit-test runner; `tests/` holds legacy
manual benchmark pages. Paths such as `packages/common/`, `demos/vanilla/` or framework demo
packages belong to the slickgrid-universal fork and do not exist here.

## Canonical configuration

- Use the nested `GridOption.pinning` shape for permanent pins:
`columns.left/right` and `rows.top/bottom`.
- Column references: a number is an inclusive left boundary or a right count over the visible
columns; an array holds column indexes (numbers) and/or column ids (strings) and may be
non-contiguous, for example `columns.left: ['account', 'status']`.
- Row references take three forms: a number is always a row index, a string is a dataset id, and
`{ id: <value> }` is a dataset id of any type, which is how a grid with numeric ids pins by id.
Id references follow their row through a sort or filter; index references do not, so a caller
pinning a positional row such as the last one must recompute it when the row count changes.
Non-contiguous rows are valid, for example `rows.top: [0, 2, 4]`.
- `setOptions({ pinning: null })` and `setOptions({ pinning: undefined })` both clear pinning.
- `Column.pinned` is the per-column permanent-pin form and is kept in sync with the option.
There is no `Column.pinnable`; menus are application code built on `setColumnPinning()`.
- Reordering stays within a band; pinning and unpinning are explicit through configuration, the
runtime API or application menus. Never infer pinning from a drag across bands.

## Sticky behavior

- Use `Column.sticky` and `GridOption.stickyRows` for scroll-activated docking. Sticky state is
scroll-dependent and is never serialized; permanent pinning is what an application persists.
- Multiple active top sticky rows stack in natural dataset order. They do not push each other out.
- Sticky row capacity uses the current viewport, not a fixed row count. The default row budget is
60% of viewport height after permanent pinned rows are accounted for, and measured row heights
determine how many candidates fit. `docking.maxRowViewportHeightPercent` and
`docking.overflowStrategy` control this behavior.
- Permanent pinned rows keep their slot in the dataset height; rows after a pin are rendered so
the pinned slot collapses under the band, and the last scrolling row stays reachable.
- Both bands nest the same way: permanent rows sit at the outer edge and active sticky rows stack
inside them, so a sticky bottom row sits above a permanently pinned bottom row.
- `docking.stickyActivationBuffer` (default 2px) is the column activation buffer; rows dock on the
exact boundary.
- Pinning and sticky docking work in both reading directions. The band names follow the reading
order, not the screen: `left` is the leading band and renders at the right edge of an RTL grid,
`right` is the trailing band. `sticky: true` docks at the leading band either way, so the same
options describe both directions. Distances inside the docking code are measured along the
inline axis; `getInlineDirection()` converts them to physical pixels where a style needs one.

## Maintenance verification

When changing this feature:

1. Check the local interfaces and implementation first:
`src/models/docking.interface.ts`, `src/models/gridOption.interface.ts`, `src/slick.grid.ts`
(rendering, scrolling, hit-testing, options), `src/slick.core.ts` (`DockingController`), and
`src/styles/_slick-docking.scss`.
2. Update `docs/pinning-sticky.md` for any option, selector or behavior change.
3. Add or update browser coverage under `cypress/e2e/`: the `example-pinning-*`,
`example-sticky-*` and `quirk-pinning-*` specs, plus `example-colspan.cy.ts` for cross-band
colspans. Self-hosted `quirk-*` harnesses (a page served through `cy.intercept`) are the
pattern for focused regressions.
4. `DockingController` is exported (`Slick.DockingController`, ESM `DockingController`) but is
an implementation detail; do not extend its public surface without an explicit API decision.
5. Keep fast vertical-scroll blanking as a separate virtual-rendering task; do not conflate it
with sticky-row activation or docking-layout refresh.
6. Verify with `npm run build:prod` and the Cypress suite; CI runs on Linux, so re-check
geometry-sensitive specs (menus, alignment) on Windows font metrics when they change.

## Source documentation

- [Pinning and sticky docking](../../../docs/pinning-sticky.md)
- [Implementation status](../../plans/pinning-sticky-progress.md)
- [Documentation README](../../../docs/README.md)
10 changes: 10 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Repository Agent Instructions

## Generated files

- Everything under `dist/` is build output. Never write or hand-edit those files:
produce them by running the project build (`npm run build:prod`) instead.
- `dist/` is refreshed on release commits, so keep it out of feature and fix
commits even after a local build has rewritten it.
- The build output itself is disposable. It is regenerated from `src/` at any
time, so there is nothing in `dist/` worth preserving across a rebuild.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ This repo builds on the legacy of the [mleibman/SlickGrid](https://github.com/ml

We extended the project from the original SlickGrid foundation while also including the following changes:
- added a few more Plugins: RowDetail, CellMenu, ContextMenu, GridMenu, CustomTooltip, GridState
- merged [X-SlickGrid](https://github.com/ddomingues/X-SlickGrid) code into the project to bring Frozen Columns/Rows (aka Pinning)
- merged [X-SlickGrid](https://github.com/ddomingues/X-SlickGrid) code into the project to bring permanent column/row pinning
- removed jQueryUI requirement in [v3](https://github.com/6pac/SlickGrid/wiki/Major-version-3.0----Removal-of-jQueryUI-requirement-(replaced-by-SortableJS)) (replaced it with [SortableJS](https://sortablejs.github.io/Sortable/))
- removed jQuery requirement in [v4](https://github.com/6pac/SlickGrid/wiki/Major-version-4.0---Removal-of-jQuery-requirement)
- modernized the project in [v5](https://github.com/6pac/SlickGrid/wiki/Major-version-5.0-%E2%80%90-ES6-ESM-and-TypeScript-Support) by migrating to TypeScript (we kept IIFE and added ES6/ESM build targets) and we also gave SlickGrid a fresh and more modern look via a new Alpine Theme (CSS/SASS)
Expand Down
Loading
Loading