Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 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
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)
79 changes: 79 additions & 0 deletions .agents/skills/pinning-sticky/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
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 are LTR-only today: an RTL grid mixes the docking scrollbar with the
non-proxy geometry and places docked columns outside the viewport.

## 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