From 40e03636635594db2cc3dc0078db7f41d87f42d1 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Mon, 7 Sep 2026 21:16:45 -0400 Subject: [PATCH 01/48] feat(grid)!: replace frozen panes with pinning and sticky docking --- PINNING-POC-PROGRESS.md | 689 ++++++ .../aurelia/test/cypress/support/commands.ts | 6 +- demos/react/test/cypress/support/commands.ts | 6 +- demos/vanilla/src/app-routing.ts | 2 + demos/vanilla/src/app.html | 1 + demos/vanilla/src/examples/example03.html | 5 +- demos/vanilla/src/examples/example03.ts | 8 +- demos/vanilla/src/examples/example04.html | 26 +- demos/vanilla/src/examples/example04.ts | 78 +- demos/vanilla/src/examples/example05.html | 5 +- demos/vanilla/src/examples/example05.ts | 6 + demos/vanilla/src/examples/example06.html | 5 +- demos/vanilla/src/examples/example06.ts | 6 + demos/vanilla/src/examples/example08.ts | 11 +- demos/vanilla/src/examples/example09.html | 5 +- demos/vanilla/src/examples/example09.ts | 6 + demos/vanilla/src/examples/example10.html | 5 +- demos/vanilla/src/examples/example10.ts | 6 + demos/vanilla/src/examples/example11.ts | 10 +- demos/vanilla/src/examples/example13.ts | 3 +- demos/vanilla/src/examples/example17.ts | 27 +- demos/vanilla/src/examples/example32.ts | 8 +- demos/vanilla/src/examples/example43.html | 5 +- demos/vanilla/src/examples/example43.ts | 6 + demos/vanilla/src/examples/example44.html | 5 +- demos/vanilla/src/examples/example44.ts | 6 + demos/vanilla/src/examples/example45.html | 5 +- demos/vanilla/src/examples/example45.ts | 11 +- demos/vanilla/src/examples/example47.html | 31 + demos/vanilla/src/examples/example47.scss | 83 + demos/vanilla/src/examples/example47.ts | 212 ++ demos/vue/test/cypress/support/commands.ts | 6 +- .../test/cypress/support/commands.ts | 6 +- packages/common/src/constants.ts | 2 + packages/common/src/core/dockingController.ts | 421 ++++ packages/common/src/core/index.ts | 1 + packages/common/src/core/slickGrid.ts | 2171 ++++++++++++++--- packages/common/src/enums/gridState.type.ts | 2 +- .../__tests__/slickGridMenu.spec.ts | 7 +- .../src/extensions/slickDraggableGrouping.ts | 21 +- .../common/src/extensions/slickGridMenu.ts | 16 +- .../common/src/extensions/slickHeaderMenu.ts | 107 +- packages/common/src/global-grid-options.ts | 1 + .../common/src/interfaces/column.interface.ts | 10 + .../src/interfaces/currentColumn.interface.ts | 5 + .../interfaces/currentPinning.interface.ts | 12 +- .../src/interfaces/docking.interface.ts | 69 + .../src/interfaces/gridOption.interface.ts | 14 +- .../interfaces/headerMenuLabel.interface.ts | 6 + .../interfaces/headerMenuOption.interface.ts | 11 +- packages/common/src/interfaces/index.ts | 1 + .../common/src/interfaces/locale.interface.ts | 6 + packages/common/src/services/grid.service.ts | 17 +- .../common/src/services/gridState.service.ts | 58 +- .../src/services/headerGrouping.service.ts | 2 +- packages/common/src/services/utilities.ts | 5 + packages/common/src/styles/slick-grid.scss | 286 ++- .../src/slick-empty-warning.component.ts | 29 +- test/cypress/e2e/example03.cy.ts | 13 +- test/cypress/e2e/example04.cy.ts | 504 ++-- test/cypress/e2e/example08.cy.ts | 134 +- test/cypress/e2e/example11.cy.ts | 337 ++- test/cypress/e2e/example17.cy.ts | 92 +- test/cypress/e2e/example19.cy.ts | 13 +- test/cypress/e2e/example32.cy.ts | 340 +-- test/cypress/e2e/example33.cy.ts | 19 +- test/cypress/e2e/example45.cy.ts | 27 +- test/cypress/e2e/example46.cy.ts | 8 +- test/cypress/e2e/example47.cy.ts | 179 ++ ...ng in frozen grid (failed) (attempt 2).png | Bin 0 -> 180494 bytes ...ng in frozen grid (failed) (attempt 3).png | Bin 0 -> 181553 bytes ... when dragging in frozen grid (failed).png | Bin 0 -> 181751 bytes ...and keep expected data shapes (failed).png | Bin 0 -> 170665 bytes test/cypress/support/commands.ts | 26 +- test/cypress/support/drag.ts | 27 +- 75 files changed, 5081 insertions(+), 1218 deletions(-) create mode 100644 PINNING-POC-PROGRESS.md create mode 100644 demos/vanilla/src/examples/example47.html create mode 100644 demos/vanilla/src/examples/example47.scss create mode 100644 demos/vanilla/src/examples/example47.ts create mode 100644 packages/common/src/core/dockingController.ts create mode 100644 packages/common/src/interfaces/docking.interface.ts create mode 100644 test/cypress/e2e/example47.cy.ts create mode 100644 test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed) (attempt 2).png create mode 100644 test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed) (attempt 3).png create mode 100644 test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed).png create mode 100644 test/cypress/screenshots/example37.cy.ts/Example 37 - Hybrid Selection Model -- Grid 1 -- should export Grid 1 to Excel without hidden columns and keep expected data shapes (failed).png diff --git a/PINNING-POC-PROGRESS.md b/PINNING-POC-PROGRESS.md new file mode 100644 index 0000000000..533bbe1357 --- /dev/null +++ b/PINNING-POC-PROGRESS.md @@ -0,0 +1,689 @@ +# Single-viewport pinning/stickiness POC — progress handoff + +Last updated: 2026-09-07 (safe LOC cleanup complete; legacy freeze removal explicitly scheduled immediately after the first PR) + +## Goal + +Replace SlickGrid's multi-pane frozen-column/frozen-row architecture with an AG Grid-style docking model: + +- exactly one live body viewport with one native horizontal scrollbar and one native vertical scrollbar; +- one virtualized DOM row per data row; +- each rendered row contains stable sibling left, center, and right cell regions; +- permanent pinning and scroll-activated stickiness use the same internal docking resolver; +- vertical and horizontal virtualization must remain viable for large datasets; +- this is intentionally a major-version breaking change; compatibility with the old freeze renderer is not a design goal. + +The POC supports per-column pinning and the canonical nested `pinning` option. +`pinning.columns.left` accepts an inclusive edge-boundary number for contiguous +left pinning, while `pinning.columns.right` accepts a count from the trailing +edge. Either side also accepts arrays of stable column ids/indexes for +non-contiguous pinning. An inclusive legacy boundary such as `frozenColumn: 2` +is therefore simply written as `pinning.columns.left: 2`; users do not need to +expand it into an index array. +There is no separate `pinnedColumn` or `pinnedRows` grid option; those temporary +aliases were removed after the canonical shape was wired through core and state. +The POC does not target compatibility with the old frozen-pane UX. + +The canonical grid-state shape is now a single nested `GridOption.pinning` object: +`{ columns: { left, right }, rows: { top, bottom } }`. `Column.pinned` remains the +per-column representation. `GridService.setPinning()` and `GridStateService` now read +and write the unified shape; sticky configuration remains separate because it has +different scroll-activated semantics. `CurrentColumn.pinning` also carries the +per-column side in column layouts, providing a hybrid preset representation for +consumers that do not want to persist a separate aggregate pinning object. + +Vanilla Example 11 serializes the new nested `CurrentPinning` shape in its +saved views and intentionally opts into both pinning header commands to +exercise the new behavior. The single-column menu action calls +`SlickGrid.setColumnPinning` and updates `Column.pinned`; the bulk “Freeze +Columns” menu action now updates `pinning.columns.left`, which applies the same +left pins through the unified pinning resolver. Neither action +changes `frozenColumn` or invokes legacy freeze-width validation. The existing +`hideFreezeColumnsCommand` setting currently controls the bulk command, while +the new `hidePinColumnCommand` setting controls the single-column command. + +The header commands are now pinning-aware: bulk actions emit +`freeze-columns`/`unfreeze-columns` for migration familiarity, while the +single-column actions emit `pin-column`/`unpin-column` and use `PIN_COLUMN`/ +`UNPIN_COLUMN` labels. None recreates the old two-pane freeze layout. + +The POC has now gone through visual hardening and selected Cypress migration. Unit tests, +coverage, the full Cypress matrix, framework validation, and documentation remain follow-up +work after the legacy freeze deletion pass. + +## Refactoring status and immediate follow-up + +The behavior-preserving cleanup pass is complete, but the refactoring is **not complete**. +The current pass only deduplicated docking-overlay event binding, shared header/footer +scroll forwarding, docking predicates, and repetitive comments. It intentionally did not +remove the legacy frozen implementation because the first PR is being used as the POC +checkpoint. + +The legacy freeze/pane removal is the **immediate next task after creating the first PR**; +it must happen before adding more compatibility branches or treating the current code as +the final architecture. The old frozen options, pane fields, synchronized-scroll branches, +validation paths, state/menu plumbing, and frozen SCSS remain temporary and must be deleted +in that follow-up breaking-change pass. + +Current working-tree library delta relative to `HEAD` (packages, excluding tests/demos) is +approximately `+2,886 / -387`, or **+2,499 net LOC**. This is a POC snapshot, not the +expected post-cleanup size. Recalculate the production LOC immediately after the legacy +deletion pass; do not use the earlier planning estimate as the final number. + +## Maintainability acceptance gate + +The original PR 1238 motivation must be reviewed before this work is accepted: frozen panes +made `slickGrid.ts` harder to maintain because ordinary operations had to know about left/right +headers, footers, viewports, and canvases. The single-viewport rewrite is successful only if it +removes that model; it is **not** sufficient to create the old panes and force their options +off at runtime. + +The final implementation must satisfy all of the following: + +- construct one header, header-row, footer-row, viewport, and canvas rather than creating + left/right/top/bottom pane elements and detaching most of them; +- make ordinary header/footer creation and column-element lookup direct single-container + operations, without `hasFrozenColumns()` / `hasFrozenRows()` target selection; +- delete the old frozen option fields, state/menu/service plumbing, pane fields, synchronized + scroll branches, resize branches, and frozen SCSS instead of retaining a disabled + compatibility path; this includes the obsolete `-1000px` header-container offset introduced + for the legacy multi-pane/IE-era renderer; +- keep pinning-specific behavior in the DOM-free `DockingController` plus a small docking DOM + layer that applies per-row left/center/right regions and pinned chrome offsets; +- remove the obsolete `HEADER_WIDTH_SLACK`/`1000px` header-coordinate workaround as part of the + rewrite; header titles, grouped headers, and header regions now use ordinary coordinates; +- rename surviving singular DOM fields to neutral names (`_header`, `_headerRow`, `_footerRow`, + `_viewport`, `_canvas`) so the old `L`/`R` pane model cannot leak back into normal code; +- complete sticky docking or remove any temporary feature-flag path; a dormant sticky API is + not an acceptable final design. + +Do not revive a `ViewportMgr` merely to conceal the old multi-pane renderer. In this design, +deleting the multi-pane renderer is simpler and better aligned with the major-version breaking +change. This gate is a required review item before tests, documentation, or release work begin. + +## leftover TODOs identified by user +- [x] Unified grid options support pinning (left, right, top, bottom) +- [x] Header Menu exposes the migration-friendly "Freeze Columns" action and the new single-column "Pin Column" action +- [x] `CurrentColumn.pinning` provides a per-column Grid State/Preset representation alongside aggregate `GridState.pinning` +- [x] Row/body/header/footer docking regions have a predictable left/center/right DOM shape. Row + regions use the compatibility-oriented names `.slick-pinned-left-cells`, + `.slick-scrolling-cells`, and `.slick-pinned-right-cells`; they are per-row regions, not old + full-height panes or independent scroll containers. Header, header-row, and footer regions use + `.slick-*-columns-left/center/right` wrappers. +- [x] The optional `.slick-docking-overlay` is not created for a grid without row pinning or + sticky-row configuration. Once row docking is configured, the overlay remains a stable row layer + even when no row is currently active. +- rowspan z-index is problematic with new docking overlay, need further investigation +- using Cypress with `.scrollTo()` doesn't actually scroll the header titles & scrollbar, but the data is being scrolled +- [x] Legacy freeze validation callbacks are restored at the new pinning API boundary. Requests that + pin every visible column or whose permanent left/right bands consume the viewport are rejected, + preserve the previous state, and invoke the configured legacy alert callback/message. +- [x] Header regions expose `.slick-header-columns-left/center/right` (and equivalent header-row/ + footer-row classes), so consumers can identify each region without relying on removed pane roots. +- a11y issues with sticky example when using arrows to navigate, it jumps from a center column to a sticky column, is that expected? I would think that it should rather move the scroll instead and show next data cell instead (that is what we were doing in the legacy freezing, also need to verify new pinning feature). +- probably need more pinning Cypress test in vanilla example04.cy.ts to cover the new feature vs legacy frozen feature. +- why is context menu opened outside of the grid viewport in vanilla example04.cy.ts? The context menu position is unexpected (seemed to be the tests in describe "accessibility sub-menus tests"). +- address comment brought up in SlickGrid for new pinning/sticky features: + > I think the major version would indicate this well enough. yeah its a bit more than a break, its a feature deprecation sort of, but the replacement is subjectively better for me. + > what the latest push in AI development made me think of though is that we might should start thinking about shipping curated skills along with the library. that would serve two purposes. first, LLMs would know better how to apply specific features from slickgrid on the consumer end. but secondly, the skills could also act as a verification of the docs and thus overall improve the development of new features as LLMs could check up on skills when touching existing features +- identify and document breaking changes (docs folder) +- **IMMEDIATE POST-PR MAJOR CLEANUP:** remove all legacy frozen grid options and the old + multi-pane renderer immediately after the first POC PR is created. The audit found this + is feasible, but it requires coordinated refactoring across `SlickGrid`, + GridState/GridService, header grouping, resizer, extensions, framework wrappers, and + frozen SCSS. The prior 950–1,560-line deletion estimate is only a planning range; the + exact production LOC must be measured again after the deletion pass. Do not continue + layering compatibility branches onto the current hybrid implementation. + +## Starting point + +- Branch: `master` +- Base commit: `e757539c2` +- Worktree was clean before this POC. +- The earlier `feat/viewport-mgr`/PR 1238 approach was inspected but not reused because it extends the old full-height pane architecture. +- GitHub Discussion 1237 was reviewed for arbitrary sticky rows/columns, pixel budgets, overflow policies, variable row heights, and hierarchical sticky-row semantics. +- AG Grid v36's single-scroll DOM change was used as the structural reference. + +## Implemented architecture + +### One live scroll viewport + +`SlickGrid.activateSingleViewportLayout()` detaches the legacy right and bottom panes from the live DOM and changes the public/internal active collections to one viewport and one canvas: + +- `_viewport = [_viewportTopL]` +- `_canvas = [_canvasTopL]` +- the active header/header-row/top-panel/footer collections likewise contain only their left/single instance. + +Legacy pane fields still exist temporarily so unrelated old code compiles, but their elements are detached and do not own scrollbars. + +### Per-row left/center/right regions + +In the single-viewport docking renderer, every rendered row has this shape, including grids +with no active pinned columns (the side regions are then empty and have no active separator): + +```html +
+ + + +
+``` + +There is no left/right row clone and no second body canvas. `renderRows()` now appends exactly one row node to `_canvasTopL`. + +The same stable-region principle applies to chrome. The single header, header-row, and footer +roots each contain persistent `left`, `center`, and `right` semantic wrappers. They use +`display: contents`, so the wrappers do not introduce another layout or scrolling layer. + +The center region retains horizontal cell virtualization. Pinned and active sticky cells are always materialized, while ordinary center cells continue to be created/cleaned according to the rendered pixel range. + +### Horizontal positioning + +The one native viewport scrolls the full-width canvas. The small number of rendered left/right row regions receive `translateX()` updates derived from that single `scrollLeft`: + +- left region translation: `scrollLeft`; +- right region translation: `scrollLeft + viewportClientWidth - contentWidth`. + +This is necessary for right-pinned cells to be visible immediately. Pure `position: sticky; right: 0` does not pull an element whose natural position starts beyond the right side of a wide canvas into the initial viewport. + +The header, header-row, footer, and optional panel content receive whole-layer `translate3d(-scrollLeft, 0, 0)` transforms. Pinned chrome receives the inverse docking offset so it remains fixed at the edge. Horizontal virtual-cell rendering is queued behind the scroll task, and ordinary rows with only leading pinned columns avoid redundant per-scroll style writes because their left region already uses CSS sticky. + +The old paired header coordinate trick (`-1000px` on the header root plus `+1000px` on +header-column rules) has been removed from the docking renderer. Header widths no longer include +the `HEADER_WIDTH_SLACK` value, and grouped/pre-header titles use the same normal coordinate +system. This is intentional cleanup for the major-version rewrite; the offset was layout +technical debt from the old pane renderer, not a pinning or virtualization requirement. + +An attempted shared sticky-canvas coordinate system was **reverted** because it broke the far-right scroll geometry (visible blank space and header/body misalignment). The replacement preserves the canvas as a normal full-width element and moves horizontal scrolling to one dedicated scrollbar overlay aligned with the body viewport. The body viewport is now vertical-only; its canvas, the pinned-row overlay, headers, header row, footer, and optional panels all receive the same `translate3d(-scrollLeft, 0, 0)` from the dedicated scrollbar's scroll event. A pair of scoped CSS variables applies the inverse offset to left/right pinned regions and pinned chrome, so regular rows no longer receive individual JavaScript positioning writes during horizontal scrolling. This is the POC's current attempt to remove native-body/header visual tearing while retaining a single horizontal scrollbar and normal canvas scroll extent; it still needs visual confirmation. + +The always-created right-region wrapper is now marked active only when right pinning has a non-zero width. This prevents a zero-width `slick-pinned-right-cells` region (present for the stable left/center/right row shape) from drawing a spurious pinned-border line in left-only pinning scenarios. + +The pinned-row overlay now uses the full canvas/docking content width rather than the visible viewport width and is refreshed after every canvas-width update. Since the dedicated horizontal scrollbar translates the overlay by `-scrollLeft`, a viewport-sized (or stale) overlay would clip itself and expose a trailing blank square as soon as it scrolled right. The top pane remains the clipping boundary. + +Example 04 keeps `enableAutoSizeColumns: true` to preserve the existing option +contract. The shared resizer service invokes `autosizeColumns()` after +browser/container resize, and the pinning layout must continue updating its +canvas, proxy scrollbar, and docked regions correctly when those widths change. + +Example 04 now exercises both docking edges by default: the first three columns are pinned left and the final `Action` column is pinned right. A separate `Pinned Right` count control updates the right band dynamically; setting it to zero removes right pinning, and the existing remove button clears both edges. + +### Unified docking resolver + +New DOM-free `DockingController` resolves both axes: + +- permanent left/right columns; +- center columns and their natural offsets; +- sticky left/right columns activated only after they have been fully seen and then clipped; +- permanent top/bottom rows; +- sticky rows activated after they have been fully seen and scroll past the top edge; +- viewport-percentage pixel budgets; +- `conveyor`, `clamp`, and `priority` sticky overflow policies; +- revision counters so DOM membership changes happen only when a docking boundary is crossed, not on every scroll pixel. + +Scroll-activated sticky docking is now enabled through the same resolver as permanent pins. +Example 47 uses it for Q1–Q4 and its three summary rows. The transition path +still needs visual validation before this can be considered production-ready. + +`sticky: true` means the leading edge (`left` in LTR and `right` in RTL). Explicit `'left'` and `'right'` remain physical edges. + +### Sticky feasibility and quarterly-style groups + +The current pinning implementation is a viable base for sticky columns/rows. Permanent +pins and scroll-activated sticky items already resolve into the same left/center/right and +top/bottom bands, and both kinds of item can be represented by the same per-row regions. +Completing the existing sticky path should therefore be an incremental change rather than a +second rendering architecture: + +- keep the single native horizontal and vertical scrollbars; +- let the controller activate/deactivate sticky candidates only when a visibility boundary is + crossed (not on every scroll pixel); +- keep center cells horizontally and vertically virtualized; only configured pinned/sticky + cells and rows are materialized outside the normal range; +- use the same width/height budgets, hysteresis, resize invalidation, and overlay stacking + already needed for permanent pins. + +For a basic sticky-column/sticky-row feature (including coexistence with permanent pins), a +rough **+150 to +300 library LOC** is expected after the current POC is cleaned up. Most of +that work is transition edge cases: large scroll jumps, RTL, resize/reorder, variable row +heights, and deciding how sticky rows are pushed off when several candidates compete. + +The user's quarterly example is also feasible, but there are two different scopes: + +1. If Q1/Q2/etc. are ordinary columns with `sticky: 'left'` (or a runtime sticky callback), + the basic estimate applies. +2. If Q1 is a group header spanning January–March and the group itself must remain visible, + grouped-header metadata and a separate sticky group-header layout are required. That is + approximately **+150 to +300 additional library LOC**, with explicit rules for groups that + cross a pinned/center boundary and for push-off/replacement as the next quarter enters. + +This is still compatible with permanent pinning: a permanent pin always wins its edge budget, +while sticky candidates use the remaining center viewport. The performance model remains +O(configured sticky candidates) per scroll event and O(1) DOM work between boundary crossings; +large datasets continue to render only the normal virtual range plus the small docked set. +Sticky activation is enabled for Example 47's quarterly columns. Permanent pinning and sticky +transition visuals still require the same acceptance review before the API is finalized. + +### Pinned/sticky rows and virtual scrolling + +Pinned row references are resolved to row indexes and cached. With a plain array, resolving a string ID may scan the dataset once; subsequent vertical scroll events are O(number of configured docked rows). With a SlickDataView, `getRowById()` is used when available. + +The normal virtual rendered range is unchanged. Only configured top/bottom rows are additionally rendered, so a million-row dataset does not produce a million-row DOM. + +Pinned and active sticky rows reuse their normal cached row element, but are reparented into a small overlay outside the scrolling canvas. Their vertical `top`/`bottom` coordinates are constant during scrolling; only the center cell region follows horizontal scroll. No additional tall top/bottom canvas is created. + +Pinned rows now live outside the scrolling canvas, so their vertical coordinate does not +change as `scrollTop` changes. Transforms remain available for ordinary rows because a +growing transform on a row inside the scrolling canvas produced visible jumps during +virtual-page recycling. Virtual-page changes update row positions only after the physical +scroll position and page offset have both been committed, avoiding a transient mixed-coordinate +frame. Pinned region boundaries use the existing `--slick-frozen-border-right` and +`--slick-frozen-border-bottom` theme variables for body rows and column chrome. The horizontal +row boundary is emitted only on the last top-pinned row (or first bottom-pinned row), rather +than repeating across every pinned row. Per-scroll vertical updates no longer rewrite pinned rows; normal +virtual rows are repositioned only when a page offset actually changes. The overlay now +inherits the normal grid-cell typography, borders, alternating backgrounds, and selection +styles, and is stacked above hovered scrolling rows so the pinned content cannot show through. +Header-row and footer cells in pinned bands now receive explicit border-box widths, so filter +controls track left- and right-pinned column resizing equally. +Their width calculation now preserves content-box semantics and subtracts each element's +horizontal padding/border from the rendered header width, preventing fractional header/body +boundary offsets. +Pinned body-region borders no longer consume the measured content width (`content-box`), keeping +body cell widths consistent with the fractional header/filter widths. +Column-resize auto-scroll is now limited to center columns; resizing a permanently pinned +right column no longer forces the native horizontal viewport to jump to its maximum position. +Pinned body regions and header/filter/footer chrome now use opaque theme backgrounds and a +dedicated stacking layer, preventing center cells or hovered rows from painting over pinned +content during width updates. +For ordinary scrolling rows, the permanently left-pinned region uses native CSS sticky +positioning at the leading edge. The canvas and pinned-row overlay now share the same +CSS-variable horizontal transform as headers and filters, while scroll-activated sticky docking +remains disabled. + +## New POC APIs + +### Column definition + +```ts +interface Column { + pinned?: 'left' | 'right' | null; + sticky?: 'left' | 'right' | 'both' | boolean; +} +``` + +Examples: + +```ts +{ id: 'title', field: 'title', pinned: 'left' } +{ id: 'total', field: 'total', pinned: 'right' } +{ id: 'country', field: 'country', sticky: true } +{ id: 'quarter1', field: 'quarter1', sticky: 'both' } +``` + +### Grid options + +```ts +interface GridOption { + pinning?: { + columns?: { + left?: number | Array; + right?: number | Array; + }; + rows?: { + top?: Array; + bottom?: Array; + }; + }; + stickyRows?: { + top?: Array; + bottom?: Array; + both?: Array; + }; + docking?: { + maxColumnViewportWidthPercent?: number; // default 60 + maxRowViewportHeightPercent?: number; // default 60 + overflowStrategy?: 'conveyor' | 'clamp' | 'priority'; + stickyHysteresis?: number; // default 2px + }; +} +``` + +Row references can currently be row indexes or values from `datasetIdPropertyName` (default `id`). Numeric references prefer row-index semantics when they are within the current data range. + +The unified `GridOption.pinning` shape now owns both column bands and row bands +(`pinning.columns.left/right` and `pinning.rows.top/bottom`). `Column.pinned` +remains the per-column representation for explicit/non-contiguous pinning. +Runtime updates and state serialization use the nested shape so initial options, +dynamic updates, and grid-state persistence cannot drift apart. + +### Runtime grid methods + +```ts +grid.getPinnedColumns(side?); +grid.setColumnPinning(columnId, 'left' | 'right' | null); +grid.setColumnStickiness(columnId, true | false | 'left' | 'right' | 'both'); +``` + +Rows are changed through `grid.setOptions({ pinning: { rows }, stickyRows })`. +`stickyRows.top` docks a seen row after scrolling below it, while `stickyRows.bottom` +docks a seen row after scrolling back above it. `stickyRows.both` chooses the closest vertical +edge after normal scrolling would clip the row. + +## Example 04 conversion + +Vanilla Example 04 now loads the equivalent of its previous configuration through the new APIs: + +- previous `frozenColumn: 2` becomes `pinning.columns.left: 2`; core expands that + inclusive boundary to the first three final visible columns (checkbox, title, + percent complete). Explicit arrays remain available for non-contiguous pins; +- previous `frozenRow: 3`/top becomes `pinning.rows.top: [0, 1, 2]`; +- the existing column-count, row-count, remove, set-three, top/bottom, and large-width controls now mutate the nested `pinning` option; +- legacy Grid Menu/Header Menu freeze commands remain available when configured, but now write + the canonical `pinning` state instead of recreating frozen panes; +- the page title identifies it as the single-viewport POC. + +Some variable/function names and Cypress `data-test` attributes still say `frozen` to keep the example diff focused. They can be renamed after the POC is accepted. + +## Example 47 — sticky financial-report fixture + +Vanilla Example 47 reproduces the report shape from Discussion 1237's animated mockup: + +- the example intentionally has **no permanent pins**; +- `Account`, Q1–Q4, and YTD retain their natural locations and declare `sticky: 'both'`, so + each docks to the nearest edge only after scrolling would clip it; +- the three report totals (`Total Revenue`, `Total Expenses`, and `Net Profit`) declare + `stickyRows.both`, so they dock to whichever vertical edge is closest after they have been + seen; they retain the dark summary band from the mockup; +- follow-on Capex, headcount, R&D, grants, FX, and provisions rows remain after Net Profit, + allowing the statement totals to be crossed in both vertical scroll directions; +- a small horizontal auto-scroll control makes it easy to inspect the eventual sticky + transitions without manually dragging the scrollbar. + +Example 47 is the primary fixture for validating sticky columns, future sticky summary rows, +and later group-header behavior without conflating those semantics with Example 04's +permanent-pinning controls. + +## User-observed status + +- Initial load first failed in `getHeaderChildren()` because column resize assumed `_headers[1]` existed. +- That was fixed by flattening the connected header collection. +- SortableJS no longer assumes or creates a connected second header instance. +- The user subsequently reported no more console errors before the Example 04 API conversion. +- Visual correctness after the final Example 04 conversion and right-edge transform change still needs user confirmation. +- Sticky columns are enabled in Example 47 for Q1–Q4; their large-scroll, resize, RTL, and + multi-sticky visual behavior still needs manual confirmation. +- The stable docking-region DOM is now implemented and the old 1000px header offset has been + removed. Header/row/footer regions and per-row body regions should now be selected by their + explicit left/center/right classes rather than by legacy pane roots. + +## Files changed + +Core implementation: + +- `packages/common/src/core/dockingController.ts` — new shared docking resolver. +- `packages/common/src/core/slickGrid.ts` — single live viewport, stable header/body regions, + per-row pin/sticky routing, scrolling, row caching, runtime API, validation, and hit-testing fixes. +- `packages/common/src/services/headerGrouping.service.ts` — grouped/pre-header titles now use + normal coordinates without the legacy 1000px offset. +- `packages/common/src/styles/slick-grid.scss` — three-region row layout and docked stacking styles. +- `packages/common/src/core/index.ts` — public controller exports. + +Public types: + +- `packages/common/src/interfaces/docking.interface.ts` +- `packages/common/src/interfaces/column.interface.ts` +- `packages/common/src/interfaces/gridOption.interface.ts` +- `packages/common/src/interfaces/index.ts` + +POC demonstration: + +- `demos/vanilla/src/examples/example04.ts` +- `demos/vanilla/src/examples/example04.html` +- `demos/vanilla/src/examples/example47.ts` +- `demos/vanilla/src/examples/example47.html` +- `demos/vanilla/src/examples/example47.scss` + +## Validation completed + +The following implementation-only checks passed: + +```bash +pnpm --filter @slickgrid-universal/common exec tsc --noEmit --pretty false +pnpm --dir demos/vanilla type-check +pnpm exec oxlint packages/common/src/core/slickGrid.ts +pnpm exec prettier --write +git diff --check +``` + +Static validation for the recent cleanup passed: common-package TypeScript, Oxlint, +Prettier, and `git diff --check`. The framework Cypress TypeScript configs also pass after +the custom-command typing fix. The root Cypress config still reports unrelated existing +errors in `test/cypress/support/common.ts` and `test/cypress/support/index.ts`; a Cypress +run in the current agent environment exits with code 132 before browser startup. + +The Cypress custom-command return-type fix was applied consistently to the root, Angular, +Aurelia, React, and Vue support copies: `getCell`/`getNthCell` now return +`Chainable>`, and `convertPosition` has its concrete chainable shape. +Do not undo this narrowing when revisiting Cypress typings. The remaining root support errors +are separate pre-existing typing issues and should be handled independently. + +## Current production LOC delta and cleanup estimate + +These are rough **library-only** figures for `packages/common` (including SCSS and public +interfaces, excluding Example 04, tests, generated output, and framework-wrapper changes). +They are calculated from the current diff, counting the two new files as additions: + +- current working-tree library diff: approximately `+2,886 / -387`, or **+2,499 net LOC** + relative to `HEAD` (packages, excluding tests/demos); +- this includes the new `DockingController` and docking types, single-viewport/per-row + routing, sticky/pinning hardening, and the pinning/docking stylesheet changes; +- this is not the expected final size because the old frozen-pane implementation is still + present and forcibly disabled for the POC. + +The earlier 800–1,200-line removal estimate is retained only as a planning range. It is not a +final forecast: the exact permanent-pinning LOC must be measured immediately after deleting +the old full-height pane/frozen-row/frozen-column plumbing, core branches, option/state/menu +wiring, and frozen SCSS. + +Hardening basic sticky columns/rows would add roughly **+150–300 LOC**, giving an estimated +**+120 to +670 net LOC** after cleanup. Supporting grouped quarterly sticky headers would add +another **+150–300 LOC**, for an estimated **+270 to +970 net LOC** in the broader design. +These ranges are planning numbers, not a final count; the deletion pass and grouped-header +requirements are the two largest sources of variance. + +## Known limitations and likely breakage + +### Latest visual fixes (2026-09-03) + +- The dedicated horizontal scrollbar now reserves its measured height from the live body viewport. Unlike the native scrollbar it replaces, the proxy is absolutely positioned and otherwise covered the last fully scrolled row. +- Financial-report summary rows now force their dark foreground/background palette on individual cells, including when a sticky row is moved to the docking overlay; this prevents an inherited canvas background from making Total Expenses unreadable. +- Bottom sticky-row activation now tests the row's bottom edge rather than its top edge. Bottom candidates are resolved upward from the viewport edge, reserving the height of each already-docked row; a preceding summary therefore docks against Net Profit rather than one full row-height late. +- Sticky-row transitions use the exact top/bottom boundary instead of the configurable 2px column hysteresis, preventing an otherwise visible 1–2px snap into the docking overlay. +- Example 47's sticky candidate and active sticky cells/headers now consistently use the exact `#e4edf7` report blue with higher CSS priority than odd-row striping; docking no longer darkens the cells. +- Horizontal sticky-column thresholds and cell coordinates now use the visible body width (excluding the vertical scrollbar gutter), preventing right stickies from activating 10–15px late. Scroll transitions commit the current header transform before measuring right chrome, avoiding stale left/right header offsets when the right sticky set changes. +- The initial leftmost sticky-column pass now seeds configured candidates as eligible, allowing offscreen-right Q3/Q4/YTD columns to dock immediately at load instead of requiring a right-and-back scroll first. +- The initial top sticky-row pass likewise seeds configured rows as eligible, allowing two-sided report summary rows to dock at their nearest vertical edge immediately at load. +- Example 47's YTD definition now retains the shared sticky-candidate classes when adding its YTD-specific classes, so it keeps the sticky blue background even when it reaches its natural right edge and Q4 takes over the separator. +- Added a higher-specificity right-edge separator rule for header, header-row, and footer chrome so the first right-sticky column title/filter receives the same `--slick-frozen-border-right` separator as the body region. +- Removed the non-user-facing Example 47 auto-scroll control and timer; the fixture now uses only normal manual grid scrolling. +- Draggable Grouping now tolerates the single-viewport layout: it creates a Sortable instance only for header containers that actually exist, instead of passing a removed right header (`null`) to SortableJS. +- Pinned left/right edge header-row and footer cells now use border-box sizing with the full measured header outer width. This keeps an empty edge filter cell aligned with its data cells when the pinning separator contributes a border (for example, 100px data cell versus 98px header-row cell). +- The single horizontal scrollbar proxy now has an opaque canvas background, themed `scrollbar-color`, pointer events, and an isolated stacking context. Its z-index remains above grid rows but below application overlays such as Bulma navbar menus, and its track is aligned to the pane content edge. +- The docking scrollbar now uses `overflow-x: auto` and sizes its spacer from the natural docking content width. When all columns fit the viewport, the proxy has zero height and no horizontal track is shown; when overflow exists, its height still comes from the measured native scrollbar dimensions. +- The full-width docked-row overlay now uses a scroll-aware clip window equal to the viewport's content width. It can still retain enough translated width for right-pinned cells, while excluding the native vertical scrollbar strip from overlay painting. +- The docked-row overlay stacking layer is now `z-index: 5`, matching the normal pinned-row layer. This keeps pinned rows above scrolling cells but below application overlays such as Bulma navbar dropdowns (`z-index: 20`). +- In single-viewport mode the header-row scroller now gets an opaque header-row background. Its unused trailing gutter (the body viewport's scrollbar space) no longer reveals translated center columns when widths change; logical right-pinned column widths remain unchanged. +- The right-edge pinned header-row cell now extends by the measured vertical scrollbar width when that scrollbar is present. This fills the header-only gutter directly while leaving body cells and right-pinning offsets at their normal widths. +- The right-edge pinned header-row cell is positioned by the computed physical separator border width (with RTL-aware direction), rather than a hard-coded 1px, to match customized or hidden borders. +- Example04 now clears the opposite `pinning.rows` side when toggling top/bottom. This is required because `setOptions()` deep-merges nested option objects; supplying only `{ bottom }` previously left the old top references active. +- Example04 bottom mode now pins the last configured rows instead of reusing indexes `0..N`. This matches the former `frozenBottom` behavior and prevents the first rows' natural slots from becoming blank when they move to the bottom overlay. +- Docked left/right row regions now mirror odd-row striping and hover backgrounds. Their opaque pinning backgrounds no longer hide the configured gray odd-row color. +- Docked rows no longer receive the legacy active-row padding, preventing every cell in a clicked row from shrinking. Active-cell coordinate resolution now handles rows rendered in the docking overlay, allowing editors to open on top-pinned cells. +- Docked rows now receive an explicit resolved `rowHeight` inline, including the default value. This prevents active/editor box-model styles from reducing a configured 45px row to its 35px content height. +- Cell interaction handlers are bound to the docking overlay as well as the canvas, enabling click/auto-edit and double-click editing for top- and bottom-pinned rows. +- Example04 includes a Toggle Right Pinning button that switches the right-pinned Action column on/off while preserving the configured left pins. +- `internal_setOptions()` now renders after `setColumns()` invalidation. This fixes dynamic row-pinning count changes, which were previously rendered and then cleared when the column refresh removed cached rows. +- `setOptions()` now replaces `pinning.rows.top`/`bottom` arrays atomically instead of deep-merging them. This removes stale row references when the configured pin count decreases. +- Browser grow-after-shrink handling now separates the natural column-content width from the rendered docked-row width. The canvas and row center region grow to at least the body viewport, preventing a white gap before a right pin; right-pinned body regions use the rendered-width offset while header chrome retains natural scroll coordinates. Right-pinned header cells are also taken out of flex flow and explicitly positioned, so their titles remain at the visible right edge. The inner header/header-row/footer column containers now allow this docked chrome to overflow to their existing outer viewport clip; the old inner `overflow: hidden` was clipping every right-pinned header title and filter. This needs visual confirmation in Example 04 after shrinking the browser and restoring it to full width. +- Right-pinned header chrome no longer uses the shared natural-content transform used by row regions. Each right-pinned header/header-row/footer cell is positioned at its direct viewport coordinate (`scrollLeft + viewportWidth - rightBandWidth + columnOffset`) inside the already translated chrome layer. This fixes titles landing beside a center column and supports multiple right-pinned columns; visual confirmation remains pending. +- The viewport width used for right-pinned chrome is now read from the header scroller itself, rather than from the horizontal-scroll proxy. The proxy can retain a stale narrow width during resize (for example, yielding `left: 1537px` from a 1637px proxy for a 100px column), while the header scroller is the actual visible clip boundary. Visual confirmation is pending. +- The legacy `-1000px` header-container / `+1000px` header-column coordinate pair has now been + removed from the single-viewport renderer and grouped-header service. Header widths no longer + include the corresponding 1000px slack. If any remaining legacy pane path is temporarily + exercised during migration, it must not be mixed with the new docking coordinate system. +- Bulk Freeze/Unfreeze migration state is keyed by stable column IDs rather than + column object identity. This preserves the generated-pin bookkeeping across + `updateColumnProps()` cloning and makes the existing `Unfreeze Columns` command + reliably restore the pre-freeze pin state. +- Sticky-column horizontal scrolling now keeps the scrollbar/compositor path + immediate while coalescing sticky-band resolution to one animation-frame pass. + On a band transition, rendered cells are moved between their existing + left/center/right row regions instead of being discarded and reformatted; the + same frame updates only those row-wrapper dimensions and lets the regular + deferred virtualizer fill missing cells. A full row rebuild remains only as a + safe fallback when a layout gains its first docking region. Permanent-pinning- + only grids retain the synchronous fast path; visual validation of Example 47 + is still required. +- Empty left docking regions no longer paint the left separator: the pinned + border is now enabled only while the left region contains an active docked + column, including when sticky membership changes during scrolling. +- Right-edge sticky/pinned header, header-row, and footer chrome now offsets + its physical start coordinate by the configured separator border width, so + the title/filter border aligns with the body separator for any border size. +- Vanilla Example 11 view presets now retain and restore the complete pinning + state again. Creating/updating a view serializes `GridState.pinning`, reset + clears permanent pinning, and selecting a view reapplies pinning after its + column layout (the required order for hidden/reordered columns). +- Removed the duplicate `pinnedColumn` and `pinnedRows` grid options. Header-menu + bulk freeze/unfreeze and row docking now write/read the canonical `pinning` + object directly; `Column.pinned` remains available for explicit per-column pins. +- Reworked the Vanilla Example 04 Cypress spec for the persistent docking DOM: + header assertions now query `.slick-header-column` descendants, row assertions + target `data-row` plus cell index, and no-pinning checks expect stable empty + left/right regions rather than removed panes. Previously skipped autocomplete, + header-menu, accessibility, large-scroll, and reorder cases are enabled again. +- Restored the legacy invalid-hide alert contract for pinning. `validateColumnFreeze()` + now validates the prospective visible set against the canonical pinning layout, + so hiding the last available center column is rejected without mutating the grid. +- Column reorder now creates Sortable instances for the persistent left, center, + and right docking wrappers and combines their order on drop. This keeps drag + auto-scroll and reorder functional after the old right pane is removed. +- Sticky transitions now rehome header titles, header-row filters, and footer + cells into the same persistent left/center/right wrappers as their body cells, + then restore column order within each wrapper. This fixes the intermittent + Example 47/48 state where body Q2/Q3 had moved bands but their chrome remained + in the old wrapper and appeared misaligned while scrolling. Left-band chrome + now uses the grouped wrapper flow rather than subtracting its old natural + center-column offset, keeping Q1/Q2 visible at the far-right scroll position. +- Docking chrome now keeps the region bands as direct `.slick-header-columns` + (and equivalent header-row/footer) children of a separate `*-columns-root`. + This preserves the legacy selector contract where `.slick-header-columns` + `.children()` are actual cells, while still exposing stable left/center/right + region classes for pinning/sticky grids. Empty explicit pinning keeps the row + bands stable; grids with no pinning configuration remain flat. +- Tightened docking activation so an empty `pinning.columns` state does not + create nested header wrappers, while an explicitly pinning-configured grid + retains predictable row-region DOM after clearing pinning. Fixed the related + TypeScript narrowing error in `hasConfiguredRowDocking()`. +- Example 04 vertical-scroll coverage now uses rows that exist in its 40-item + fixture, resets both scroll owners between suites, and identifies reordered + columns by stable IDs. With the dev server running, Firefox headless Cypress + passes all 42 Example 04 tests with retries disabled. Electron cannot start in + this environment because its bundled binary exits with SIGILL. +- Example 04's large-column action now applies its explicit layout before + applying pinning. This avoids validating stale, previously resized widths and + accidentally clearing pinning (which removed the center docking region before + the final drag/reorder test). +- Added dedicated right-pinning coverage to Example 04: multiple right columns, + header/header-row regions, horizontal-scroll retention, numeric disable/re-enable, + removing the first right-pinned column, and restoring the hidden edge column. + The focused Firefox spec now passes all 46 tests. +- Example 04 now explicitly enables `showHeaderRow`. The persistent docking + header-row root also receives `headerRowHeight`; without that root height, + `display: contents` band wrappers collapsed the visible filter bar to the + 1px spacer height. +- Example 17 Cypress migration is in progress. Its demo now uses canonical + `pinning` instead of inert frozen options, and the shared drag helper reads the + docking horizontal scrollbar for single-viewport grids. Active canvas/viewport + fallback now also supports drag selection from pinned-row overlays. A related + `scrollRowIntoView()` fix accounts for top/bottom docked-row height when + determining the usable center viewport. Firefox currently reports 12 passing, + 1 pending (the intentionally skipped grouping auto-scroll case), and one + remaining bottom-edge assertion that still needs follow-up; do not spend more + migration time here until the other spec failures are triaged. + +1. **Legacy freeze code is inert, not fully deleted.** `setFrozenOptions()` forces `frozenColumn`/`frozenRow` to `-1` and `frozenBottom` to `false`. Many dead fields, branches, validation callbacks, services, grid-state shapes, styles, and public option types remain and must be removed after acceptance. +2. **Old options intentionally no longer work.** `frozenColumn`, `frozenRow`, and `frozenBottom` are not valid ways to configure this POC. The existing `Freeze Columns` menu label remains for migration familiarity, but its implementation writes the canonical `pinning` option. `GridService.setPinning()` accepts the unified nested shape. +3. **Visual/browser validation is incomplete.** Left/right pinning, bottom rows, sticky transitions, resize, reorder, RTL, variable row height, row/column spans, editors, selection, and all four framework wrappers need manual follow-up. +4. **Colspans crossing docking bands are not defined.** A colspan beginning in one band and ending in another can produce incorrect geometry. The final design should reject, split, or explicitly define this case. +5. **Grouped/pre-header chrome is not dock-aware yet.** Standard column headers, header-row cells, footer cells, and body cells are wired. Multi-level/group header layout needs a dedicated band implementation. +6. **Sticky activation can be skipped by a very large scroll jump.** Candidates currently must first be fully visible. A final implementation should detect that the scroll path crossed a candidate even if no intermediate frame showed it fully. +7. **Numeric row reference ambiguity.** An in-range number is treated as a row index before it is treated as a dataset ID. A tagged `{ id } | { index }` row reference would remove this ambiguity. +8. **Pinned rows remain part of the normal dataset height.** They reuse/move the real row node and their natural dataset slot remains represented in scroll geometry. Confirm this product semantic against the desired AG Grid behavior. +9. **Permanent pin over-allocation is rejected at the API boundary.** Pinning every visible + column or consuming the whole viewport invokes the configured legacy validation callback and + leaves the prior pinning state intact. The final API may still rename those legacy callback and + message options during the cleanup pass. +10. **Column reorder policy is undecided.** The visual order groups permanent pins at the edges, but dragging between center and pinned regions does not yet automatically change `pinned` state. +11. **Header Menu terminology is provisional.** The bulk `Freeze Columns` command is + retained as a migration-friendly label and writes `pinning.columns.left`; the + single-column `Pin Column` command writes `Column.pinned`. Add explicit left/right + submenu actions only if the final public UX requires them. +12. **Public controller surface is provisional.** `DockingController` is currently exported for the POC; it may be better kept internal in the final API. +13. **No compatibility/migration layer is intended.** Any temporary legacy fields should be deleted, not deprecated, once this direction is approved. + +## Resume checklist + +1. Reload Vanilla Example 04 and confirm: + - only one horizontal and one vertical scrollbar are visible; + - checkbox/title/percent-complete stay pinned left; + - the first three rows stay pinned top; + - toggling rows to the bottom works; + - widening the first three columns preserves the right/center layout; + - cell clicking, editing, filtering, resizing, and horizontal scrolling still work. +2. Add at least one temporary right-pinned column in Example 04 and confirm it is visible before horizontal scrolling and remains aligned vertically/horizontally. +3. Use Example 47's Account/Q1–Q4/YTD sticky columns and bottom sticky totals to confirm activation/deactivation/hysteresis. Add a top sticky-row counterpart only after deciding its report hierarchy/push-off behavior. +4. Fix visual/interaction problems before adding tests. +5. Review the implemented unified `GridOption.pinning` shape and `CurrentColumn.pinning` precedence before freezing the public API. The former `pinnedColumn`/`pinnedRows` shorthands have been removed. +6. Review the provisional Header Menu terminology: retain `Freeze Columns` only as the + migration-friendly bulk command while the POC is being validated, and keep `Pin Column`/ + `Unpin Column` for the single-column action. Remove the legacy terminology during the final + major-version API cleanup if the migration window no longer requires it. +7. Immediately after the first PR is created, delete the old pane-freezing implementation rather than layering compatibility over it: + - remove frozen options/defaults/validation/messages; + - remove right/bottom pane fields and DOM construction entirely; + - collapse canvas/viewport/header collections to single objects where practical; + - remove old synchronized-scroll branches; + - replace `CurrentPinning`, GridService, GridStateService, menus, and extensions with the new shapes; + - remove obsolete frozen SCSS and framework assumptions. +8. Recalculate production LOC after the deletion pass. +9. Only then begin unit, coverage, Cypress, framework, documentation, and migration work. + +## New-context handoff checklist + +- Treat this file and the current working tree as the source of truth; do not restart the POC + from the old PR 1238 frozen-pane branch. +- The safe cleanup pass is finished, but no claim has been made that the full refactor is done. + The first PR checkpoint may still contain temporary frozen code; delete it immediately after + that PR is created. +- Before changing layout code, preserve the current invariants: one native horizontal scroll, + one native vertical scroll, one rendered row with left/center/right regions, stable header / + header-row / footer region wrappers, and one shared `DockingController`. +- Re-run the focused checks after edits. A browser/Cypress failure in the current agent + environment may be infrastructure-related when the process exits with code 132 before + browser startup; distinguish that from a real spec failure. +- When the legacy deletion pass begins, audit every reference to frozen options, pane roots, + synchronized scroll branches, header-width slack, frozen SCSS, GridState/GridService, + resizer, header menus, extensions, and all four framework wrappers. Then recalculate LOC + from `git diff HEAD` and update this file again. + +## Suggested resume prompt + +> Read `PINNING-POC-PROGRESS.md` and inspect the current diff. This is a major-breaking +> single-native-scroll pinning/stickiness rewrite, not an extension of SlickGrid frozen panes. +> The immediate task after the first PR is to remove all dead freeze/pane code. Preserve one +> live viewport, one row node with left/center/right cell regions, and the shared +> `DockingController`; do not add more frozen compatibility branches. Coordinate the deletion +> across `SlickGrid`, GridState/GridService, header grouping, resizer, extensions, framework +> wrappers, menus, and frozen SCSS, then run focused Cypress specs and recalculate the +> production-library LOC. diff --git a/demos/aurelia/test/cypress/support/commands.ts b/demos/aurelia/test/cypress/support/commands.ts index 3be5a3685b..1f58e699f1 100644 --- a/demos/aurelia/test/cypress/support/commands.ts +++ b/demos/aurelia/test/cypress/support/commands.ts @@ -32,19 +32,19 @@ declare global { namespace Cypress { interface Chainable { // triggerHover: (elements: NodeListOf) => void; - convertPosition(viewport: string): Chainable | { x: string; y: string }>; + convertPosition(viewport: string): Chainable<{ x: string; y: string }>; getCell( row: number, col: number, viewport?: string, options?: { parentSelector?: string; rowHeight?: number } - ): Chainable>; + ): Chainable>; getNthCell( row: number, nthCol: number, viewport?: string, options?: { parentSelector?: string; rowHeight?: number } - ): Chainable>; + ): Chainable>; saveLocalStorage: () => void; restoreLocalStorage: () => void; getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable; diff --git a/demos/react/test/cypress/support/commands.ts b/demos/react/test/cypress/support/commands.ts index 3be5a3685b..1f58e699f1 100644 --- a/demos/react/test/cypress/support/commands.ts +++ b/demos/react/test/cypress/support/commands.ts @@ -32,19 +32,19 @@ declare global { namespace Cypress { interface Chainable { // triggerHover: (elements: NodeListOf) => void; - convertPosition(viewport: string): Chainable | { x: string; y: string }>; + convertPosition(viewport: string): Chainable<{ x: string; y: string }>; getCell( row: number, col: number, viewport?: string, options?: { parentSelector?: string; rowHeight?: number } - ): Chainable>; + ): Chainable>; getNthCell( row: number, nthCol: number, viewport?: string, options?: { parentSelector?: string; rowHeight?: number } - ): Chainable>; + ): Chainable>; saveLocalStorage: () => void; restoreLocalStorage: () => void; getTransformValue(cssTransformMatrix: string, absoluteValue: boolean, transformType?: 'rotate' | 'scale'): Chainable; diff --git a/demos/vanilla/src/app-routing.ts b/demos/vanilla/src/app-routing.ts index 1976666f59..e62c7a9322 100644 --- a/demos/vanilla/src/app-routing.ts +++ b/demos/vanilla/src/app-routing.ts @@ -44,6 +44,7 @@ import Example43 from './examples/example43.js'; import Example44 from './examples/example44.js'; import Example45 from './examples/example45.js'; import Example46 from './examples/example46.js'; +import Example47 from './examples/example47.js'; import Icons from './examples/icons.js'; import type { RouterConfig } from './interfaces.js'; @@ -98,6 +99,7 @@ export class AppRouting { { route: 'example44', name: 'example44', view: './examples/example44.html', viewModel: Example44, title: 'Example44' }, { route: 'example45', name: 'example45', view: './examples/example45.html', viewModel: Example45, title: 'Example45' }, { route: 'example46', name: 'example46', view: './examples/example46.html', viewModel: Example46, title: 'Example46' }, + { route: 'example47', name: 'example47', view: './examples/example47.html', viewModel: Example47, title: 'Example47' }, { route: '', redirect: 'example01' }, { route: '**', redirect: 'example01' }, ]; diff --git a/demos/vanilla/src/app.html b/demos/vanilla/src/app.html index 8756cc7cb8..e53bbe478a 100644 --- a/demos/vanilla/src/app.html +++ b/demos/vanilla/src/app.html @@ -87,6 +87,7 @@

Slickgrid-Universal

Example44 - Variable Row Height (provider) Example45 - Variable Row Height (metadata) Example46 - RTL (Right-to-Left) + Example47 - Sticky Financial Report diff --git a/demos/vanilla/src/examples/example03.html b/demos/vanilla/src/examples/example03.html index b93ea06b33..9314ed910a 100644 --- a/demos/vanilla/src/examples/example03.html +++ b/demos/vanilla/src/examples/example03.html @@ -2,6 +2,9 @@

Example 03 - Draggable Grouping (with Salesforce Theme) + -
+
The Autocomplete Editor (City of Origin) might throw some console errors because it uses "http://gd.geobytes.com/" request (which is not using https). You can allow "Insecure content" in your browser for this website only to test the Autocomplete
@@ -27,14 +30,23 @@
+ + + + + - + - diff --git a/demos/vanilla/src/examples/example04.ts b/demos/vanilla/src/examples/example04.ts index b3dbe176b9..cf18a8964a 100644 --- a/demos/vanilla/src/examples/example04.ts +++ b/demos/vanilla/src/examples/example04.ts @@ -19,6 +19,8 @@ import fetchJsonp from './jsonp.js'; import { showToast } from './utilities.js'; import './example04.scss'; +const ITEMS_COUNT = 40; + // you can create custom validator to pass to an inline editor const myCustomTitleValidator = (value) => { if (value === null || value === undefined || !value.length) { @@ -53,11 +55,13 @@ export default class Example04 { dataViewObj: SlickDataView; commandQueue: EditCommand[] = []; frozenColumnCount = 2; + pinnedRightColumnCount = 1; frozenRowCount = 3; isFrozenBottom = false; sgb: SlickVanillaGridBundle; checkboxSelectorInstance: SlickCheckboxSelectColumn; isSelectAllShownAsColumnTitle = false; + subTitleStyle = 'display: block'; constructor() { this._bindingEventService = new BindingEventService(); @@ -394,6 +398,9 @@ export default class Example04 { autoTooltipOptions: { enableForHeaderCells: true, }, + // Preserve the existing behavior: the resizer re-fits columns after a + // browser/container resize, including when the dedicated pinning + // scrollbar is active. enableAutoSizeColumns: true, enableAutoResize: true, enableCellNavigation: true, @@ -416,9 +423,15 @@ export default class Example04 { onExtensionRegistered: (instance) => (this.checkboxSelectorInstance = instance), }, enableSelection: true, - frozenColumn: this.frozenColumnCount, - frozenRow: this.frozenRowCount, - // frozenBottom: true, // if you want to freeze the bottom instead of the top, you can enable this property + pinning: { + columns: { + left: this.frozenColumnCount, + // Keep the Action column on the trailing edge without depending on + // extension columns (such as the checkbox selector) being inserted. + right: this.pinnedRightColumnCount > 0 ? ['action'] : [], + }, + rows: { top: this.getPinnedRowIndexes() }, + }, editCommandHandler: (_item, _column, editCommand) => { this.commandQueue.push(editCommand); editCommand.execute(); @@ -531,7 +544,7 @@ export default class Example04 { // mock data this.dataset = []; - for (let i = 0; i < 500; i++) { + for (let i = 0; i < ITEMS_COUNT; i++) { this.dataset[i] = { id: i, title: 'Task ' + i, @@ -582,38 +595,60 @@ export default class Example04 { /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ changeFrozenColumnCount() { - if (this.sgb?.slickGrid?.setOptions) { - this.sgb?.slickGrid.setOptions({ - frozenColumn: +this.frozenColumnCount, - }); - } + this.setPinnedColumns(+this.frozenColumnCount, this.pinnedRightColumnCount); + } + + changePinnedRightColumnCount() { + this.setPinnedColumns(this.frozenColumnCount, +this.pinnedRightColumnCount); + } + + /** Toggle the demo's right-pinned Action column while preserving left pinning. */ + toggleRightPinning() { + this.setPinnedColumns(this.frozenColumnCount, this.pinnedRightColumnCount > 0 ? 0 : 1); } /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ changeFrozenRowCount() { if (this.sgb?.slickGrid?.setOptions) { - this.sgb?.slickGrid.setOptions({ - frozenRow: +this.frozenRowCount, + const rows = this.getPinnedRowIndexes(); + this.sgb.slickGrid.setOptions({ + pinning: { rows: this.isFrozenBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } }, }); } } - setFrozenColumns(frozenCols: number) { - this.sgb?.slickGrid?.setOptions({ frozenColumn: frozenCols, alwaysShowVerticalScroll: false }); + setPinnedColumns(frozenCols: number, rightCols = this.pinnedRightColumnCount) { + this.sgb?.slickGrid?.setOptions({ + pinning: { + columns: { + left: frozenCols, + right: Math.max(0, rightCols), + }, + }, + }); this.gridOptions = this.sgb?.slickGrid?.getOptions() ?? {}; this.frozenColumnCount = frozenCols; + this.pinnedRightColumnCount = Math.max(0, rightCols); } /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ toggleFrozenBottomRows() { if (this.sgb?.slickGrid && this.sgb?.slickGrid.setOptions) { - this.sgb?.slickGrid.setOptions({ - frozenBottom: !this.isFrozenBottom, + this.isFrozenBottom = !this.isFrozenBottom; + const rows = this.getPinnedRowIndexes(); + this.sgb.slickGrid.setOptions({ + pinning: { rows: this.isFrozenBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } }, }); - this.isFrozenBottom = !this.isFrozenBottom; // toggle the variable } } + private getPinnedRowIndexes() { + const rowCount = Math.max(0, +this.frozenRowCount); + const dataLength = this.sgb?.slickGrid?.getDataLength?.() ?? this.dataset?.length ?? ITEMS_COUNT; + const firstPinnedRow = this.isFrozenBottom ? Math.max(0, dataLength - rowCount) : 0; + return Array.from({ length: rowCount }, (_value, index) => firstPinnedRow + index); + } + toggleWhichRowToShowSelectAll() { this.isSelectAllShownAsColumnTitle = !this.isSelectAllShownAsColumnTitle; this.checkboxSelectorInstance.setOptions({ @@ -652,7 +687,8 @@ export default class Example04 { } setLargeFreezedColumns() { - this.setFrozenColumns(2); + // Apply target widths before pinning. Pinning validates against the + // currently rendered widths, which may still reflect a prior resize. this.sgb.gridStateService.applyColumnLayout( [ { columnId: '_checkbox_selector', cssClass: 'slick-cell-checkboxsel', headerCssClass: '', width: 40 }, @@ -663,11 +699,17 @@ export default class Example04 { { columnId: 'completed', cssClass: '', headerCssClass: '', width: 372 }, { columnId: 'cost', cssClass: '', headerCssClass: '', width: 288 }, { columnId: 'cityOfOrigin', cssClass: '', headerCssClass: '', width: 204 }, - { columnId: 'action', cssClass: '', headerCssClass: '', width: 64 }, + { columnId: 'action', cssClass: '', headerCssClass: '', width: 110 }, ], false, false ); + this.setPinnedColumns(2, this.pinnedRightColumnCount); // console.log('setLargeFreezedColumns', this.sgb.gridStateService.getCurrentColumns()); } + + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } } diff --git a/demos/vanilla/src/examples/example05.html b/demos/vanilla/src/examples/example05.html index ae5e10483e..f40075fbb7 100644 --- a/demos/vanilla/src/examples/example05.html +++ b/demos/vanilla/src/examples/example05.html @@ -2,6 +2,9 @@

Example 05 - Tree Data (from a flat dataset with parentId references) +

-
+
NOTE: The grid will automatically sort Ascending with the column that has the Tree Data, you could add a "sortByFieldId" in your column "treeData" option if you wish to sort on a different column
diff --git a/demos/vanilla/src/examples/example05.ts b/demos/vanilla/src/examples/example05.ts index 3c6874cef6..33eb02f8d9 100644 --- a/demos/vanilla/src/examples/example05.ts +++ b/demos/vanilla/src/examples/example05.ts @@ -25,6 +25,7 @@ export default class Example05 { loadingClass = ''; isLargeDataset = false; hasNoExpandCollapseChanged = true; + subTitleStyle = 'display: block'; treeToggleItems: TreeToggledItem[] = []; constructor() { @@ -532,4 +533,9 @@ export default class Example05 { reapplyToggledItems() { this.sgb.treeDataService.applyToggledItemStateChanges(this.treeToggleItems); } + + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } } diff --git a/demos/vanilla/src/examples/example06.html b/demos/vanilla/src/examples/example06.html index 2520dc7ace..8a75cc4d23 100644 --- a/demos/vanilla/src/examples/example06.html +++ b/demos/vanilla/src/examples/example06.html @@ -2,6 +2,9 @@

Example 06 - Tree Data with Aggregators (from a Hierarchical Dataset) +

-
+
NOTE #1: The grid will automatically sort in Ascending order with the column that has the Tree Data, you could add a "sortByFieldId" in your column "treeData" option if you wish to sort on a different column
diff --git a/demos/vanilla/src/examples/example06.ts b/demos/vanilla/src/examples/example06.ts index 5eff64b89f..4a1cb7381f 100644 --- a/demos/vanilla/src/examples/example06.ts +++ b/demos/vanilla/src/examples/example06.ts @@ -31,6 +31,7 @@ export default class Example06 { isRemoveLastInsertedPopSongDisabled = true; lastInsertedPopSongId: number | undefined; searchString = ''; + subTitleStyle = 'display: block'; attached() { this.initializeGrid(); @@ -478,4 +479,9 @@ export default class Example06 { // reset dataset to clear all tree data stat mutations (basically recreate the grid entirely to start from scratch) this.sgb.datasetHierarchical = this.mockDataset(); } + + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } } diff --git a/demos/vanilla/src/examples/example08.ts b/demos/vanilla/src/examples/example08.ts index bc426a7b68..3cab2688b7 100644 --- a/demos/vanilla/src/examples/example08.ts +++ b/demos/vanilla/src/examples/example08.ts @@ -144,7 +144,7 @@ export default class Example08 { showPreHeaderPanel: true, preHeaderPanelHeight: 35, explicitInitialization: true, - frozenColumn: 2, + pinning: { columns: { left: 2 } }, rowHeight: 33, showCustomFooter: true, gridMenu: { hideClearFrozenColumnsCommand: false }, @@ -175,7 +175,14 @@ export default class Example08 { } setFrozenColumns2(frozenCols: number) { - this.sgb2.slickGrid?.setOptions({ frozenColumn: frozenCols, alwaysShowVerticalScroll: false }); + this.sgb2.slickGrid?.setOptions({ + pinning: { + columns: { + left: frozenCols >= 0 ? frozenCols : [], + }, + }, + alwaysShowVerticalScroll: false, + }); this.gridOptions2 = this.sgb2.slickGrid!.getOptions(); } diff --git a/demos/vanilla/src/examples/example09.html b/demos/vanilla/src/examples/example09.html index 3e2a3d8f5b..9e21c3ac27 100644 --- a/demos/vanilla/src/examples/example09.html +++ b/demos/vanilla/src/examples/example09.html @@ -1,5 +1,8 @@

Example 09 - Grid with OData Backend Service +

-
+
NOTE: For demo purposes, the last column (filter & sort) will always throw an error and its only purpose is to demo what would happen when you encounter a backend server error (the UI should rollback to previous state before you did the action). Also changing Page Size to 50,000 will also throw which again is for demo purposes. Moreover, the example will persist diff --git a/demos/vanilla/src/examples/example09.ts b/demos/vanilla/src/examples/example09.ts index 80562f9a41..e4739d4c1d 100644 --- a/demos/vanilla/src/examples/example09.ts +++ b/demos/vanilla/src/examples/example09.ts @@ -36,6 +36,7 @@ export default class Example09 { errorStatusClass = 'hidden'; status = ''; statusClass = 'is-success'; + subTitleStyle = 'display: block'; isPageErrorTest = false; constructor() { @@ -527,6 +528,11 @@ export default class Example09 { return true; } + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } + private resetOptions(options: Partial) { this.displaySpinner(true); const odataService = this.gridOptions.backendServiceApi!.service; diff --git a/demos/vanilla/src/examples/example10.html b/demos/vanilla/src/examples/example10.html index 0e0a1a7dc3..46ed6c6f15 100644 --- a/demos/vanilla/src/examples/example10.html +++ b/demos/vanilla/src/examples/example10.html @@ -1,13 +1,16 @@

Example 10 - Grid with GraphQL Backend Service + - -``` \ No newline at end of file +``` diff --git a/frameworks/angular-slickgrid/docs/TOC.md b/frameworks/angular-slickgrid/docs/TOC.md index cc7d6c0dd6..f1ee115fe3 100644 --- a/frameworks/angular-slickgrid/docs/TOC.md +++ b/frameworks/angular-slickgrid/docs/TOC.md @@ -76,7 +76,7 @@ * [Custom Menu Slots](grid-functionalities/menu-slots.md) * [Header Menu & Header Buttons](grid-functionalities/header-menu-header-buttons.md) * [Infinite Scroll](grid-functionalities/infinite-scroll.md) -* [Pinning (frozen) of Columns/Rows](grid-functionalities/frozen-columns-rows.md) +* [Pinning of Columns/Rows](grid-functionalities/pinning.md) * [Variable Row Height](grid-functionalities/variable-row-height.md) * [Providing data to the grid](grid-functionalities/providing-grid-data.md) * [Row Detail](grid-functionalities/row-detail.md) @@ -122,4 +122,3 @@ * [Migration Guide to 8.x (2024-05-23)](migrations/migration-to-8.x.md) * [Migration Guide to 9.x (2025-05-10)](migrations/migration-to-9.x.md) * [Migration Guide to 10.x (2026-03-02)](migrations/migration-to-10.x.md) - diff --git a/frameworks/angular-slickgrid/docs/grid-functionalities/frozen-columns-rows.md b/frameworks/angular-slickgrid/docs/grid-functionalities/frozen-columns-rows.md deleted file mode 100644 index 083301a63d..0000000000 --- a/frameworks/angular-slickgrid/docs/grid-functionalities/frozen-columns-rows.md +++ /dev/null @@ -1,170 +0,0 @@ -#### Index -- [Columns/Rows Pinning Basic](#columnsrows-pinning-basic) -- [Rows Pinning starting from Bottom](#rows-pinning-starting-from-bottom) -- [Change Pinning Dynamically](#change-pinning-dynamically) -- [Animated Gif Demo](#animated-gif-demo) - -### Demo -[Demo Page](https://ghiscoding.github.io/angular-slickgrid-bs4-demo/#/example20) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts) - -### Introduction -One of the requested features, columns or rows pinning (aka frozen). You can pin 1 or more Columns and/or 1 or more Rows. Columns can only be pinned starting from the left side, while Rows can be pinned starting from the Top (default) or Bottom. You can also change the pinning dynamically with `setOptions()`. - -## Columns/Rows Pinning basic -To set a pinning for the entire duration of the grid, simply use the Grid Options `frozenColumn` (starting from top) and `frozenRow` (starting from left), which are both `number` types. - -```html - - -``` - -##### Component -```typescript -import { Component, OnInit} from '@angular/core'; - -export class GridBasicComponent implements OnInit { - columns: Column[]; - gridOptions: GridOption; - dataset: any[]; - - ngOnInit(): void { - // your columns definition - this.columns = []; - - this.gridOptions = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top - } - } -} -``` - -> **Caution** -> Please be aware that `frozenColumn` (left canvas) **cannot** be wider than the actual grid viewport width and you will get an alert when you try to do this. You can disable it by setting `invalidColumnFreezeWidthCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). - -> **Caution** -> Also be aware that you also cannot uncheck (from Column Picker or Grid Menu) more columns than the actual `frozenColumn` index (in other words, you need to have at least 1, or more, columns on the right section of the freeze/pinning). Also similar as above you can disable it by setting `invalidColumnFreezePickerCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). - -## Rows Pinning starting from bottom -This is basically the same thing as previous code sample, except that you will set the Grid Option property `frozenBottom` to true and that it's. -##### Component -```ts -import { Component, OnInit} from '@angular/core'; - -export class GridBasicComponent implements OnInit { - columns: Column[]; - gridOptions: GridOption; - dataset: any[]; - - ngOnInit(): void { - // your columns definition - this.columns = []; - - this.gridOptions = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns (starting from bottom with next property) - frozenBottom: true, // this will make rows to be pinned starting from the bottom and the number of rows will be 3 - } - } -} -``` - -## Change Pinning Dynamically -You can change the number of pinned columns/rows and even the pinning of columns from top to bottom. For a demo of what that could look like, take a look at the [Animated Gif Demo](#animated-gif-demo) below. - -```html -
- - - - - - - - - - - - - : {{ isFrozenBottom ? 'Bottom' : 'Top' }} - -
- - - -``` - -##### Component -```ts -import { Component, OnInit} from '@angular/core'; -import { AngularGridInstance } from 'angular-slickgrid'; - -export class GridBasicComponent implements OnInit { - columns: Column[]; - dataset: any[]; - gridOptions: GridOption; - gridObj: any; - isFrozenBottom = false; - - ngOnInit(): void { - // your columns definition - this.columns = []; - - this.gridOptions = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top - } - } - - angularGridReady(angularGrid: AngularGridInstance) { - this.gridObj = angularGrid.slickGrid; - } - - /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - changeFrozenColumnCount() { - if (this.gridObj && this.gridObj.setOptions) { - this.gridObj.setOptions({ - frozenColumn: this.frozenColumnCount - }); - } - } - - /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - changeFrozenRowCount() { - if (this.gridObj && this.gridObj.setOptions) { - this.gridObj.setOptions({ - frozenRow: this.frozenRowCount - }); - } - } - - /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - toggleFrozenBottomRows() { - if (this.gridObj && this.gridObj.setOptions) { - this.gridObj.setOptions({ - frozenBottom: !this.isFrozenBottom - }); - this.isFrozenBottom = !this.isFrozenBottom; // toggle the variable - } - } -} -``` - -## Animated Gif Demo -![](https://user-images.githubusercontent.com/643976/50852303-28d57c80-134d-11e9-859c-aeb55af24c24.gif) \ No newline at end of file diff --git a/frameworks/angular-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md b/frameworks/angular-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md index c8f47ed031..252578b139 100644 --- a/frameworks/angular-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md +++ b/frameworks/angular-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md @@ -72,12 +72,12 @@ this.columns = [ ...builtInItems, 'divider', { - command: 'freeze-column', - title: 'Freeze Column', + command: 'custom-pin-column', + title: 'Pin Column', iconCssClass: 'mdi mdi-pin', action: (e, args) => { - // Implement column freezing - console.log('Freeze column:', args.column.name); + // Implement column pinning + console.log('Pin column:', args.column.name); } } ]; @@ -224,4 +224,4 @@ this.columns.forEach(col => { } }; }); -``` \ No newline at end of file +``` diff --git a/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md b/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md new file mode 100644 index 0000000000..5744b87981 --- /dev/null +++ b/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md @@ -0,0 +1,179 @@ +#### Index +- [Columns/Rows Pinning Basic](#columnsrows-pinning-basic) +- [Rows Pinning starting from Bottom](#rows-pinning-starting-from-bottom) +- [Change Pinning Dynamically](#change-pinning-dynamically) +- [Animated Gif Demo](#animated-gif-demo) + +### Demo +[Demo Page](https://ghiscoding.github.io/angular-slickgrid-bs4-demo/#/example20) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts) + +### Introduction +Pinning keeps selected columns or rows attached to a grid edge while the remaining content scrolls. Columns can be pinned on the left or right, and rows can be pinned at the top or bottom. You can also change pinning dynamically with `setOptions()`. + +## Columns/Rows Pinning basic +To configure pinning for the entire lifetime of the grid, use the nested `pinning` Grid Option. + +```html + + +``` + +##### Component +```typescript +import { Component, OnInit} from '@angular/core'; + +export class GridBasicComponent implements OnInit { + columns: Column[]; + gridOptions: GridOption; + dataset: any[]; + + ngOnInit(): void { + // your columns definition + this.columns = []; + + this.gridOptions = { + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3 + } + } +} +``` + +> **Caution** +> Please be aware that pinned columns cannot consume the entire grid viewport. You can customize the validation with `invalidColumnPinningWidthCallback` or disable it with `skipPinningValidation`. + +> **Caution** +> The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. + +## Rows Pinning starting from bottom +To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. +##### Component +```ts +import { Component, OnInit} from '@angular/core'; + +export class GridBasicComponent implements OnInit { + columns: Column[]; + gridOptions: GridOption; + dataset: any[]; + + ngOnInit(): void { + // your columns definition + this.columns = []; + + this.gridOptions = { + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { bottom: [97, 98, 99] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3, frozenBottom: true + } + } +} +``` + +## Change Pinning Dynamically +You can change the number of pinned columns/rows and even the pinning of columns from top to bottom. For a demo of what that could look like, take a look at the [Animated Gif Demo](#animated-gif-demo) below. + +```html +
+ + + + + + + + + + + + + : {{ isPinnedBottom ? 'Bottom' : 'Top' }} + +
+ + + +``` + +##### Component +```ts +import { Component, OnInit} from '@angular/core'; +import { AngularGridInstance } from 'angular-slickgrid'; + +export class GridBasicComponent implements OnInit { + columns: Column[]; + dataset: any[]; + gridOptions: GridOption; + gridObj: any; + isPinnedBottom = false; + + ngOnInit(): void { + // your columns definition + this.columns = []; + + this.gridOptions = { + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, + } + } + + angularGridReady(angularGrid: AngularGridInstance) { + this.gridObj = angularGrid.slickGrid; + } + + /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ + changePinnedColumnCount() { + if (this.gridObj && this.gridObj.setOptions) { + this.gridObj.setOptions({ + pinning: { columns: { left: this.pinnedColumnCount } } + }); + } + } + + /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ + changePinnedRowCount() { + if (this.gridObj && this.gridObj.setOptions) { + this.gridObj.setOptions({ + pinning: { rows: { top: [0, 1, 2] } } + }); + } + } + + /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ + togglePinnedBottomRows() { + if (this.gridObj && this.gridObj.setOptions) { + this.gridObj.setOptions({ + pinning: { + rows: this.isPinnedBottom ? { top: [0, 1, 2], bottom: [] } : { top: [], bottom: [0, 1, 2] }, + } + }); + this.isPinnedBottom = !this.isPinnedBottom; // toggle the variable + } + } +} +``` + +## Animated Gif Demo +![](https://user-images.githubusercontent.com/643976/50852303-28d57c80-134d-11e9-859c-aeb55af24c24.gif) diff --git a/frameworks/angular-slickgrid/docs/grid-functionalities/resize-by-cell-content.md b/frameworks/angular-slickgrid/docs/grid-functionalities/resize-by-cell-content.md index 91f893a640..fd36580c52 100644 --- a/frameworks/angular-slickgrid/docs/grid-functionalities/resize-by-cell-content.md +++ b/frameworks/angular-slickgrid/docs/grid-functionalities/resize-by-cell-content.md @@ -101,7 +101,7 @@ export interface ResizeByContentOption { */ maxItemToInspectSingleColumnWidthByContent?: number; - /** Defaults to 50, what width to remove from new column width when the grid is a frozen (pinned) grid and its column width exceeds the viewport full width. */ + /** Defaults to 50, what width to remove from new column width when the grid is a pinned grid and its column width exceeds the viewport full width. */ widthToRemoveFromExceededWidthReadjustment?: number; } ``` @@ -135,4 +135,4 @@ export interface Column { /** no defaults, what is optional extra width padding to add to the calculation when resizing columns by their cell content */ resizeExtraWidthPadding?: number; } -``` \ No newline at end of file +``` diff --git a/frameworks/angular-slickgrid/docs/styling/multiple-column-header-rows.md b/frameworks/angular-slickgrid/docs/styling/multiple-column-header-rows.md index 37ec8f1165..df099c9e2b 100644 --- a/frameworks/angular-slickgrid/docs/styling/multiple-column-header-rows.md +++ b/frameworks/angular-slickgrid/docs/styling/multiple-column-header-rows.md @@ -59,7 +59,7 @@ Also note that if you use a stylesheet attached to your component (or inline), y ### Automatic header height -Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by frozen header panes and is recalculated after column resizing or autosizing. +Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by pinned header regions and is recalculated after column resizing or autosizing. ```ts const gridOptions = { diff --git a/frameworks/angular-slickgrid/docs/styling/styling.md b/frameworks/angular-slickgrid/docs/styling/styling.md index bfb3dd10cb..d33657d7d3 100644 --- a/frameworks/angular-slickgrid/docs/styling/styling.md +++ b/frameworks/angular-slickgrid/docs/styling/styling.md @@ -117,7 +117,7 @@ export const GlobalGridOptions = { iconCssClass: 'mdi mdi-menu', iconClearAllFiltersCommand: 'mdi mdi-filter-remove-outline', iconClearAllSortingCommand: 'mdi mdi-sort-variant-off', - iconClearFrozenColumnsCommand: 'mdi mdi-pin-off-outline', + iconUnpinningColumns: 'mdi mdi-pin-off-outline', iconExportCsvCommand: 'mdi mdi-download', iconExportExcelCommand: 'mdi mdi-file-excel-outline', iconExportTextDelimitedCommand: 'mdi mdi-download', @@ -270,4 +270,4 @@ If you want to inject custom css classes into the inner grids container, you can (onAngularGridCreated)="angularGridReady1($event.detail)" [containerClasses]="myContainerClasses" > -``` \ No newline at end of file +``` diff --git a/frameworks/angular-slickgrid/src/library/components/__tests__/angular-slickgrid.component.spec.ts b/frameworks/angular-slickgrid/src/library/components/__tests__/angular-slickgrid.component.spec.ts index ae4c2047b3..b696ff6d2f 100644 --- a/frameworks/angular-slickgrid/src/library/components/__tests__/angular-slickgrid.component.spec.ts +++ b/frameworks/angular-slickgrid/src/library/components/__tests__/angular-slickgrid.component.spec.ts @@ -1330,7 +1330,7 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () = expect(backendSpy).toHaveBeenCalledWith(mockColumnFilter, false); }); - it('should override frozen grid options when "pinning" is defined in the "presets" property', () => { + it('should override pinned grid options when "pinning" is defined in the "presets" property', () => { const pinningMock = { columns: { left: [], right: [] }, rows: { top: [], bottom: [] } } as CurrentPinning; component.options.presets = { pinning: pinningMock }; diff --git a/frameworks/aurelia-slickgrid/docs/TOC.md b/frameworks/aurelia-slickgrid/docs/TOC.md index 9b555a6337..c550f2f5fc 100644 --- a/frameworks/aurelia-slickgrid/docs/TOC.md +++ b/frameworks/aurelia-slickgrid/docs/TOC.md @@ -74,7 +74,7 @@ * [Custom Menu Slots](grid-functionalities/custom-menu-slots.md) * [Header Menu & Header Buttons](grid-functionalities/header-menu-header-buttons.md) * [Infinite Scroll](grid-functionalities/infinite-scroll.md) -* [Pinning (frozen) of Columns/Rows](grid-functionalities/frozen-columns-rows.md) +* [Pinning of Columns/Rows](grid-functionalities/pinning.md) * [Variable Row Height](grid-functionalities/variable-row-height.md) * [Providing data to the grid](grid-functionalities/providing-grid-data.md) * [Row Detail](grid-functionalities/row-detail.md) diff --git a/frameworks/aurelia-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md b/frameworks/aurelia-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md index 80a4f130a7..efde36dfac 100644 --- a/frameworks/aurelia-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md +++ b/frameworks/aurelia-slickgrid/docs/grid-functionalities/header-menu-header-buttons.md @@ -72,12 +72,12 @@ this.columns = [ ...builtInItems, 'divider', { - command: 'freeze-column', - title: 'Freeze Column', + command: 'custom-pin-column', + title: 'Pin Column', iconCssClass: 'mdi mdi-pin', action: (e, args) => { - // Implement column freezing - console.log('Freeze column:', args.column.name); + // Implement column pinning + console.log('Pin column:', args.column.name); } } ]; diff --git a/frameworks/aurelia-slickgrid/docs/grid-functionalities/frozen-columns-rows.md b/frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md similarity index 51% rename from frameworks/aurelia-slickgrid/docs/grid-functionalities/frozen-columns-rows.md rename to frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md index c0e1f24b82..b1ce0cf4d5 100644 --- a/frameworks/aurelia-slickgrid/docs/grid-functionalities/frozen-columns-rows.md +++ b/frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md @@ -8,10 +8,10 @@ [Demo Page](https://ghiscoding.github.io/aurelia-slickgrid-demos/#/example20) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/aurelia/src/examples/slickgrid/example20.ts) ### Introduction -One of the requested features, columns or rows pinning (aka frozen). You can pin 1 or more Columns and/or 1 or more Rows. Columns can only be pinned starting from the left side, while Rows can be pinned starting from the Top (default) or Bottom. You can also change the pinning dynamically with `setOptions()`. +Pinning keeps selected columns or rows attached to a grid edge while the remaining content scrolls. Columns can be pinned on the left or right, and rows can be pinned at the top or bottom. You can also change pinning dynamically with `setOptions()`. ## Columns/Rows Pinning basic -To set a pinning for the entire duration of the grid, simply use the Grid Options `frozenColumn` (starting from top) and `frozenRow` (starting from left), which are both `number` types. +To configure pinning for the entire lifetime of the grid, use the nested `pinning` Grid Option. ```html **Caution** -> Please be aware that `frozenColumn` (left canvas) **cannot** be wider than the actual grid viewport width and you will get an alert when you try to do this. You can disable it by setting `invalidColumnFreezeWidthCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). +> Please be aware that pinned columns cannot consume the entire grid viewport. You can customize the validation with `invalidColumnPinningWidthCallback` or disable it with `skipPinningValidation`. > **Caution** -> Also be aware that you also cannot uncheck (from Column Picker or Grid Menu) more columns than the actual `frozenColumn` index (in other words, you need to have at least 1, or more, columns on the right section of the freeze/pinning). Also similar as above you can disable it by setting `invalidColumnFreezePickerCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). +> The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. ## Rows Pinning starting from bottom -This is basically the same thing as previous code sample, except that you will set the Grid Option property `frozenBottom` to true and that it's. +To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component ```typescript export class GridBasicComponent { @@ -62,10 +65,12 @@ export class GridBasicComponent { this.columns = []; this.gridOptions = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns (starting from bottom with next property) - frozenBottom: true, // this will make rows to be pinned starting from the bottom and the number of rows will be 3 + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { bottom: [97, 98, 99] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3, frozenBottom: true } } } @@ -78,23 +83,23 @@ You can change the number of pinned columns/rows and even the pinning of columns
- - - - - - : {{ isFrozenBottom ? 'Bottom' : 'Top' }} + : {{ isPinnedBottom ? 'Bottom' : 'Top' }}
@@ -116,16 +121,18 @@ export class GridBasicComponent { gridOptions: GridOption; dataset: any[]; gridObj: any; - isFrozenBottom = false; + isPinnedBottom = false; attached(): void { // your columns definition this.columns = []; this.gridOptions = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, } } @@ -134,30 +141,32 @@ export class GridBasicComponent { } /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - changeFrozenColumnCount() { + changePinnedColumnCount() { if (this.gridObj && this.gridObj.setOptions) { this.gridObj.setOptions({ - frozenColumn: this.frozenColumnCount + pinning: { columns: { left: this.pinnedColumnCount } } }); } } /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - changeFrozenRowCount() { + changePinnedRowCount() { if (this.gridObj && this.gridObj.setOptions) { this.gridObj.setOptions({ - frozenRow: this.frozenRowCount + pinning: { rows: { top: [0, 1, 2] } } }); } } /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - toggleFrozenBottomRows() { + togglePinnedBottomRows() { if (this.gridObj && this.gridObj.setOptions) { this.gridObj.setOptions({ - frozenBottom: !this.isFrozenBottom + pinning: { + rows: this.isPinnedBottom ? { top: [0, 1, 2], bottom: [] } : { top: [], bottom: [0, 1, 2] }, + } }); - this.isFrozenBottom = !this.isFrozenBottom; // toggle the variable + this.isPinnedBottom = !this.isPinnedBottom; // toggle the variable } } } diff --git a/frameworks/aurelia-slickgrid/docs/grid-functionalities/resize-by-cell-content.md b/frameworks/aurelia-slickgrid/docs/grid-functionalities/resize-by-cell-content.md index bce18507d3..35290b9c0d 100644 --- a/frameworks/aurelia-slickgrid/docs/grid-functionalities/resize-by-cell-content.md +++ b/frameworks/aurelia-slickgrid/docs/grid-functionalities/resize-by-cell-content.md @@ -101,7 +101,7 @@ export interface ResizeByContentOption { */ maxItemToInspectSingleColumnWidthByContent?: number; - /** Defaults to 50, what width to remove from new column width when the grid is a frozen (pinned) grid and its column width exceeds the viewport full width. */ + /** Defaults to 50, what width to remove from new column width when the grid is a pinned grid and its column width exceeds the viewport full width. */ widthToRemoveFromExceededWidthReadjustment?: number; } ``` diff --git a/frameworks/aurelia-slickgrid/docs/styling/multiple-column-header-rows.md b/frameworks/aurelia-slickgrid/docs/styling/multiple-column-header-rows.md index 37ec8f1165..df099c9e2b 100644 --- a/frameworks/aurelia-slickgrid/docs/styling/multiple-column-header-rows.md +++ b/frameworks/aurelia-slickgrid/docs/styling/multiple-column-header-rows.md @@ -59,7 +59,7 @@ Also note that if you use a stylesheet attached to your component (or inline), y ### Automatic header height -Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by frozen header panes and is recalculated after column resizing or autosizing. +Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by pinned header regions and is recalculated after column resizing or autosizing. ```ts const gridOptions = { diff --git a/frameworks/aurelia-slickgrid/docs/styling/styling.md b/frameworks/aurelia-slickgrid/docs/styling/styling.md index 14214af54f..08f11b2d91 100644 --- a/frameworks/aurelia-slickgrid/docs/styling/styling.md +++ b/frameworks/aurelia-slickgrid/docs/styling/styling.md @@ -118,7 +118,7 @@ export const GlobalGridOptions = { iconCssClass: 'mdi mdi-menu', iconClearAllFiltersCommand: 'mdi mdi-filter-remove-outline', iconClearAllSortingCommand: 'mdi mdi-sort-variant-off', - iconClearFrozenColumnsCommand: 'mdi mdi-pin-off-outline', + iconUnpinningColumns: 'mdi mdi-pin-off-outline', iconExportCsvCommand: 'mdi mdi-download', iconExportExcelCommand: 'mdi mdi-file-excel-outline', iconExportTextDelimitedCommand: 'mdi mdi-download', diff --git a/frameworks/slickgrid-react/docs/TOC.md b/frameworks/slickgrid-react/docs/TOC.md index ef37f588b1..c9a1c8e080 100644 --- a/frameworks/slickgrid-react/docs/TOC.md +++ b/frameworks/slickgrid-react/docs/TOC.md @@ -74,7 +74,7 @@ * [Custom Menu Slots](grid-functionalities/menu-slots.md) * [Header Menu & Header Buttons](grid-functionalities/header-menu-header-buttons.md) * [Infinite Scroll](grid-functionalities/infinite-scroll.md) -* [Pinning (frozen) of Columns/Rows](grid-functionalities/frozen-columns-rows.md) +* [Pinning of Columns/Rows](grid-functionalities/pinning.md) * [Variable Row Height](grid-functionalities/variable-row-height.md) * [Providing data to the grid](grid-functionalities/providing-grid-data.md) * [Row Detail](grid-functionalities/row-detail.md) diff --git a/frameworks/slickgrid-react/docs/grid-functionalities/header-menu-header-buttons.md b/frameworks/slickgrid-react/docs/grid-functionalities/header-menu-header-buttons.md index bc56f11094..ec677d2f95 100644 --- a/frameworks/slickgrid-react/docs/grid-functionalities/header-menu-header-buttons.md +++ b/frameworks/slickgrid-react/docs/grid-functionalities/header-menu-header-buttons.md @@ -72,12 +72,12 @@ const columns = [ ...builtInItems, 'divider', { - command: 'freeze-column', - title: 'Freeze Column', + command: 'custom-pin-column', + title: 'Pin Column', iconCssClass: 'mdi mdi-pin', action: (e, args) => { - // Implement column freezing - console.log('Freeze column:', args.column.name); + // Implement column pinning + console.log('Pin column:', args.column.name); } } ]; diff --git a/frameworks/slickgrid-react/docs/grid-functionalities/frozen-columns-rows.md b/frameworks/slickgrid-react/docs/grid-functionalities/pinning.md similarity index 51% rename from frameworks/slickgrid-react/docs/grid-functionalities/frozen-columns-rows.md rename to frameworks/slickgrid-react/docs/grid-functionalities/pinning.md index e1f5e35ab6..2b69e01d55 100644 --- a/frameworks/slickgrid-react/docs/grid-functionalities/frozen-columns-rows.md +++ b/frameworks/slickgrid-react/docs/grid-functionalities/pinning.md @@ -8,10 +8,10 @@ [Demo Page](https://ghiscoding.github.io/slickgrid-react-demos/#/Example20) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/react/src/examples/slickgrid/Example20.tsx) ### Introduction -One of the requested features, columns or rows pinning (aka frozen). You can pin 1 or more Columns and/or 1 or more Rows. Columns can only be pinned starting from the left side, while Rows can be pinned starting from the Top (default) or Bottom. You can also change the pinning dynamically with `setOptions()`. +Pinning keeps selected columns or rows attached to a grid edge while the remaining content scrolls. Columns can be pinned on the left or right, and rows can be pinned at the top or bottom. You can also change pinning dynamically with `setOptions()`. ## Columns/Rows Pinning basic -To set a pinning for the entire duration of the grid, simply use the Grid Options `frozenColumn` (starting from top) and `frozenRow` (starting from left), which are both `number` types. +To configure pinning for the entire lifetime of the grid, use the nested `pinning` Grid Option. ##### Component ```tsx @@ -25,9 +25,12 @@ const Example: React.FC = () => { function defineGrid() { setColumns([]); setOptions({ - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3 }); } @@ -44,13 +47,13 @@ const Example: React.FC = () => { ``` > **Caution** -> Please be aware that `frozenColumn` (left canvas) **cannot** be wider than the actual grid viewport width and you will get an alert when you try to do this. You can disable it by setting `invalidColumnFreezeWidthCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). +> Please be aware that pinned columns cannot consume the entire grid viewport. You can customize the validation with `invalidColumnPinningWidthCallback` or disable it with `skipPinningValidation`. > **Caution** -> Also be aware that you also cannot uncheck (from Column Picker or Grid Menu) more columns than the actual `frozenColumn` index (in other words, you need to have at least 1, or more, columns on the right section of the freeze/pinning). Also similar as above you can disable it by setting `invalidColumnFreezePickerCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). +> The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. ## Rows Pinning starting from bottom -This is basically the same thing as previous code sample, except that you will set the Grid Option property `frozenBottom` to true and that it's. +To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component ```tsx const Example: React.FC = () => { @@ -64,17 +67,19 @@ const Example: React.FC = () => { // your columns definition setColumns([]); setOptions({ - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns (starting from bottom with next property) - frozenBottom: true, // this will make rows to be pinned starting from the bottom and the number of rows will be 3 + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { bottom: [97, 98, 99] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3, frozenBottom: true }); } } ``` ## Change Pinning Dynamically -You can change the number of pinned columns/rows and even the pinning of columns from top to bottom. For a demo of what that could look like, take a look at the [Animated Gif Demo](../grid-functionalities/frozen-columns-rows.md#animated-gif-demo) below. +You can change the number of pinned columns/rows and even the pinning of columns from top to bottom. For a demo of what that could look like, take a look at the [Animated Gif Demo](../grid-functionalities/pinning.md#animated-gif-demo) below. ##### Component ```tsx @@ -84,7 +89,7 @@ const Example: React.FC = () => { const [dataset, setDataset] = useState([]); const [columns, setColumns] = useState([]); const [options, setOptions] = useState(undefined); - const [isFrozenBottom, setIsFrozenBottom] = useState(false); + const [isPinnedBottom, setIsPinnedBottom] = useState(false); const reactGridRef = useRef(); useEffect(() => defineGrid(), []); @@ -96,38 +101,42 @@ const Example: React.FC = () => { function defineGrid() { setColumns([ /*...*/ ]); setOptions({ - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, }); } /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - function changeFrozenColumnCount() { + function changePinnedColumnCount() { if (reactGridRef.current?.slickGrid.setOptions) { reactGridRef.current?.slickGrid.setOptions({ - frozenColumn: frozenColumnCount + pinning: { columns: { left: pinnedColumnCount } } }); } } /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - function changeFrozenRowCount() { + function changePinnedRowCount() { if (reactGridRef.current?.slickGrid.setOptions) { reactGridRef.current?.slickGrid.setOptions({ - frozenRow: frozenRowCount + pinning: { rows: { top: [0, 1, 2] } } }); } } /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - function toggleFrozenBottomRows() { - const newIsFrozenBottom = !isFrozenBottom; + function togglePinnedBottomRows() { + const newIsPinnedBottom = !isPinnedBottom; if (reactGridRef.current?.slickGrid.setOptions) { reactGridRef.current?.slickGrid.setOptions({ - frozenBottom: newIsFrozenBottom + pinning: { + rows: newIsPinnedBottom ? { top: [], bottom: [0, 1, 2] } : { top: [0, 1, 2], bottom: [] }, + } }); - setIsFrozenBottom(newIsFrozenBottom); // toggle the variable + setIsPinnedBottom(newIsPinnedBottom); // toggle the variable } } @@ -136,15 +145,15 @@ const Example: React.FC = () => {
- changeFrozenRowCount($event)} /> - - changeFrozenColumnCount($event)} /> - diff --git a/frameworks/slickgrid-react/docs/grid-functionalities/resize-by-cell-content.md b/frameworks/slickgrid-react/docs/grid-functionalities/resize-by-cell-content.md index 240d04175e..a19929dbc8 100644 --- a/frameworks/slickgrid-react/docs/grid-functionalities/resize-by-cell-content.md +++ b/frameworks/slickgrid-react/docs/grid-functionalities/resize-by-cell-content.md @@ -101,7 +101,7 @@ export interface ResizeByContentOption { */ maxItemToInspectSingleColumnWidthByContent?: number; - /** Defaults to 50, what width to remove from new column width when the grid is a frozen (pinned) grid and its column width exceeds the viewport full width. */ + /** Defaults to 50, what width to remove from new column width when the grid is a pinned grid and its column width exceeds the viewport full width. */ widthToRemoveFromExceededWidthReadjustment?: number; } ``` diff --git a/frameworks/slickgrid-react/docs/styling/multiple-column-header-rows.md b/frameworks/slickgrid-react/docs/styling/multiple-column-header-rows.md index 37ec8f1165..df099c9e2b 100644 --- a/frameworks/slickgrid-react/docs/styling/multiple-column-header-rows.md +++ b/frameworks/slickgrid-react/docs/styling/multiple-column-header-rows.md @@ -59,7 +59,7 @@ Also note that if you use a stylesheet attached to your component (or inline), y ### Automatic header height -Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by frozen header panes and is recalculated after column resizing or autosizing. +Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by pinned header regions and is recalculated after column resizing or autosizing. ```ts const gridOptions = { diff --git a/frameworks/slickgrid-react/docs/styling/styling.md b/frameworks/slickgrid-react/docs/styling/styling.md index 5ff9ddaa13..963e1de1f5 100644 --- a/frameworks/slickgrid-react/docs/styling/styling.md +++ b/frameworks/slickgrid-react/docs/styling/styling.md @@ -110,7 +110,7 @@ export const GlobalGridOptions = { iconCssClass: 'mdi mdi-menu', iconClearAllFiltersCommand: 'mdi mdi-filter-remove-outline', iconClearAllSortingCommand: 'mdi mdi-sort-variant-off', - iconClearFrozenColumnsCommand: 'mdi mdi-pin-off-outline', + iconUnpinningColumns: 'mdi mdi-pin-off-outline', iconExportCsvCommand: 'mdi mdi-download', iconExportExcelCommand: 'mdi mdi-file-excel-outline', iconExportTextDelimitedCommand: 'mdi mdi-download', diff --git a/frameworks/slickgrid-vue/docs/TOC.md b/frameworks/slickgrid-vue/docs/TOC.md index e5a1592fbe..c9058d2d91 100644 --- a/frameworks/slickgrid-vue/docs/TOC.md +++ b/frameworks/slickgrid-vue/docs/TOC.md @@ -74,7 +74,7 @@ * [Header & Footer Slots](grid-functionalities/header-footer-slots.md) * [Custom Menu Slots](grid-functionalities/menu-slots.md) * [Infinite Scroll](grid-functionalities/infinite-scroll.md) -* [Pinning (frozen) of Columns/Rows](grid-functionalities/frozen-columns-rows.md) +* [Pinning of Columns/Rows](grid-functionalities/pinning.md) * [Variable Row Height](grid-functionalities/variable-row-height.md) * [Providing data to the grid](grid-functionalities/providing-grid-data.md) * [Row Detail](grid-functionalities/row-detail.md) @@ -111,4 +111,3 @@ * [Migration Guide to 9.x (2025-05-10)](migrations/migration-to-9.x.md) * [Migration Guide to 10.x (2026-03-02)](migrations/migration-to-10.x.md) - diff --git a/frameworks/slickgrid-vue/docs/grid-functionalities/header-menu-header-buttons.md b/frameworks/slickgrid-vue/docs/grid-functionalities/header-menu-header-buttons.md index 98dae3fab0..7d42b73a1e 100644 --- a/frameworks/slickgrid-vue/docs/grid-functionalities/header-menu-header-buttons.md +++ b/frameworks/slickgrid-vue/docs/grid-functionalities/header-menu-header-buttons.md @@ -71,12 +71,12 @@ columns.value = [ ...builtInItems, 'divider', { - command: 'freeze-column', - title: 'Freeze Column', + command: 'custom-pin-column', + title: 'Pin Column', iconCssClass: 'mdi mdi-pin', action: (e, args) => { - // Implement column freezing - console.log('Freeze column:', args.column.name); + // Implement column pinning + console.log('Pin column:', args.column.name); } } ]; diff --git a/frameworks/slickgrid-vue/docs/grid-functionalities/frozen-columns-rows.md b/frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md similarity index 53% rename from frameworks/slickgrid-vue/docs/grid-functionalities/frozen-columns-rows.md rename to frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md index 039ab9c29c..83b74aeaa0 100644 --- a/frameworks/slickgrid-vue/docs/grid-functionalities/frozen-columns-rows.md +++ b/frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md @@ -8,10 +8,10 @@ [Demo Page](https://ghiscoding.github.io/slickgrid-vue-demos/#/Example20) / [Demo Component](https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/vue/src/components/Example20.vue) ### Introduction -One of the requested features, columns or rows pinning (aka frozen). You can pin 1 or more Columns and/or 1 or more Rows. Columns can only be pinned starting from the left side, while Rows can be pinned starting from the Top (default) or Bottom. You can also change the pinning dynamically with `setOptions()`. +Pinning keeps selected columns or rows attached to a grid edge while the remaining content scrolls. Columns can be pinned on the left or right, and rows can be pinned at the top or bottom. You can also change pinning dynamically with `setOptions()`. ## Columns/Rows Pinning basic -To set a pinning for the entire duration of the grid, simply use the Grid Options `frozenColumn` (starting from top) and `frozenRow` (starting from left), which are both `number` types. +To configure pinning for the entire lifetime of the grid, use the nested `pinning` Grid Option. ##### Component ```vue @@ -31,9 +31,12 @@ function defineGrid() { columns.value = []; gridOptions.value = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3 } } @@ -50,13 +53,13 @@ function defineGrid() { ``` > **Caution** -> Please be aware that `frozenColumn` (left canvas) **cannot** be wider than the actual grid viewport width and you will get an alert when you try to do this. You can disable it by setting `invalidColumnFreezeWidthCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). +> Please be aware that pinned columns cannot consume the entire grid viewport. You can customize the validation with `invalidColumnPinningWidthCallback` or disable it with `skipPinningValidation`. > **Caution** -> Also be aware that you also cannot uncheck (from Column Picker or Grid Menu) more columns than the actual `frozenColumn` index (in other words, you need to have at least 1, or more, columns on the right section of the freeze/pinning). Also similar as above you can disable it by setting `invalidColumnFreezePickerCallback` to `undefined` (which will still do the validation but it won't show an alert to the end user) or set `skipFreezeColumnValidation` to completely skip the validation. Also note that if the condition is invalid, it will cancel the action and reapply previous `frozenColumn` value (unless skip validation is enabled). +> The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. ## Rows Pinning starting from bottom -This is basically the same thing as previous code sample, except that you will set the Grid Option property `frozenBottom` to true and that it's. +To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component ```vue @@ -77,17 +80,19 @@ function defineGrid() { columns.value = []; gridOptions.value = { - alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns (starting from bottom with next property) - frozenBottom: true, // this will make rows to be pinned starting from the bottom and the number of rows will be 3 + alwaysShowVerticalScroll: false, + pinning: { + columns: { left: 2 }, + rows: { bottom: [97, 98, 99] }, + }, + // v11 and lower: frozenColumn: 2, frozenRow: 3, frozenBottom: true } } ``` ## Change Pinning Dynamically -You can change the number of pinned columns/rows and even the pinning of columns from top to bottom. For a demo of what that could look like, take a look at the [Animated Gif Demo](../grid-functionalities/frozen-columns-rows.md#animated-gif-demo) below. +You can change the number of pinned columns/rows and even the pinning of columns from top to bottom. For a demo of what that could look like, take a look at the [Animated Gif Demo](../grid-functionalities/pinning.md#animated-gif-demo) below. ##### Component ```vue @@ -110,8 +115,10 @@ function defineGrid() { gridOptions.value = { alwaysShowVerticalScroll: false, // disable scroll since we don't want it to show on the left pinned columns - frozenColumn: 2, // number of pinned columns starting from the left - frozenRow: 3, // number of pinned columns starting from the top + pinning: { + columns: { left: 2 }, + rows: { top: [0, 1, 2] }, + }, } } @@ -120,30 +127,30 @@ function vueGridReady(vGrid: SlickgridVueInstance) { } /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ -function changeFrozenColumnCount() { +function changePinnedColumnCount() { if (gridObj?.setOptions) { gridObj.setOptions({ - frozenColumn: frozenColumnCount + pinning: { columns: { left: pinnedColumnCount } } }); } } /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ -function changeFrozenRowCount() { +function changePinnedRowCount() { if (gridObj?.setOptions) { gridObj.setOptions({ - frozenRow: frozenRowCount + pinning: { rows: { top: [0, 1, 2] } } }); } } /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ -function toggleFrozenBottomRows() { +function togglePinnedBottomRows() { if (gridObj?.setOptions) { gridObj.setOptions({ - frozenBottom: !isFrozenBottom + pinning: { rows: { top: [], bottom: [0, 1, 2] } } }); - isFrozenBottom = !isFrozenBottom; // toggle the variable + isPinnedBottom = !isPinnedBottom; // toggle the variable } } @@ -153,15 +160,15 @@ function toggleFrozenBottomRows() {
- - - - diff --git a/frameworks/slickgrid-vue/docs/grid-functionalities/resize-by-cell-content.md b/frameworks/slickgrid-vue/docs/grid-functionalities/resize-by-cell-content.md index d1817b624e..2dc077e840 100644 --- a/frameworks/slickgrid-vue/docs/grid-functionalities/resize-by-cell-content.md +++ b/frameworks/slickgrid-vue/docs/grid-functionalities/resize-by-cell-content.md @@ -102,7 +102,7 @@ export interface ResizeByContentOption { */ maxItemToInspectSingleColumnWidthByContent?: number; - /** Defaults to 50, what width to remove from new column width when the grid is a frozen (pinned) grid and its column width exceeds the viewport full width. */ + /** Defaults to 50, what width to remove from new column width when the grid is a pinned grid and its column width exceeds the viewport full width. */ widthToRemoveFromExceededWidthReadjustment?: number; } ``` diff --git a/frameworks/slickgrid-vue/docs/styling/multiple-column-header-rows.md b/frameworks/slickgrid-vue/docs/styling/multiple-column-header-rows.md index 37ec8f1165..df099c9e2b 100644 --- a/frameworks/slickgrid-vue/docs/styling/multiple-column-header-rows.md +++ b/frameworks/slickgrid-vue/docs/styling/multiple-column-header-rows.md @@ -59,7 +59,7 @@ Also note that if you use a stylesheet attached to your component (or inline), y ### Automatic header height -Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by frozen header panes and is recalculated after column resizing or autosizing. +Set `autoHeaderHeight: true` to size column headers from their rendered content instead of using a fixed row count. This supports multi-line text, HTML, and DOM elements used as column names. The calculated height is shared by pinned header regions and is recalculated after column resizing or autosizing. ```ts const gridOptions = { diff --git a/frameworks/slickgrid-vue/docs/styling/styling.md b/frameworks/slickgrid-vue/docs/styling/styling.md index 505df47303..e56faaa6f3 100644 --- a/frameworks/slickgrid-vue/docs/styling/styling.md +++ b/frameworks/slickgrid-vue/docs/styling/styling.md @@ -114,7 +114,7 @@ export const GlobalGridOptions = { iconCssClass: 'mdi mdi-menu', iconClearAllFiltersCommand: 'mdi mdi-filter-remove-outline', iconClearAllSortingCommand: 'mdi mdi-sort-variant-off', - iconClearFrozenColumnsCommand: 'mdi mdi-pin-off-outline', + iconUnpinningColumns: 'mdi mdi-pin-off-outline', iconExportCsvCommand: 'mdi mdi-download', iconExportExcelCommand: 'mdi mdi-file-excel-outline', iconExportTextDelimitedCommand: 'mdi mdi-download', diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index b4fe00ed3e..5a609bb70a 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -10,7 +10,7 @@ export class Constants { TEXT_CLEAR_ALL_FILTERS: 'Clear all Filters', TEXT_CLEAR_ALL_GROUPING: 'Clear all Grouping', TEXT_CLEAR_ALL_SORTING: 'Clear all Sorting', - TEXT_CLEAR_PINNING: 'Unfreeze Columns/Rows', + TEXT_CLEAR_PINNING: 'Unpin Columns/Rows', TEXT_CLONE: 'Clone', TEXT_COLLAPSE_ALL_GROUPS: 'Collapse all Groups', TEXT_CONTAINS: 'Contains', @@ -38,8 +38,10 @@ export class Constants { TEXT_EXPORT_TO_TAB_DELIMITED: 'Export in Text format (Tab delimited)', TEXT_FORCE_FIT_COLUMNS: 'Force fit columns', TEXT_FILTER_SHORTCUTS: 'Filter Shortcuts', - TEXT_FREEZE_COLUMNS: 'Freeze Columns', - TEXT_PIN_COLUMN: 'Pin Column', + TEXT_PIN_COLUMNS: 'Pin Through Here', + TEXT_PIN_COLUMN: 'Column Pinning', + TEXT_PIN_LEFT: 'Pin Left', + TEXT_PIN_RIGHT: 'Pin Right', TEXT_GREATER_THAN: 'Greater than', TEXT_GREATER_THAN_OR_EQUAL_TO: 'Greater than or equal to', TEXT_GROUP_BY: 'Group By', @@ -73,7 +75,7 @@ export class Constants { TEXT_TOGGLE_DARK_MODE: 'Toggle Dark Mode', TEXT_TOGGLE_FILTER_ROW: 'Toggle Filter Row', TEXT_TOGGLE_PRE_HEADER_ROW: 'Toggle Pre-Header Row', - TEXT_UNFREEZE_COLUMNS: 'Unfreeze Columns', + TEXT_UNPIN_COLUMNS: 'Unpin All Columns', TEXT_UNPIN_COLUMN: 'Unpin Column', TEXT_X_OF_Y_SELECTED: '# of % selected', TEXT_X_OF_Y_MASS_SELECTED: '{{x}} of {{y}} selected', diff --git a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts index 45a8d29694..0788da9d8d 100644 --- a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts @@ -664,7 +664,7 @@ describe('SlickGrid unified pinning', () => { } }); - it('handles regular and shift mouse-wheel scrolling and prevents native scrolling for frozen columns', () => { + it('handles regular and shift mouse-wheel scrolling and prevents native scrolling for pinned columns', () => { const slickGrid = createGrid(); const horizontalScroller = (slickGrid as any)._viewportScrollContainerX as HTMLDivElement; const verticalScroller = (slickGrid as any)._viewportScrollContainerY as HTMLDivElement; diff --git a/packages/common/src/core/__tests__/slickGrid.spec.ts b/packages/common/src/core/__tests__/slickGrid.spec.ts index b13c5309ef..42de19e1be 100644 --- a/packages/common/src/core/__tests__/slickGrid.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid.spec.ts @@ -1747,7 +1747,7 @@ describe('SlickGrid core file', () => { expect(grid.getFooterRowColumn('firstName')).toEqual(footerElms[0].querySelector('.slick-footerrow-column')); }); - it('should show footer when "showFooterRow" is enabled with frozen column', () => { + it('should show footer when "showFooterRow" is enabled with pinned column', () => { const columns = [ { id: 'firstName', field: 'firstName', name: 'First Name', alwaysRenderColumn: true }, { id: 'lastName', field: 'lastName', name: 'Last Name', hidden: true }, @@ -2308,10 +2308,11 @@ describe('SlickGrid core file', () => { describe('getActiveCanvasNode() function', () => { it('should return the active canvas when calling the method when the Event does not include any target', () => { grid = new SlickGrid(container, [], columns, defaultOptions); + (grid as any)._activeCanvasNode = undefined; const mockEvent = new CustomEvent('click'); const result = grid.getActiveCanvasNode(mockEvent); - expect(result).toBe(grid.getActiveCanvasNode()); + expect(result).toBe(container.querySelector('.grid-canvas')); }); it('should return closest grid canvas when calling the method when the Event includes grid canvas', () => { @@ -2346,6 +2347,7 @@ describe('SlickGrid core file', () => { describe('getActiveViewportNode() function', () => { it('should return the active viewport when calling the method when the Event does not include any target', () => { grid = new SlickGrid(container, [], columns, defaultOptions); + (grid as any)._activeViewportNode = undefined; const mockEvent = new CustomEvent('click'); const result = grid.getActiveViewportNode(mockEvent); @@ -2410,6 +2412,51 @@ describe('SlickGrid core file', () => { }); }); + it('should apply sticky docking chrome offsets and preserve a hidden range endpoint', () => { + const dockingColumns = [ + { id: 'a', field: 'a', name: 'A', width: 80 }, + { id: 'b', field: 'b', name: 'B', width: 80 }, + ] as Column[]; + grid = new SlickGrid(container, [{ id: 0, a: 'a0', b: 'b0' }], dockingColumns, { + ...defaultOptions, + pinning: { columns: { right: ['b'] } }, + }); + grid.init(); + + const internals = grid as any; + const rightDocking = internals.dockingLayout.right[0]; + rightDocking.sticky = true; + rightDocking.naturalOffset = 80; + internals.dockingLayout.contentWidth = 160; + internals.dockingLayout.leftBaseWidth = 0; + internals.dockingLayout.rightWidth = 80; + internals.viewportW = 200; + internals.scrollLeft = 10; + Object.defineProperty(internals._viewportScrollContainerX, 'clientWidth', { configurable: true, value: 200 }); + internals._dockingHorizontalScroller = undefined; + + internals.applyDockingChromeScrollOffsets(); + const rightHeader = container.querySelector('.slick-header-column[data-id="b"]'); + expect(rightHeader?.style.transform).toContain('translateX'); + + internals.applyDockingToColumnChrome(); + expect(rightHeader?.classList.contains('slick-column-sticky')).toBe(true); + + internals.columns[0].hidden = true; + internals.dockingByColumn = new Map([[1, { band: 'right' }]]); + internals.columnPosRight = [77, 160]; + expect(internals.getColumnRangeRight(0, 0)).toBe(77); + }); + + it('should merge row-only pinning without creating a columns option', () => { + grid = new SlickGrid(container, [{ id: 0, firstName: 'John' }], columns, defaultOptions); + grid.init(); + + grid.setOptions({ pinning: { rows: { top: [0] } } }); + + expect(grid.getOptions().pinning).toEqual({ rows: { top: [0], bottom: [] } }); + }); + describe('getCellNodeBox() function', () => { it('should return null when no data is empty', () => { const columns = [ @@ -3993,7 +4040,7 @@ describe('SlickGrid core file', () => { expect(columns[4].width).toBe(80); }); - it('should resize 3rd column that has a "minWidth" defined using default sizing grid options with a frozen column', () => { + it('should resize 3rd column that has a "minWidth" defined using default sizing grid options with a pinned column', () => { grid = new SlickGrid(container, data, columns, { ...defaultOptions, forceFitColumns: false, @@ -4041,7 +4088,7 @@ describe('SlickGrid core file', () => { expect(columns[4].width).toBe(80); }); - it('should resize 3rd column that has a "minWidth" with a frozen column that is greater than available columns', () => { + it('should resize 3rd column that has a "minWidth" with a pinned column that is greater than available columns', () => { grid = new SlickGrid(container, data, columns, { ...defaultOptions, forceFitColumns: false, @@ -4298,7 +4345,7 @@ describe('SlickGrid core file', () => { expect(columns[4].width).toBe(199); }); - it('should resize 3rd column with forceFitColumns option enabled with a frozen column', () => { + it('should resize 3rd column with forceFitColumns option enabled with a pinned column', () => { grid = new SlickGrid(container, data, columns, { ...defaultOptions, forceFitColumns: true, pinning: { columns: { left: 0 } } }); grid.init(); grid.autosizeColumns(); @@ -4338,7 +4385,7 @@ describe('SlickGrid core file', () => { expect(columns[4].width).toBe(199); }); - it('should resize 3rd column without forceFitColumns option but with a frozen column', () => { + it('should resize 3rd column without forceFitColumns option but with a pinned column', () => { grid = new SlickGrid(container, data, columns, { ...defaultOptions, forceFitColumns: false, pinning: { columns: { left: 0 } } }); grid.init(); grid.autosizeColumns(); @@ -4378,7 +4425,7 @@ describe('SlickGrid core file', () => { expect(columns[4].width).toBeGreaterThanOrEqual(550); }); - it('should resize 4th column with forceFitColumns option enabled and a column with maxWidth and a frozen column', () => { + it('should resize 4th column with forceFitColumns option enabled and a column with maxWidth and a pinned column', () => { grid = new SlickGrid(container, data, columns, { ...defaultOptions, forceFitColumns: true, pinning: { columns: { left: 0 } } }); grid.init(); grid.autosizeColumns(); @@ -5211,7 +5258,7 @@ describe('SlickGrid core file', () => { { id: 1, firstName: 'Jane', lastName: 'Doe', age: 28 }, ]; - it('should scroll when calling scrollCellIntoView() with lower position than frozen column', () => { + it('should scroll when calling scrollCellIntoView() with lower position than pinned column', () => { grid = new SlickGrid(container, data, columns, { ...defaultOptions, pinning: { columns: { left: 0 } } }); grid.init(); const renderSpy = vi.spyOn(grid, 'render'); @@ -5245,6 +5292,22 @@ describe('SlickGrid core file', () => { expect(horizontalScroller.scrollLeft).toBeGreaterThan(0); }); + it('should reveal the natural position of a sticky column currently docked at the right edge', () => { + grid = new SlickGrid(container, [{ id: 0, firstName: 'John', lastName: 'Doe', age: 30 }], columns, { + ...defaultOptions, + pinning: { columns: { left: 0, right: [] } }, + }); + grid.init(); + + const internals = grid as any; + internals.dockingByColumn.set(1, { band: 'right', sticky: true, naturalOffset: 80, width: 80 }); + const scrollSpy = vi.spyOn(internals, 'internalScrollColumnIntoView').mockImplementation(() => undefined); + + grid.scrollCellIntoView(0, 1); + + expect(scrollSpy).toHaveBeenCalled(); + }); + it('should find the first column intersecting the horizontal render range', () => { const wideColumns = Array.from({ length: 100 }, (_, index) => ({ id: `column${index}`, diff --git a/packages/common/src/core/__tests__/slickInteractions.spec.ts b/packages/common/src/core/__tests__/slickInteractions.spec.ts index adb5ba21c8..96cc9bce38 100644 --- a/packages/common/src/core/__tests__/slickInteractions.spec.ts +++ b/packages/common/src/core/__tests__/slickInteractions.spec.ts @@ -340,8 +340,8 @@ describe('Resizable class', () => { Object.defineProperty(pointerUpEvt, 'pointerId', { writable: true, configurable: true, value: 1 }); containerElement.dispatchEvent(pointerDownEvt); - containerElement.dispatchEvent(pointerMoveEvt); - containerElement.dispatchEvent(pointerUpEvt); + document.dispatchEvent(pointerMoveEvt); + document.dispatchEvent(pointerUpEvt); } else { const mdEvt = new MouseEvent('mousedown'); Object.defineProperty(mdEvt, 'clientX', { writable: true, configurable: true, value: 10 }); @@ -399,8 +399,8 @@ describe('Resizable class', () => { Object.defineProperty(pointerUpEvt, 'pointerId', { writable: true, configurable: true, value: 1 }); containerElement.dispatchEvent(pointerDownEvt); - containerElement.dispatchEvent(pointerMoveEvt); - containerElement.dispatchEvent(pointerUpEvt); + document.dispatchEvent(pointerMoveEvt); + document.dispatchEvent(pointerUpEvt); expect(setPointerCaptureSpy).toHaveBeenCalledWith(1); expect(resizeStartSpy).toHaveBeenCalledWith(pointerDownEvt, { @@ -413,6 +413,49 @@ describe('Resizable class', () => { expect(releasePointerCaptureSpy).toHaveBeenCalledWith(1); }); + it('should continue resizing when the handle loses pointer capture during a layout update', () => { + const resizeSpy = vi.fn(); + const resizeEndSpy = vi.fn(); + const handle = document.createElement('div'); + const newParent = document.createElement('div'); + const setPointerCaptureSpy = vi.fn(); + const releasePointerCaptureSpy = vi.fn(); + const hasPointerCaptureSpy = vi.fn().mockReturnValue(true); + + (window as any).PointerEvent = class PointerEventMock extends Event {}; + (handle as any).setPointerCapture = setPointerCaptureSpy; + (handle as any).releasePointerCapture = releasePointerCaptureSpy; + (handle as any).hasPointerCapture = hasPointerCaptureSpy; + containerElement.appendChild(handle); + document.body.appendChild(containerElement); + document.body.appendChild(newParent); + + rsz = Resizable({ + resizeableElement: containerElement, + resizeableHandleElement: handle, + onResize: resizeSpy, + onResizeEnd: resizeEndSpy, + }); + + const createPointerEvent = (type: string) => { + const event = new Event(type); + Object.defineProperty(event, 'pointerType', { writable: true, configurable: true, value: 'mouse' }); + Object.defineProperty(event, 'pointerId', { writable: true, configurable: true, value: 21 }); + Object.defineProperty(event, 'button', { writable: true, configurable: true, value: 0 }); + return event; + }; + + handle.dispatchEvent(createPointerEvent('pointerdown')); + newParent.appendChild(handle); + handle.dispatchEvent(createPointerEvent('lostpointercapture')); + document.dispatchEvent(createPointerEvent('pointermove')); + document.dispatchEvent(createPointerEvent('pointerup')); + + expect(resizeSpy).toHaveBeenCalledTimes(1); + expect(resizeEndSpy).toHaveBeenCalledTimes(1); + expect(releasePointerCaptureSpy).toHaveBeenCalledWith(21); + }); + it('should ignore non-left mouse button with pointer events', () => { const resizeStartSpy = vi.fn(); @@ -502,12 +545,12 @@ describe('Resizable class', () => { const wrongPointerMoveEvt = new Event('pointermove'); Object.defineProperty(wrongPointerMoveEvt, 'pointerType', { writable: true, configurable: true, value: 'mouse' }); Object.defineProperty(wrongPointerMoveEvt, 'pointerId', { writable: true, configurable: true, value: 8 }); - containerElement.dispatchEvent(wrongPointerMoveEvt); + document.dispatchEvent(wrongPointerMoveEvt); const wrongPointerUpEvt = new Event('pointerup'); Object.defineProperty(wrongPointerUpEvt, 'pointerType', { writable: true, configurable: true, value: 'mouse' }); Object.defineProperty(wrongPointerUpEvt, 'pointerId', { writable: true, configurable: true, value: 8 }); - containerElement.dispatchEvent(wrongPointerUpEvt); + document.dispatchEvent(wrongPointerUpEvt); expect(resizeSpy).not.toHaveBeenCalled(); expect(resizeEndSpy).not.toHaveBeenCalled(); @@ -535,7 +578,7 @@ describe('Resizable class', () => { Object.defineProperty(pointerMoveEvt, 'pointerId', { writable: true, configurable: true, value: 9 }); const preventDefaultSpy = vi.fn(); Object.defineProperty(pointerMoveEvt, 'preventDefault', { writable: true, configurable: true, value: preventDefaultSpy }); - containerElement.dispatchEvent(pointerMoveEvt); + document.dispatchEvent(pointerMoveEvt); expect(preventDefaultSpy).not.toHaveBeenCalled(); expect(resizeSpy).toHaveBeenCalled(); diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index 385e30d9a9..8180657045 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -1724,9 +1724,7 @@ export class SlickGrid = Column, O e `.slick-header-column[data-id="${String(this.columns[idx]?.id ?? columnIdOrIdx)}"]` ) as HTMLDivElement; } - const targetHeader = this.usesDockingChromeRegions() - ? this.getDockingChromeRegion('header', this.getColumnDockingBand(idx)) - : this._headerL; + const targetHeader = this._headerL; const targetIndex = idx; const directMatch = targetHeader.children[targetIndex] as HTMLDivElement | undefined; const targetColumnId = String(this.columns[idx]?.id ?? columnIdOrIdx); @@ -1780,9 +1778,7 @@ export class SlickGrid = Column, O e if (this.usesDockingChromeRegions()) { return this._headerRowL.querySelector(`.slick-headerrow-column.l${idx}`) as HTMLDivElement; } - const headerRowTarget = this.usesDockingChromeRegions() - ? this.getDockingChromeRegion('headerRow', this.getColumnDockingBand(idx)) - : this._headerRowL; + const headerRowTarget = this._headerRowL; return (headerRowTarget.querySelector(`.slick-headerrow-column.l${idx}`) || headerRowTarget.children[idx]) as HTMLDivElement; } @@ -1795,9 +1791,7 @@ export class SlickGrid = Column, O e if (this.usesDockingChromeRegions()) { return this._footerRowL?.querySelector(`.slick-footerrow-column.l${idx}`) as HTMLDivElement; } - const footerRowTarget = this.usesDockingChromeRegions() - ? this.getDockingChromeRegion('footerRow', this.getColumnDockingBand(idx)) - : this._footerRowL; + const footerRowTarget = this._footerRowL; return (footerRowTarget?.querySelector(`.slick-footerrow-column.l${idx}`) || footerRowTarget?.children[idx]) as HTMLDivElement; } @@ -8032,8 +8026,10 @@ export class SlickGrid = Column, O e const docking = this.dockingByColumn.get(cell); const isPermanentPinnedColumn = docking && docking.band !== 'center' && !docking.sticky; // Permanent pins are already visible; sticky columns must reveal their - // natural position before keyboard navigation activates them. - if (!isPermanentPinnedColumn && docking?.band === 'center') { + // natural position before keyboard navigation activates them, regardless + // of which edge currently owns the sticky column. Center columns retain + // the existing scroll-into-view behavior. + if (!isPermanentPinnedColumn && (docking?.sticky || docking?.band === 'center')) { const colspan = this.getColspan(row, cell); const lastCell = cell + (colspan > 1 ? colspan - 1 : 0); const { left, right } = this.getNaturalColumnRange(cell, lastCell); diff --git a/packages/common/src/core/slickInteractions.ts b/packages/common/src/core/slickInteractions.ts index 2e6abb2acb..86f50cca11 100644 --- a/packages/common/src/core/slickInteractions.ts +++ b/packages/common/src/core/slickInteractions.ts @@ -306,17 +306,19 @@ export function Resizable(options: ResizableOption): { } function addPointerListeners(): void { - resizeableHandleElement.addEventListener('pointermove', pointerResizingHandler as EventListener); - resizeableHandleElement.addEventListener('pointerup', pointerResizeEndHandler as EventListener); - resizeableHandleElement.addEventListener('pointercancel', pointerResizeEndHandler as EventListener); - resizeableHandleElement.addEventListener('lostpointercapture', pointerResizeEndHandler as EventListener); + // Track the pointer on the document because resizing can move or reparent + // the header that owns the handle (for example when pinning/sticky docking + // is active). A listener attached only to the handle stops receiving + // events after that layout update. + document.addEventListener('pointermove', pointerResizingHandler as EventListener); + document.addEventListener('pointerup', pointerResizeEndHandler as EventListener); + document.addEventListener('pointercancel', pointerResizeEndHandler as EventListener); } function removePointerListeners(): void { - resizeableHandleElement.removeEventListener('pointermove', pointerResizingHandler as EventListener); - resizeableHandleElement.removeEventListener('pointerup', pointerResizeEndHandler as EventListener); - resizeableHandleElement.removeEventListener('pointercancel', pointerResizeEndHandler as EventListener); - resizeableHandleElement.removeEventListener('lostpointercapture', pointerResizeEndHandler as EventListener); + document.removeEventListener('pointermove', pointerResizingHandler as EventListener); + document.removeEventListener('pointerup', pointerResizeEndHandler as EventListener); + document.removeEventListener('pointercancel', pointerResizeEndHandler as EventListener); } function executeResizeCallbackWhenDefined( diff --git a/packages/common/src/extensions/__tests__/slickCellRangeSelector.spec.ts b/packages/common/src/extensions/__tests__/slickCellRangeSelector.spec.ts index c3effb0721..7ec39612dd 100644 --- a/packages/common/src/extensions/__tests__/slickCellRangeSelector.spec.ts +++ b/packages/common/src/extensions/__tests__/slickCellRangeSelector.spec.ts @@ -575,7 +575,7 @@ describe('CellRangeSelector Plugin', () => { }); }); - it('should handle drag and expect the decorator to NOT call the "show" method and return (frozen row) with canvas bottom right', () => { + it('should handle drag and expect the decorator to NOT call the "show" method and return (pinned row) with canvas bottom right', () => { const divCanvas = document.createElement('div'); const divViewport = document.createElement('div'); divViewport.className = 'slick-viewport'; @@ -625,7 +625,7 @@ describe('CellRangeSelector Plugin', () => { // }); }); - it('should handle drag and expect the decorator to NOT call the "show" method and return (frozen column) with canvas top right', () => { + it('should handle drag and expect the decorator to NOT call the "show" method and return (pinned column) with canvas top right', () => { const divCanvas = document.createElement('div'); const divViewport = document.createElement('div'); divViewport.className = 'slick-viewport'; diff --git a/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts b/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts index bd4c92bcf7..ce8705c5da 100644 --- a/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts +++ b/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts @@ -1715,7 +1715,7 @@ describe('GridMenuControl', () => { expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([]); }); - it('should expect menu related to "Unfreeze Columns/Rows"', () => { + it('should expect menu related to "Unpin Columns/Rows"', () => { const copyGridOptionsMock = { ...gridOptionsMock, gridMenu: { commandLabels: gridOptionsMock.gridMenu!.commandLabels, hideClearPinningCommand: false, hideToggleDarkModeCommand: true }, @@ -1728,7 +1728,7 @@ describe('GridMenuControl', () => { _orgTitle: '', iconCssClass: 'mdi mdi-pin-off-outline', titleKey: 'CLEAR_PINNING', - title: 'Dégeler les colonnes/rangées', + title: 'Désépingler les colonnes/rangées', disabled: false, command: 'clear-pinning', positionOrder: 52, diff --git a/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts b/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts index 53c759f4bd..0df9feedb7 100644 --- a/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts +++ b/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts @@ -697,7 +697,6 @@ describe('HeaderMenu Plugin', () => { expect(columnsMock[1].hidden).toBeTruthy(); expect(pubSubSpy).toHaveBeenCalledWith('onHideColumns', { columns: columnsMock, hiddenColumn: columnsMock[1] }); }); - }); describe('with sub-menus', () => { @@ -1284,15 +1283,37 @@ describe('HeaderMenu Plugin', () => { const pinCommand = gridContainerDiv.querySelector('[data-command="pin-column"]') as HTMLDivElement; expect(pinCommand).toBeTruthy(); - pinCommand.dispatchEvent(new Event('click')); + expect(gridContainerDiv.querySelector('[data-command="pin-columns"]')).toBeFalsy(); + expect(gridContainerDiv.querySelector('[data-command="unpin-column"]')).toBeFalsy(); + expect(gridContainerDiv.querySelector('[data-command="unpin-columns"]')).toBeFalsy(); + expect(pinCommand.querySelector('.slick-menu-content')?.textContent).toBe('Column Pinning'); + const pinColumnCommandItems = testColumns[0].header?.menu?.commandItems?.find( + (item) => item !== 'divider' && item?.command === 'pin-column' + )?.commandItems; + expect(pinColumnCommandItems?.map((item) => (item === 'divider' ? item : item.command))).toEqual([ + 'pin-left', + 'pin-right', + 'pin-columns', + 'divider-pin-unpin', + 'unpin-column', + 'unpin-columns', + ]); + const pinLeftCommand = pinColumnCommandItems?.find((item) => item !== 'divider' && item?.command === 'pin-left') as MenuCommandItem; + const pinRightCommand = pinColumnCommandItems?.find((item) => item !== 'divider' && item?.command === 'pin-right') as MenuCommandItem; + expect(pinLeftCommand.title).toBe('Pin Left'); + expect(pinRightCommand.title).toBe('Pin Right'); + pinLeftCommand.action?.(new SlickEventData(), { column: testColumns[0] } as any); expect(setColumnPinningSpy).toHaveBeenCalledWith('field2', 'left'); + pinRightCommand.action?.(new SlickEventData(), { column: testColumns[0] } as any); + expect(setColumnPinningSpy).toHaveBeenCalledWith('field2', 'right'); testColumns[0].pinned = 'left'; (plugin as any).recreateHeaderMenu(testColumns); - expect(testColumns[0].header?.menu?.commandItems).toEqual(expect.arrayContaining([expect.objectContaining({ command: 'unpin-column' })])); - const unpinCommand = testColumns[0].header?.menu?.commandItems?.find( - (item) => item !== 'divider' && item?.command === 'unpin-column' + const pinColumnCommand = testColumns[0].header?.menu?.commandItems?.find( + (item) => item !== 'divider' && item?.command === 'pin-column' ) as MenuCommandItem; + expect(pinColumnCommand).toBeTruthy(); + const unpinCommand = pinColumnCommand.commandItems?.find((item) => item !== 'divider' && item?.command === 'unpin-column') as MenuCommandItem; unpinCommand.action?.(new SlickEventData(), { column: testColumns[0] } as any); expect(setColumnPinningSpy).toHaveBeenCalledWith('field2', null); gridOptionsMock.headerMenu = originalHeaderMenuOptions; @@ -1300,7 +1321,30 @@ describe('HeaderMenu Plugin', () => { vi.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); }); - it('should expect menu related to Freeze Columns when "hidePinningColumnsCommand" is disabled and also expect grid "setOptions" method to be called with current column position', async () => { + it('should not offer pinning commands when the column is locked', () => { + const testColumns = [{ ...columnsMock[1], header: undefined, pinned: 'left', lockPinned: true }] as Column[]; + const getColumnsSpy = vi.spyOn(gridStub, 'getColumns').mockReturnValue(testColumns); + + plugin.init(); + plugin.addonOptions = { hidePinColumnCommand: false }; + gridStub.onBeforeSetColumns.notify({ previousColumns: [], newColumns: testColumns, grid: gridStub }, eventData, gridStub); + gridStub.onHeaderCellRendered.notify({ column: testColumns[0], node: headerDiv, grid: gridStub }, eventData, gridStub); + (headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement).dispatchEvent( + new Event('click', { bubbles: true, cancelable: true, composed: false }) + ); + + expect(gridContainerDiv.querySelector('[data-command="pin-column"]')).toBeFalsy(); + expect(gridContainerDiv.querySelector('[data-command="unpin-column"]')).toBeFalsy(); + expect(gridContainerDiv.querySelector('[data-command="pin-columns"]')).toBeFalsy(); + expect(gridContainerDiv.querySelector('[data-command="unpin-columns"]')).toBeFalsy(); + expect(testColumns[0].header?.menu?.commandItems).not.toEqual( + expect.arrayContaining([expect.objectContaining({ command: expect.stringMatching(/pin-columns?/i) })]) + ); + getColumnsSpy.mockRestore(); + vi.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); + }); + + it('should expect menu related to Pin Through Here when "hidePinningColumnsCommand" is disabled and also expect grid "setOptions" method to be called with current column position', async () => { vi.spyOn(gridStub, 'validatePinnedColumnWidth').mockReturnValue(true); const setOptionsSpy = vi.spyOn(gridStub, 'setOptions'); vi.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue({ @@ -1317,23 +1361,23 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="freeze-columns"]') as HTMLDivElement; + const commandDivElm = gridContainerDiv.querySelector('[data-command="pin-columns"]') as HTMLDivElement; const commandIconElm = commandDivElm.querySelector('.slick-menu-icon') as HTMLDivElement; const commandLabelElm = commandDivElm.querySelector('.slick-menu-content') as HTMLDivElement; expect(columnsMock[1].header!.menu!.commandItems!).toEqual([ { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, ]); expect(commandIconElm.classList.contains('mdi-pin-outline')).toBeTruthy(); - expect(commandLabelElm.textContent).toBe('Freeze Columns'); + expect(commandLabelElm.textContent).toBe('Pin Through Here'); await translateService.use('fr'); plugin.translateHeaderMenu(); @@ -1341,10 +1385,10 @@ describe('HeaderMenu Plugin', () => { { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Geler les colonnes', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: "Épingler jusqu'ici", + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, @@ -1355,7 +1399,7 @@ describe('HeaderMenu Plugin', () => { expect(gridStub.setColumns).toHaveBeenCalledWith(columnsMock); }); - it('should expect menu related to Freeze Columns when "hidePinningColumnsCommand" is disabled and also expect grid "setOptions" method to be called with current column position', async () => { + it('should expect menu related to Pin Through Here when "hidePinningColumnsCommand" is disabled and also expect grid "setOptions" method to be called with current column position', async () => { vi.spyOn(gridStub, 'validatePinnedColumnWidth').mockReturnValue(true); const setOptionsSpy = vi.spyOn(gridStub, 'setOptions'); vi.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue({ @@ -1370,23 +1414,23 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="freeze-columns"]') as HTMLDivElement; + const commandDivElm = gridContainerDiv.querySelector('[data-command="pin-columns"]') as HTMLDivElement; const commandIconElm = commandDivElm.querySelector('.slick-menu-icon') as HTMLDivElement; const commandLabelElm = commandDivElm.querySelector('.slick-menu-content') as HTMLDivElement; expect(columnsMock[1].header!.menu!.commandItems!).toEqual([ { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, ]); expect(commandIconElm.classList.contains('mdi-pin-outline')).toBeTruthy(); - expect(commandLabelElm.textContent).toBe('Freeze Columns'); + expect(commandLabelElm.textContent).toBe('Pin Through Here'); await translateService.use('fr'); plugin.translateHeaderMenu(); @@ -1394,10 +1438,10 @@ describe('HeaderMenu Plugin', () => { { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Geler les colonnes', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: "Épingler jusqu'ici", + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, @@ -1408,7 +1452,7 @@ describe('HeaderMenu Plugin', () => { expect(gridStub.setColumns).toHaveBeenCalledWith(columnsMock); }); - it('should expect menu related to Unfreeze Columns when "hidePinningColumnsCommand" is disabled and column is already frozen to that index and then also expect grid "setOptions" method to be called with current column position', async () => { + it('should expect menu related to Unpin All Columns when "hidePinningColumnsCommand" is disabled and column is already pinned to that index and then also expect grid "setOptions" method to be called with current column position', async () => { vi.spyOn(gridStub, 'validatePinnedColumnWidth').mockReturnValue(true); const setOptionsSpy = vi.spyOn(gridStub, 'setOptions'); vi.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue({ @@ -1417,6 +1461,7 @@ describe('HeaderMenu Plugin', () => { headerMenu: { hidePinningColumnsCommand: false, hideColumnHideCommand: true, hideColumnResizeByContentCommand: true }, pinning: { columns: { left: 1 } }, }); + plugin.addonOptions = { hidePinColumnCommand: false }; // calling `onBeforeSetColumns` 2x times shouldn't duplicate clear sort menu gridStub.onBeforeSetColumns.notify({ previousColumns: [], newColumns: columnsMock, grid: gridStub }, eventData as any, gridStub); @@ -1425,45 +1470,32 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="unfreeze-columns"]') as HTMLDivElement; - const commandIconElm = commandDivElm.querySelector('.slick-menu-icon') as HTMLDivElement; - const commandLabelElm = commandDivElm.querySelector('.slick-menu-content') as HTMLDivElement; - expect(columnsMock[1].header!.menu!.commandItems!).toEqual([ - { - _orgTitle: '', - iconCssClass: 'mdi mdi-pin-off-outline', - title: 'Unfreeze Columns', - titleKey: 'UNFREEZE_COLUMNS', - command: 'unfreeze-columns', - positionOrder: 45, - action: expect.any(Function), - }, - { divider: true, command: 'divider-1', positionOrder: 48 }, + const pinCommand = gridContainerDiv.querySelector('[data-command="pin-column"]') as HTMLDivElement; + expect(pinCommand.querySelector('.slick-menu-content')?.textContent).toBe('Column Pinning'); + const pinColumnCommandItems = columnsMock[1].header!.menu!.commandItems!.find( + (item) => item !== 'divider' && item.command === 'pin-column' + )?.commandItems; + expect(pinColumnCommandItems?.map((item) => (item === 'divider' ? item : item.command))).toEqual([ + 'pin-left', + 'pin-right', + 'pin-columns', + 'divider-pin-unpin', + 'unpin-column', + 'unpin-columns', ]); - expect(commandIconElm.classList.contains('mdi-pin-off-outline')).toBeTruthy(); - expect(commandLabelElm.textContent).toBe('Unfreeze Columns'); + const unpinCommand = pinColumnCommandItems?.find((item) => item !== 'divider' && item.command === 'unpin-columns') as MenuCommandItem; + expect(unpinCommand.title).toBe('Unpin All Columns'); await translateService.use('fr'); plugin.translateHeaderMenu(); - expect(columnsMock[1].header!.menu!.commandItems!).toEqual([ - { - _orgTitle: '', - iconCssClass: 'mdi mdi-pin-off-outline', - title: 'Dégeler les colonnes', - titleKey: 'UNFREEZE_COLUMNS', - command: 'unfreeze-columns', - positionOrder: 45, - action: expect.any(Function), - }, - { divider: true, command: 'divider-1', positionOrder: 48 }, - ]); + expect(unpinCommand.title).toBe('Désépingler toutes les colonnes'); - commandDivElm.dispatchEvent(new Event('click')); // execute command - expect(setOptionsSpy).toHaveBeenCalledWith({ pinning: { columns: { left: [] } } }, false, true); + unpinCommand.action?.(new SlickEventData(), { column: columnsMock[1] } as any); + expect(setOptionsSpy).toHaveBeenCalledWith({ pinning: { columns: { left: [], right: [] } } }, false, true); expect(gridStub.setColumns).toHaveBeenCalledWith(columnsMock); }); - it('should expect menu related to Freeze Columns when "hidePinningColumnsCommand" is disabled and also expect grid "setOptions" method to be called with frozen column of -1 because the column found is not visible', () => { + it('should expect menu related to Pin Through Here when "hidePinningColumnsCommand" is disabled and also expect grid "setOptions" method to be called with pinned column of -1 because the column found is not visible', () => { sharedService.hasColumnsReordered = true; const setOptionsSpy = vi.spyOn(gridStub, 'setOptions'); const updateColumnSpy = vi.spyOn(gridStub, 'updateColumns'); @@ -1480,15 +1512,15 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="freeze-columns"]') as HTMLDivElement; + const commandDivElm = gridContainerDiv.querySelector('[data-command="pin-columns"]') as HTMLDivElement; expect(columnsMock[2].header!.menu!.commandItems!).toEqual([ { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, @@ -1523,15 +1555,15 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="freeze-columns"]') as HTMLDivElement; + const commandDivElm = gridContainerDiv.querySelector('[data-command="pin-columns"]') as HTMLDivElement; expect((originalColumnDefinitions[1] as any).header!.menu!.commandItems!).toEqual([ { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, @@ -1659,11 +1691,11 @@ describe('HeaderMenu Plugin', () => { const headerMenuExpected = [ { _orgTitle: '', - command: 'freeze-columns', + command: 'pin-columns', iconCssClass: 'mdi mdi-pin-outline', - positionOrder: 45, - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', + positionOrder: 46, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', action: expect.any(Function), }, { command: 'show-negative-numbers', cssClass: 'mdi mdi-lightbulb-on', tooltip: 'Highlight negative numbers.' }, @@ -1720,7 +1752,7 @@ describe('HeaderMenu Plugin', () => { const blankValueCommandElm = subCommandShortcut1.querySelector('[data-command="blank-values"]') as HTMLDivElement; const commandIconElm = subCommandShortcut1.querySelector('.slick-menu-icon') as HTMLDivElement; const commandLabelElm = subCommandShortcut1.querySelector('.slick-menu-content') as HTMLDivElement; - expect(columnsMock[0].header!.menu!.commandItems!).toEqual(headerMenuExpected); + expect(columnsMock[0].header!.menu!.commandItems!).toEqual(expect.arrayContaining(headerMenuExpected)); expect(commandIconElm.classList.contains('mdi-filter-minus-outline')).toBeTruthy(); expect(commandLabelElm.textContent).toBe('Blank Values'); @@ -1752,10 +1784,10 @@ describe('HeaderMenu Plugin', () => { { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, @@ -1907,7 +1939,7 @@ describe('HeaderMenu Plugin', () => { expect(clearSortSpy).toHaveBeenCalledWith(clickEvent, 'field2'); }); - it('should expect menu related to Freeze Columns when "hidePinningColumnsCommand" is disabled and also expect "updateColumns" to be called', () => { + it('should expect menu related to Pin Through Here when "hidePinningColumnsCommand" is disabled and also expect "updateColumns" to be called', () => { const originalColumnDefinitions = [ { id: 'field1', field: 'field1', width: 100, nameKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }, @@ -1929,15 +1961,15 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="freeze-columns"]') as HTMLDivElement; + const commandDivElm = gridContainerDiv.querySelector('[data-command="pin-columns"]') as HTMLDivElement; expect((originalColumnDefinitions[1] as any).header!.menu!.commandItems!).toEqual([ { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, @@ -1948,7 +1980,7 @@ describe('HeaderMenu Plugin', () => { expect(updateColumnSpy).toHaveBeenCalled(); }); - it('should expect menu related to Freeze Columns when "hidePinningColumnsCommand" is disabled and also expect "updateColumns" to be called when hasColumnsReordered returns true', () => { + it('should expect menu related to Pin Through Here when "hidePinningColumnsCommand" is disabled and also expect "updateColumns" to be called when hasColumnsReordered returns true', () => { const originalColumnDefinitions = [ { id: 'field1', field: 'field1', width: 100, nameKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }, @@ -1974,15 +2006,15 @@ describe('HeaderMenu Plugin', () => { const headerButtonElm = headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement; headerButtonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - const commandDivElm = gridContainerDiv.querySelector('[data-command="freeze-columns"]') as HTMLDivElement; + const commandDivElm = gridContainerDiv.querySelector('[data-command="pin-columns"]') as HTMLDivElement; expect((visibleColumnDefinitions[1] as any).header!.menu!.commandItems!).toEqual([ { _orgTitle: '', iconCssClass: 'mdi mdi-pin-outline', - title: 'Freeze Columns', - titleKey: 'FREEZE_COLUMNS', - command: 'freeze-columns', - positionOrder: 45, + title: 'Pin Through Here', + titleKey: 'PIN_COLUMNS', + command: 'pin-columns', + positionOrder: 46, action: expect.any(Function), }, { divider: true, command: 'divider-1', positionOrder: 48 }, diff --git a/packages/common/src/extensions/slickHeaderMenu.ts b/packages/common/src/extensions/slickHeaderMenu.ts index 86ddee81a9..a4588accc9 100644 --- a/packages/common/src/extensions/slickHeaderMenu.ts +++ b/packages/common/src/extensions/slickHeaderMenu.ts @@ -47,7 +47,7 @@ export class SlickHeaderMenu extends MenuBaseClass { buttonImage: null, minWidth: 100, hideColumnHideCommand: false, - // Single-column pinning is opt-in; preserve the legacy menu by default. + // Single-column pinning is opt-in, independently of bulk pinning. hidePinColumnCommand: true, hideSortCommands: false, title: '', @@ -312,93 +312,126 @@ export class SlickHeaderMenu extends MenuBaseClass { let columnHeaderMenuItems: Array = columnDef?.header?.menu?.commandItems ?? []; - // Bulk index-based pinning (kept under the existing Freeze Columns - // label for users migrating from the old command). + // These commands belong to the Column Pinning submenu and should + // never remain as standalone root commands after menu recreation. + for (const command of ['pin-left', 'pin-right', 'unpin-column', 'unpin-columns']) { + this.removeCommandWhenFound(columnHeaderMenuItems, command); + } + + // Keep all pinning actions under one submenu so the menu shape stays + // consistent regardless of the selected column's current state. let hasPinningOrResizeCommand = false; - if (headerMenuOptions && !headerMenuOptions.hidePinningColumnsCommand) { + let bulkPinningCommandItem: MenuCommandItem | undefined; + const columnPosition = columns.findIndex((col) => col.id === columnDef.id); + const isBulkPinningLocked = + columnPosition >= 0 && columns.slice(0, columnPosition + 1).some((column) => column?.lockPinned === true); + if (headerMenuOptions && !headerMenuOptions.hidePinningColumnsCommand && !isBulkPinningLocked) { hasPinningOrResizeCommand = true; - const columnPosition = columns.findIndex((col) => col.id === columnDef.id); - const leftPinning = gridOptions.pinning?.columns?.left; - const isBulkPinned = - typeof leftPinning === 'number' - ? leftPinning === columnPosition - : Array.isArray(leftPinning) && (leftPinning.includes(columnPosition) || leftPinning.includes(columnDef.id)); - if (isBulkPinned) { - // make sure the "freeze-columns" command doesn't exist before adding the "unfreeze-columns" - this.removeCommandWhenFound(columnHeaderMenuItems, 'freeze-columns'); - - // add unfreeze command (the implementation clears generated - // `Column.pinned` values through the new index option) - const cmdUnfreeze = 'unfreeze-columns'; - this.addMissingCommandOrAction( - { - _orgTitle: commandLabels?.unpinningColumnsCommand || '', - iconCssClass: headerMenuOptions.iconUnpinningColumns || 'mdi mdi-pin-off-outline', - titleKey: `${translationPrefix}UNFREEZE_COLUMNS`, - command: cmdUnfreeze, - positionOrder: 45, - action: (_e, args) => this.freezeOrUnfreezeColumns(args.column, cmdUnfreeze), - }, - headerMenuOptions.hideCommands, - columnHeaderMenuItems - ); - } else { - // make sure the "unfreeze-columns" command doesn't exist before adding the "freeze-columns" - this.removeCommandWhenFound(columnHeaderMenuItems, 'unfreeze-columns'); - - // add bulk freeze/pinning command - const cmdFreeze = 'freeze-columns'; - this.addMissingCommandOrAction( - { - _orgTitle: commandLabels?.pinningColumnsCommand || '', - iconCssClass: headerMenuOptions.iconPinningColumns || 'mdi mdi-pin-outline', - titleKey: `${translationPrefix}FREEZE_COLUMNS`, - command: cmdFreeze, - positionOrder: 45, - action: (_e, args) => this.freezeOrUnfreezeColumns(args.column, cmdFreeze), - }, - headerMenuOptions.hideCommands, - columnHeaderMenuItems - ); - } + const cmdPin = 'pin-columns'; + bulkPinningCommandItem = { + _orgTitle: commandLabels?.pinningColumnsCommand || '', + iconCssClass: headerMenuOptions.iconPinningColumns || 'mdi mdi-pin-outline', + titleKey: `${translationPrefix}PIN_COLUMNS`, + command: cmdPin, + // Keep the standalone fallback in its existing root-menu slot; + // the submenu preserves the explicit command array order. + positionOrder: 46, + action: (_e, args) => this.pinOrUnpinColumns(args.column, cmdPin), + }; } - // Single-column pinning. This is independent of the bulk index - // command above and writes only the selected column definition. - if (headerMenuOptions && !this._addonOptions?.hidePinColumnCommand) { + // Single-column pinning writes only the selected column definition. + if (headerMenuOptions && !this._addonOptions?.hidePinColumnCommand && !columnDef.lockPinned) { hasPinningOrResizeCommand = true; - const isPinned = columnDef.pinned === 'left' || columnDef.pinned === 'right'; - if (isPinned) { - this.removeCommandWhenFound(columnHeaderMenuItems, 'pin-column'); - const cmdUnpin = 'unpin-column'; - this.addMissingCommandOrAction( - { - _orgTitle: commandLabels?.unpinColumnCommand || '', - iconCssClass: headerMenuOptions.iconUnpinColumn || 'mdi mdi-pin-off-outline', - titleKey: `${translationPrefix}UNPIN_COLUMN`, - command: cmdUnpin, - positionOrder: 46, - action: (_e, args) => this.pinOrUnpinColumn(args.column, cmdUnpin), - }, - headerMenuOptions.hideCommands, - columnHeaderMenuItems - ); - } else { - this.removeCommandWhenFound(columnHeaderMenuItems, 'unpin-column'); - const cmdPin = 'pin-column'; - this.addMissingCommandOrAction( - { - _orgTitle: commandLabels?.pinColumnCommand || '', - iconCssClass: headerMenuOptions.iconPinColumn || 'mdi mdi-pin-outline', - titleKey: `${translationPrefix}PIN_COLUMN`, - command: cmdPin, - positionOrder: 46, - action: (_e, args) => this.pinOrUnpinColumn(args.column, cmdPin), - }, - headerMenuOptions.hideCommands, - columnHeaderMenuItems - ); + const cmdPin = 'pin-column'; + const existingPinColumnCommand = columnHeaderMenuItems.find((item) => item !== 'divider' && item?.command === cmdPin) as + MenuCommandItem | undefined; + const pinColumnCommandItems: Array = existingPinColumnCommand?.commandItems ?? []; + const cmdPinLeft = 'pin-left'; + const cmdPinRight = 'pin-right'; + const cmdUnpinColumn = 'unpin-column'; + const cmdUnpinColumns = 'unpin-columns'; + + // Remove older versions that placed these commands at the root. + for (const command of [cmdPinLeft, cmdPinRight, 'pin-columns', cmdUnpinColumn, cmdUnpinColumns]) { + this.removeCommandWhenFound(columnHeaderMenuItems, command); + } + + this.addMissingCommandOrAction( + { + _orgTitle: commandLabels?.pinLeftCommand || '', + iconCssClass: headerMenuOptions.iconPinLeft || 'mdi mdi-pin-outline', + titleKey: `${translationPrefix}PIN_LEFT`, + command: cmdPinLeft, + positionOrder: 1, + action: (_e, args) => this.pinOrUnpinColumn(args.column, cmdPinLeft), + }, + headerMenuOptions.hideCommands, + pinColumnCommandItems + ); + this.addMissingCommandOrAction( + { + _orgTitle: commandLabels?.pinRightCommand || '', + iconCssClass: headerMenuOptions.iconPinRight || 'mdi mdi-pin-outline', + titleKey: `${translationPrefix}PIN_RIGHT`, + command: cmdPinRight, + positionOrder: 2, + action: (_e, args) => this.pinOrUnpinColumn(args.column, cmdPinRight), + }, + headerMenuOptions.hideCommands, + pinColumnCommandItems + ); + if (bulkPinningCommandItem) { + this.addMissingCommandOrAction(bulkPinningCommandItem, headerMenuOptions.hideCommands, pinColumnCommandItems); + } + + if (!pinColumnCommandItems.some((item) => item !== 'divider' && item.command === 'divider-pin-unpin')) { + pinColumnCommandItems.push({ divider: true, command: 'divider-pin-unpin', positionOrder: 4 }); } + + this.addMissingCommandOrAction( + { + _orgTitle: commandLabels?.unpinColumnCommand || '', + iconCssClass: headerMenuOptions.iconUnpinColumn || 'mdi mdi-pin-off-outline', + titleKey: `${translationPrefix}UNPIN_COLUMN`, + command: cmdUnpinColumn, + positionOrder: 5, + action: (_e, args) => this.pinOrUnpinColumn(args.column, cmdUnpinColumn), + }, + headerMenuOptions.hideCommands, + pinColumnCommandItems + ); + this.addMissingCommandOrAction( + { + _orgTitle: commandLabels?.unpinningColumnsCommand || '', + iconCssClass: headerMenuOptions.iconUnpinningColumns || 'mdi mdi-pin-off-outline', + titleKey: `${translationPrefix}UNPIN_COLUMNS`, + command: cmdUnpinColumns, + positionOrder: 6, + action: (_e, args) => this.pinOrUnpinColumns(args.column, cmdUnpinColumns), + }, + headerMenuOptions.hideCommands, + pinColumnCommandItems + ); + + this.addMissingCommandOrAction( + { + _orgTitle: commandLabels?.pinColumnCommand || '', + iconCssClass: headerMenuOptions.iconPinColumn || 'mdi mdi-pin-outline', + titleKey: `${translationPrefix}PIN_COLUMN`, + command: cmdPin, + positionOrder: 45, + commandItems: pinColumnCommandItems, + }, + headerMenuOptions.hideCommands, + columnHeaderMenuItems + ); + } + + // Preserve the standalone bulk command when the submenu is hidden, + // but never expose standalone unpin commands at the root. + if (bulkPinningCommandItem && this._addonOptions?.hidePinColumnCommand) { + this.addMissingCommandOrAction(bulkPinningCommandItem, headerMenuOptions?.hideCommands, columnHeaderMenuItems); } // Column Resize by Content (column autofit) @@ -423,7 +456,7 @@ export class SlickHeaderMenu extends MenuBaseClass { ); } - // add a divider (separator) between the top freeze columns commands and the rest of the commands + // add a divider (separator) between the top pin columns commands and the rest of the commands if (hasPinningOrResizeCommand && !columnHeaderMenuItems.some((item) => item !== 'divider' && item.positionOrder === 48)) { columnHeaderMenuItems.push({ divider: true, command: 'divider-1', positionOrder: 48 }); } @@ -518,7 +551,7 @@ export class SlickHeaderMenu extends MenuBaseClass { commandItems: shortcutSubItems, }); - // add a divider (separator) between the top freeze columns commands and the rest of the commands + // add a divider (separator) between the top pin columns commands and the rest of the commands if ( hasPinningOrResizeCommand && !columnHeaderMenuItems.some((item) => item !== 'divider' && item.positionOrder === filterShortcutsPositionOrder + 1) @@ -604,9 +637,9 @@ export class SlickHeaderMenu extends MenuBaseClass { } } - /** Pin or unpin a column at the left edge through the column definition. */ - protected pinOrUnpinColumn(column: Column, command: 'pin-column' | 'unpin-column'): void { - this.grid.setColumnPinning(column.id, command === 'unpin-column' ? null : 'left'); + /** Pin a column to the selected edge or remove its permanent pin. */ + protected pinOrUnpinColumn(column: Column, command: 'pin-left' | 'pin-right' | 'unpin-column'): void { + this.grid.setColumnPinning(column.id, command === 'unpin-column' ? null : command === 'pin-right' ? 'right' : 'left'); // remove the last pin/unpin command called from Header Menu since it will be replaced by the other one when reopening the menu const columnHeaderMenuItems: Array = column?.header?.menu?.commandItems ?? []; @@ -615,9 +648,17 @@ export class SlickHeaderMenu extends MenuBaseClass { } /** Apply or clear the bulk index-based pinning option. */ - protected freezeOrUnfreezeColumns(column: Column, command: 'freeze-columns' | 'unfreeze-columns'): void { + protected pinOrUnpinColumns(column: Column, command: 'pin-columns' | 'unpin-columns'): void { const columnPosition = this.grid.getColumns().findIndex((col) => col.id === column.id); - this.grid.setOptions({ pinning: { columns: { left: command === 'unfreeze-columns' ? [] : columnPosition } } }, false, true); + this.grid.setOptions( + { + pinning: { + columns: command === 'unpin-columns' ? { left: [], right: [] } : { left: columnPosition }, + }, + }, + false, + true + ); const columnHeaderMenuItems: Array = column?.header?.menu?.commandItems ?? []; this.removeCommandWhenFound(columnHeaderMenuItems, command); diff --git a/packages/common/src/interfaces/column.interface.ts b/packages/common/src/interfaces/column.interface.ts index f35cc27fbd..b3c0367739 100644 --- a/packages/common/src/interfaces/column.interface.ts +++ b/packages/common/src/interfaces/column.interface.ts @@ -227,6 +227,9 @@ export interface Column { /** Permanently dock this column at the left or right edge of the single grid viewport. */ pinned?: DockingSide | null; + /** Set to true to prevent the user from pinning or unpinning this column through the UI. */ + lockPinned?: boolean; + /** Maximum Width of the column in pixels (number only). */ maxWidth?: number; diff --git a/packages/common/src/interfaces/headerMenu.interface.ts b/packages/common/src/interfaces/headerMenu.interface.ts index 888fb0fd6b..f5634a2b65 100644 --- a/packages/common/src/interfaces/headerMenu.interface.ts +++ b/packages/common/src/interfaces/headerMenu.interface.ts @@ -9,11 +9,15 @@ export type BuiltInHeaderMenuCommand = | 'divider-1' | 'divider-2' | 'divider-3' - | 'freeze-columns' + | 'pin-column' + | 'pin-left' + | 'pin-columns' + | 'pin-right' | 'hide-column' | 'sort-asc' | 'sort-desc' - | 'unfreeze-columns'; + | 'unpin-column' + | 'unpin-columns'; export interface HeaderMenuCommandItemCallbackArgs { /** Column definition */ diff --git a/packages/common/src/interfaces/headerMenuLabel.interface.ts b/packages/common/src/interfaces/headerMenuLabel.interface.ts index 706bb907f0..31cd9c8b96 100644 --- a/packages/common/src/interfaces/headerMenuLabel.interface.ts +++ b/packages/common/src/interfaces/headerMenuLabel.interface.ts @@ -23,13 +23,19 @@ export interface HeaderMenuLabel { /** Defaults to "FILTER_SHORTCUTS" translation key */ filterShortcutsCommandKey?: string; - /** Defaults to "Pin Columns" */ + /** Defaults to "Pin Through Here" */ pinningColumnsCommand?: string; - /** Label for the pin-column command. */ + /** Defaults to "Column Pinning" for the pinning submenu. */ pinColumnCommand?: string; - /** Defaults to "FREEZE_COLUMNS" translation key */ + /** Defaults to "Pin Left" */ + pinLeftCommand?: string; + + /** Defaults to "Pin Right" */ + pinRightCommand?: string; + + /** Defaults to "PIN_COLUMNS" translation key */ pinningColumnsCommandKey?: string; /** Defaults to "Hide Column" */ @@ -50,12 +56,12 @@ export interface HeaderMenuLabel { /** Defaults to "SORT_DESCENDING" translation key */ sortDescCommandKey?: string; - /** Defaults to "Unpin Columns" */ + /** Defaults to "Unpin All Columns" */ unpinningColumnsCommand?: string; /** Label for the unpin-column command. */ unpinColumnCommand?: string; - /** Defaults to "UNFREEZE_COLUMNS" translation key */ + /** Defaults to "UNPIN_COLUMNS" translation key */ unpinningColumnsCommandKey?: string; } diff --git a/packages/common/src/interfaces/headerMenuOption.interface.ts b/packages/common/src/interfaces/headerMenuOption.interface.ts index 323c48330b..aed270d348 100644 --- a/packages/common/src/interfaces/headerMenuOption.interface.ts +++ b/packages/common/src/interfaces/headerMenuOption.interface.ts @@ -34,7 +34,7 @@ export interface HeaderMenuOption extends MenuOption .slick-pinned-left-cells.slick-pinned-left-cells-active { - border-inline-end: var(--slick-frozen-border-right, v.$slick-frozen-border-right); + border-inline-end: var(--slick-pinned-border-right, v.$slick-pinned-border-right); } > .slick-scrolling-cells { @@ -296,7 +296,7 @@ } > .slick-pinned-right-cells.slick-pinned-right-cells-active { - border-inline-start: var(--slick-frozen-border-right, v.$slick-frozen-border-right); + border-inline-start: var(--slick-pinned-border-right, v.$slick-pinned-border-right); } // A RowSpan cell extends beyond its host row. The normal region clip is @@ -339,11 +339,11 @@ } &.slick-row-pinned-top-edge { - border-bottom: var(--slick-frozen-border-bottom, v.$slick-frozen-border-bottom); + border-bottom: var(--slick-pinned-border-bottom, v.$slick-pinned-border-bottom); } &.slick-row-pinned-bottom-edge { - border-top: var(--slick-frozen-border-bottom, v.$slick-frozen-border-bottom); + border-top: var(--slick-pinned-border-bottom, v.$slick-pinned-border-bottom); } &:hover { @@ -1129,14 +1129,14 @@ } } - /** Frozen/Pinned styling */ + /** Pinned styling */ .slick-column-pinned-left-edge { - border-inline-end: var(--slick-frozen-border-right, v.$slick-frozen-border-right) !important; + border-inline-end: var(--slick-pinned-border-right, v.$slick-pinned-border-right) !important; box-sizing: border-box; } .slick-column-pinned-right-edge { - border-inline-start: var(--slick-frozen-border-right, v.$slick-frozen-border-right) !important; + border-inline-start: var(--slick-pinned-border-right, v.$slick-pinned-border-right) !important; box-sizing: border-box; } @@ -1160,7 +1160,7 @@ .slick-header-column.slick-column-pinned-right-edge, .slick-headerrow-column.slick-column-pinned-right-edge, .slick-footerrow-column.slick-column-pinned-right-edge { - border-inline-start: var(--slick-frozen-border-right, v.$slick-frozen-border-right) !important; + border-inline-start: var(--slick-pinned-border-right, v.$slick-pinned-border-right) !important; } .slick-footerrow-column.slick-column-pinned-left, @@ -1169,16 +1169,6 @@ background-color: var(--slick-grid-footer-bg-color, v.$slick-grid-footer-bg-color); } - .slick-pane-left.frozen { - border-right: var(--slick-frozen-border-right, v.$slick-frozen-border-right); - } - - .slick-pane-bottom { - border-top: var(--slick-frozen-border-bottom, v.$slick-frozen-border-bottom); - } - .slick-viewport-bottom.slick-viewport-right { - overflow-y: var(--slick-frozen-overflow-right, v.$slick-frozen-overflow-right) !important; - } .input-group { display: flex; diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts index 76c634f156..ecd0390e04 100644 --- a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts +++ b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts @@ -1199,7 +1199,7 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () expect(backendSpy).toHaveBeenCalledWith(mockColumnFilter as unknown as CurrentFilter[], false); }); - it('should override frozen grid options when "pinning" is defined in the "presets" property', () => { + it('should override pinned grid options when "pinning" is defined in the "presets" property', () => { const pinningMock = { columns: { left: [], right: [] }, rows: { top: [], bottom: [] } } as CurrentPinning; const gridOptionSetterSpy = vi.spyOn(component, 'gridOptions', 'set'); diff --git a/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts b/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts index bbb019eb89..76e3421c9f 100644 --- a/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts +++ b/packages/vanilla-force-bundle/src/salesforce-global-grid-options.ts @@ -70,7 +70,7 @@ export const SalesforceGlobalGridOptions = { }, gridMenu: { commandLabels: { - clearPinningCommandKey: 'UNFREEZE_COLUMNS', + clearPinningCommandKey: 'UNPIN_COLUMNS', }, hideToggleDarkModeCommand: true, hideTogglePreHeaderCommand: true, @@ -79,6 +79,7 @@ export const SalesforceGlobalGridOptions = { hideForceFitButton: true, }, headerMenu: { + hidePinColumnCommand: false, hidePinningColumnsCommand: false, iconSortAscCommand: 'mdi mdi-arrow-up', iconSortDescCommand: 'mdi mdi-arrow-down', diff --git a/test/cypress/e2e/example03.cy.ts b/test/cypress/e2e/example03.cy.ts index 7ac6cfd272..cca54bf732 100644 --- a/test/cypress/e2e/example03.cy.ts +++ b/test/cypress/e2e/example03.cy.ts @@ -542,7 +542,7 @@ describe('Example 03 - Draggable Grouping & Aggregators', () => { .each(($child, index) => expect($child.text()).to.eq(titlesWithTitleDuration[index])); }); - it('should open Header Menu on Duration column and freeze column', () => { + it('should open Header Menu on Duration column and pin columns through it', () => { cy.get('.slick-header:not(.slick-preheader-panel) .slick-header-columns') .find('.slick-header-column:nth(2)') .trigger('mouseover') @@ -552,10 +552,11 @@ describe('Example 03 - Draggable Grouping & Aggregators', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .contains('Freeze Columns') + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') .click(); + + cy.get('.slick-submenu [data-command="pin-columns"]').should('be.visible').and('contain', 'Pin Through Here').click(); }); it('should open Cost column Header Menu then click on "Hide Column" and still expect all headers shown', () => { diff --git a/test/cypress/e2e/example04.cy.ts b/test/cypress/e2e/example04.cy.ts index 0e68a664ae..3aa535429a 100644 --- a/test/cypress/e2e/example04.cy.ts +++ b/test/cypress/e2e/example04.cy.ts @@ -1,4 +1,4 @@ -describe('Example 04 - Frozen Grid', () => { +describe('Example 04 - Pinned Grid', () => { const withTitleRowTitles = ['Sel', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; const withoutTitleRowTitles = ['', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; const getCell = (rowIndex: number, columnIndex: number) => @@ -29,7 +29,7 @@ describe('Example 04 - Frozen Grid', () => { const row0 = '.grid4 .slick-row[data-row="0"]'; // Pinning uses one row node split into regions and a single docking overlay; - // it no longer duplicates rows into legacy left/right frozen canvases. + // it no longer duplicates rows into legacy left/right pinned canvases. cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); cy.get(`${row0} .slick-pinned-left-cells > .slick-cell`).should('have.length', 3); cy.get(`${row0} .slick-scrolling-cells > .slick-cell`).should('have.length', 5); @@ -64,7 +64,7 @@ describe('Example 04 - Frozen Grid', () => { setRightPinning(2); // The default fixture fits in the viewport, so use the demo's wide layout // to exercise an actual horizontal scroll rather than a no-op scroll. - cy.get('[data-test="set-large-freezed-columns"]').click(); + cy.get('[data-test="set-large-pinned-columns"]').click(); const actionCell = '.grid4 .slick-row[data-row="10"] .slick-pinned-right-cells .slick-cell.l8'; cy.get(actionCell).then(($cell) => { @@ -81,6 +81,39 @@ describe('Example 04 - Frozen Grid', () => { cy.visit(`${Cypress.config('baseUrl')}/example04`); }); + it('should resize columns while left and right pinning are active', () => { + cy.visit(`${Cypress.config('baseUrl')}/example04`); + + const resizeColumn = (columnSelector: string, pinClass: string) => { + cy.get(columnSelector).should('have.class', pinClass); + cy.get(`${columnSelector} .slick-resizable-handle`) + .should('exist') + .then(($handle) => { + const header = $handle.closest('.slick-header-column')[0] as HTMLElement; + const initialWidth = header.getBoundingClientRect().width; + + cy.wrap($handle).trigger('mousedown', { which: 1, pageX: 100, clientX: 100, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 125, clientX: 125, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 150, clientX: 150, force: true }); + cy.get('body').trigger('mouseup', { which: 1, pageX: 150, clientX: 150, force: true }); + + cy.get(columnSelector).should(($updatedHeader) => { + expect($updatedHeader[0].getBoundingClientRect().width).to.be.greaterThan(initialWidth); + }); + }); + }; + + resizeColumn('.grid4 .slick-header-columns-left [data-id="title"]', 'slick-column-pinned-left'); + + // Use City of Origin here because Action has a maxWidth of 100px and + // would correctly refuse a wider resize. + setRightPinning(2); + resizeColumn('.grid4 .slick-header-columns-right [data-id="cityOfOrigin"]', 'slick-column-pinned-right'); + + // Keep the following serial tests on the demo's default configuration. + cy.visit(`${Cypress.config('baseUrl')}/example04`); + }); + it('should disable and re-enable right pinning through the numeric grid control', () => { setRightPinning(0); cy.get('.grid4 .slick-row[data-row="0"] .slick-pinned-right-cells').should('exist'); @@ -112,7 +145,7 @@ describe('Example 04 - Frozen Grid', () => { setRightPinning(1); }); - it('should hide "Title" column from Grid Menu and expect last frozen column to be "% Complete"', () => { + it('should hide "Title" column from Grid Menu and expect last pinned column to be "% Complete"', () => { const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; const row0 = '.grid4 .slick-row[data-row="0"]'; @@ -140,7 +173,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(1)`).should('contain', '2009-05-05'); }); - it('should show again "Title" column from Grid Menu and expect last frozen column to still be "% Complete"', () => { + it('should show again "Title" column from Grid Menu and expect last pinned column to still be "% Complete"', () => { cy.get('.grid4') .get('.slick-grid-menu:visible') .find('.slick-column-picker-list') @@ -166,7 +199,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.slick-scrolling-cells .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should hide "Title" column from Header Menu and expect last frozen column to be "% Complete"', () => { + it('should hide "Title" column from Header Menu and expect last pinned column to be "% Complete"', () => { const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; cy.get('.grid4').find('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); @@ -200,7 +233,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell').should('not.exist'); }); - it('should show again "Title" column from Column Picker and expect last frozen column to still be "% Complete"', () => { + it('should show again "Title" column from Column Picker and expect last pinned column to still be "% Complete"', () => { cy.get('.grid4').find('.slick-header-column:nth(4)').trigger('mouseover').trigger('contextmenu').invoke('show'); cy.get('.slick-column-picker') @@ -226,8 +259,8 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should click on the "Remove Frozen Columns" button to switch to a regular grid view without frozen columns and expect 7 columns on the left container', () => { - cy.get('[data-test=remove-frozen-column-button]').click({ force: true }); + it('should click on the "Remove Pinned Columns" button to switch to a regular grid view without pinned columns and expect 7 columns on the left container', () => { + cy.get('[data-test=remove-pinned-column-button]').click({ force: true }); cy.get('.grid4 .slick-row[data-row="0"]').should('have.length.at.least', 1); cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').should('exist').and('not.have.class', 'slick-pinned-left-cells-active'); @@ -245,8 +278,8 @@ describe('Example 04 - Frozen Grid', () => { .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { - cy.get('[data-test=set-3frozen-columns]').click({ force: true }); + it('should click on the "Set 3 Pinned Columns" button to switch pinned columns grid and expect 3 pinned columns on the left and 4 columns on the right', () => { + cy.get('[data-test=set-3pinned-columns]').click({ force: true }); cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); @@ -264,10 +297,10 @@ describe('Example 04 - Frozen Grid', () => { .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns/rows', () => { + it('should click on the Grid Menu command "Unpin Columns/Rows" to switch to a regular grid without pinned columns/rows', () => { cy.get('.grid4').find('button.slick-grid-menu-button').click({ force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + cy.contains('Unpin Columns/Rows').click({ force: true }); cy.get('.grid4 .slick-row[data-row="0"]').should('have.length.at.least', 1); cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').should('exist').and('not.have.class', 'slick-pinned-left-cells-active'); @@ -580,7 +613,7 @@ describe('Example 04 - Frozen Grid', () => { cy.window().then((win) => { cy.stub(win, 'alert').as('alertStub'); }); - cy.get('[data-test=set-3frozen-columns]').click({ force: true }); + cy.get('[data-test=set-3pinned-columns]').click({ force: true }); const leftColumns = ['', 'Title', '% Complete']; const rightColumns = ['Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; @@ -602,8 +635,8 @@ describe('Example 04 - Frozen Grid', () => { .then(() => { cy.get('@alertStub').should( 'have.been.calledWith', - '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column on the right section of the column freeze/pining. ' + - 'You could alternatively "Unfreeze all the columns" before trying again.' + '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + + 'You could alternatively unpin columns before trying again.' ); }); } @@ -630,8 +663,8 @@ describe('Example 04 - Frozen Grid', () => { .then(() => { cy.get('@alertStub').should( 'have.been.calledWith', - '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column on the right section of the column freeze/pining. ' + - 'You could alternatively "Unfreeze all the columns" before trying again.' + '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + + 'You could alternatively unpin columns before trying again.' ); }); @@ -683,10 +716,10 @@ describe('Example 04 - Frozen Grid', () => { }); describe('Test UI rendering after Scrolling with large columns', () => { - it('should unfreeze all columns/rows', () => { + it('should unpin all columns/rows', () => { cy.get('.grid4').find('button.slick-grid-menu-button').click({ force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + cy.contains('Unpin Columns/Rows').click({ force: true }); }); it('should resize all columns and make them wider', () => { @@ -878,8 +911,8 @@ describe('Example 04 - Frozen Grid', () => { cy.focused().type('{enter}'); }); - it('should reapply 3 Frozen Columns and expect to be able to focus on first filter and go left/right between both viewports without problems', () => { - cy.get('[data-test="set-3frozen-columns"]').click(); + it('should reapply 3 Pinned Columns and expect to be able to focus on first filter and go left/right between both viewports without problems', () => { + cy.get('[data-test="set-3pinned-columns"]').click(); cy.get('.slick-headerrow-column.l1 input').focus(); cy.press(Cypress.Keyboard.Keys.TAB); cy.get('.slick-headerrow-column.l2 input').should('have.focus'); @@ -910,7 +943,7 @@ describe('Example 04 - Frozen Grid', () => { $viewport[0].dispatchEvent(new Event('scroll', { bubbles: true })); }); cy.get('.slick-viewport-top .slick-scrolling-cells').its('0.scrollLeft').should('equal', 0); - cy.get('[data-test="set-large-freezed-columns"]').click(); + cy.get('[data-test="set-large-pinned-columns"]').click(); // Step 1: call SortableJS onStart for the "Start" column (1st center-section column). // This binds the document 'drag' auto-scroll listener for the shared proxy scrollbar. diff --git a/test/cypress/e2e/example08.cy.ts b/test/cypress/e2e/example08.cy.ts index 8eff4d2e4a..713162bc49 100644 --- a/test/cypress/e2e/example08.cy.ts +++ b/test/cypress/e2e/example08.cy.ts @@ -48,8 +48,8 @@ describe('Example 08 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the "Remove Frozen Columns" button to switch to a regular grid without pinned columns', () => { - cy.get('[data-test="remove-frozen-column-button"]').click(); + it('should click on the "Remove Pinned Columns" button to switch to a regular grid without pinned columns', () => { + cy.get('[data-test="remove-pinned-column-button"]').click(); const firstRow = '.grid2 .slick-row[data-row="0"]'; cy.get(firstRow).should('have.length', 1); @@ -74,8 +74,8 @@ describe('Example 08 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the "Set 3 Frozen Columns" button to pin 3 columns and leave 4 scrolling columns', () => { - cy.contains('Set 3 Frozen Columns').click({ force: true }); + it('should click on the "Set 3 Pinned Columns" button to pin 3 columns and leave 4 scrolling columns', () => { + cy.contains('Set 3 Pinned Columns').click({ force: true }); const firstRow = '.grid2 .slick-row[data-row="0"]'; cy.get(firstRow).should('have.length', 1); @@ -102,10 +102,10 @@ describe('Example 08 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without pinned columns', () => { + it('should click on the Grid Menu command "Unpin Columns/Rows" to switch to a regular grid without pinned columns', () => { cy.get('.grid2').find('button.slick-grid-menu-button').click({ force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + cy.contains('Unpin Columns/Rows').click({ force: true }); const firstRow = '.grid2 .slick-row[data-row="0"]'; cy.get(firstRow).should('have.length', 1); @@ -185,8 +185,8 @@ describe('Example 08 - Column Span & Header Grouping', () => { }); }); - it('should reapply 3 frozen columns on 2nd grid', () => { - cy.contains('Set 3 Frozen Columns').click({ force: true }); + it('should reapply 3 pinned columns on 2nd grid', () => { + cy.contains('Set 3 Pinned Columns').click({ force: true }); cy.get('.grid2 .slick-header.slick-header-left .slick-header-columns .slick-column-pinned-left').should('have.length', 3); cy.get('.grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column:not(.slick-column-pinned-left)').should( @@ -195,7 +195,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { ); }); - it('should be able to "Unfreeze Columns" from header menu', () => { + it('should be able to "Unpin All Columns" from header menu', () => { cy.get('.grid2') .find('.slick-header.slick-header-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') .trigger('mouseover') @@ -203,17 +203,13 @@ describe('Example 08 - Column Span & Header Grouping', () => { .invoke('show') .click(); - cy.get('.slick-header-menu .slick-menu-command-list') - .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Unfreeze Columns') - .click(); + cy.get('.slick-header-menu .slick-menu-command-list').should('be.visible').find('[data-command="pin-column"]').click(); + cy.get('.slick-submenu [data-command="unpin-columns"]').should('contain', 'Unpin All Columns').click(); cy.get('.grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should be able to "Freeze Columns" back from header menu', () => { + it('should be able to "Pin Through Here" back from header menu', () => { cy.get('.grid2') .find('.slick-header.slick-header-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') .trigger('mouseover') @@ -223,11 +219,12 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') .click(); + cy.get('.slick-submenu [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); + cy.get('.grid2 .slick-header.slick-header-left .slick-header-columns .slick-column-pinned-left').should('have.length', 3); cy.get('.grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column:not(.slick-column-pinned-left)').should( 'have.length', @@ -237,7 +234,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { describe('Basic Key Navigations', () => { it('should remove any freezing', () => { - cy.get('[data-test="remove-frozen-column-button"]').click(); + cy.get('[data-test="remove-pinned-column-button"]').click(); cy.get('.grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); diff --git a/test/cypress/e2e/example11.cy.ts b/test/cypress/e2e/example11.cy.ts index c646281aa9..635118924f 100644 --- a/test/cypress/e2e/example11.cy.ts +++ b/test/cypress/e2e/example11.cy.ts @@ -436,9 +436,9 @@ describe('Example 11 - Batch Editing', () => { cy.get('.search-filter.filter-completed .ms-choice').should('contain', 'True'); - cy.get('[data-row=0] .slick-cell:nth(4)').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); - cy.get('[data-row=1] .slick-cell:nth(4)').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); - cy.get('[data-row=2] .slick-cell:nth(4)').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); + cy.get('[data-row=0] .slick-cell.l4.r4').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); + cy.get('[data-row=1] .slick-cell.l4.r4').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); + cy.get('[data-row=2] .slick-cell.l4.r4').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); cy.get('[data-row=0] .slick-cell:nth(7)').find('.checkmark-icon').should('have.length', 1); cy.get('[data-row=1] .slick-cell:nth(7)').find('.checkmark-icon').should('have.length', 1); @@ -517,6 +517,18 @@ describe('Example 11 - Batch Editing', () => { }); cy.get('input.search-filter.filter-title').type('*0'); + cy.get('.filter-title.filled').should('exist'); + cy.get('.grid11') + .find('.slick-custom-footer') + .find('.right-footer .item-count') + .should(($span) => { + expect(Number($span.text())).to.be.greaterThan(2); + }); + // The input value updates before the grid's filtered rows finish rendering. + // Wait for the rendered rows to reflect the complete `*0` expression. + cy.get('.grid11 .slick-row[data-row="0"] .slick-cell.l1.r1').should('contain', '0'); + cy.get('.grid11 .slick-row[data-row="1"] .slick-cell.l1.r1').should('contain', '0'); + cy.get('.grid11 .slick-row[data-row="2"] .slick-cell.l1.r1').should('contain', '0'); cy.get('.action.dropdown').click(); @@ -529,23 +541,24 @@ describe('Example 11 - Batch Editing', () => { expect(Object.keys(savedDefinedFilters)).to.have.lengthOf(3); }); - cy.get('[data-row=0] .slick-cell:nth(1)').should('contain', '0'); - cy.get('[data-row=1] .slick-cell:nth(1)').should('contain', '0'); - cy.get('[data-row=2] .slick-cell:nth(1)').should('contain', '0'); + // Target the Title column by its stable classes instead of relying on + // the DOM child order of the pinned/scrolling cell regions. + cy.get('.selected-view').should('have.value', 'CustomViewTest'); + cy.get('.grid11 .slick-row[data-row="0"] .slick-cell.l1.r1').should('contain', '0'); + cy.get('.grid11 .slick-row[data-row="1"] .slick-cell.l1.r1').should('contain', '0'); + cy.get('.grid11 .slick-row[data-row="2"] .slick-cell.l1.r1').should('contain', '0'); - cy.get('[data-row=0] .slick-cell:nth(4)').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); - cy.get('[data-row=1] .slick-cell:nth(4)').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); - cy.get('[data-row=2] .slick-cell:nth(4)').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); + cy.get('[data-row=0] .slick-cell.l4.r4').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); + cy.get('[data-row=1] .slick-cell.l4.r4').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); + cy.get('[data-row=2] .slick-cell.l4.r4').should(($elm) => expect(+$elm.text()).to.be.greaterThan(50)); - cy.get('[data-row=0] .slick-cell:nth(7)').click(); + cy.get('[data-row=0] .slick-cell.l7.r7').click(); cy.get('[data-name="editor-completed"]') .find('li.selected') .find('input[data-name=selectItemeditor-completed][value=true]') .should('exist'); - cy.get('[data-row=0] .slick-cell:nth(7)').click(); - - cy.get('.selected-view').should('have.value', 'CustomViewTest'); + cy.get('[data-row=0] .slick-cell.l7.r7').click(); }); it('should change pre-defined view to "Tasks Finishing in Future Years" and expect data to be filtered accordingly', () => { @@ -838,7 +851,7 @@ describe('Example 11 - Batch Editing', () => { }); }); - it('should be able to freeze "Duration" column', () => { + it('should be able to pin "Duration" column', () => { cy.get('.grid11') .find('.slick-header-columns') .find('.slick-header-column:nth(2)') @@ -849,10 +862,11 @@ describe('Example 11 - Batch Editing', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Column') + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') .click(); + + cy.get('.slick-submenu [data-command="pin-columns"]').should('be.visible').and('contain', 'Pin Through Here').click(); }); it('should have one docked row with 3 pinned columns and 8 scrolling columns', () => { @@ -1004,10 +1018,10 @@ describe('Example 11 - Batch Editing', () => { cy.get('[data-row=4] .slick-cell:nth(9)').should('contain', 'Canada'); }); - it('should clear pinning from Grid Menu & expect to no longer have any columns freezed', () => { + it('should clear pinning from Grid Menu & expect to no longer have any columns pinned', () => { cy.get('.grid11').find('button.slick-grid-menu-button').click({ force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + cy.contains('Unpin Columns/Rows').click({ force: true }); cy.get('.grid11 .slick-row[data-row="0"] .slick-cell').should('have.length', 11); }); @@ -1074,12 +1088,12 @@ describe('Example 11 - Batch Editing', () => { }); }); - describe('with hidden & frozen columns', () => { + describe('with hidden & pinned columns', () => { it('should reset defined views before next View test', () => { cy.get('[data-test="clear-storage-btn"]').click({ force: true }); }); - it('should be able to freeze/pin "Cost" column', () => { + it('should be able to pin/pin "Cost" column', () => { cy.get('.grid11') .find('.slick-header-columns') .find('.slick-header-column:nth(3)') @@ -1090,10 +1104,11 @@ describe('Example 11 - Batch Editing', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Column') + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') .click(); + + cy.get('.slick-submenu [data-command="pin-columns"]').should('be.visible').and('contain', 'Pin Through Here').click(); }); it('should hide "Duration" and "Country of Origin" columns and Save as a New View', () => { @@ -1152,7 +1167,7 @@ describe('Example 11 - Batch Editing', () => { cy.get('.grid11 .slick-header-columns .slick-header-column').should('have.length', 11); // The persisted preset stores the exact pinned column IDs (checkbox, - // Title, and Cost), rather than the original numeric freeze shorthand. + // Title, and Cost), rather than the original numeric pin shorthand. cy.get('.grid11 .slick-header-columns .slick-column-pinned-left').should('have.length', 3); cy.get('.grid11 .slick-header-columns .slick-column-pinned-left .slick-column-name').last().should('contain', 'Cost'); }); diff --git a/test/cypress/e2e/example17.cy.ts b/test/cypress/e2e/example17.cy.ts index 18f96051a1..d3dce782a1 100644 --- a/test/cypress/e2e/example17.cy.ts +++ b/test/cypress/e2e/example17.cy.ts @@ -2,7 +2,7 @@ import { getScrollDistanceWhenDragOutsideGrid } from '../support/drag'; describe('Example 17 - Auto-Scroll with Range Selector', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows + // NOTE: everywhere there's a * 2 is because we have a top+bottom (pinned rows) containers even after Unpin Columns/Rows const CELL_WIDTH = 80; const CELL_HEIGHT = 35; const SCROLLBAR_DIMENSION = 17; @@ -224,11 +224,11 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { }); }); - it('should have a pinned grid with 2 columns on the left and 3 rows on the top after click Set/Clear Frozen button', () => { + it('should have a pinned grid with 2 columns on the left and 3 rows on the top after click Set/Clear Pinned button', () => { cy.get('.grid17-1 .slick-row-pinned-top').should('not.exist'); cy.get('.grid17-2 .slick-row-pinned-top').should('not.exist'); - cy.get('[data-test="set-clear-frozen-btn"]').click(); + cy.get('[data-test="set-clear-pinned-btn"]').click(); cy.get('.grid17-1 .slick-row-pinned-top').should('have.length', 3); cy.get('.grid17-2 .slick-row-pinned-top').should('have.length', 3); @@ -236,7 +236,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { cy.get('.grid17-2 .slick-row-pinned-top').first().find('.slick-pinned-left-cells > .slick-cell').should('have.length', 2); }); - function resetScrollInFrozen() { + function resetScrollInPinned() { cy.get('.grid17-1 .slick-docking-horizontal-scroller').scrollTo(0, 0); cy.get('.grid17-2 .slick-docking-horizontal-scroller').scrollTo(0, 0); cy.get('.grid17-1 .slick-viewport:last').scrollTo(0, 0); @@ -265,7 +265,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { expect(result.scrollTopBefore).to.be.lte(result.scrollTopAfter); expect(result.scrollLeftBefore).to.be.lessThan(result.scrollLeftAfter); }); - resetScrollInFrozen(); + resetScrollInPinned(); // bottom left - to bottomRight getScrollDistanceWhenDragOutsideGrid('.grid17-1', 'bottomLeft', 'bottomRight', 3, 1).then((result: any) => { @@ -276,7 +276,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { expect(result.scrollTopBefore).to.be.lte(result.scrollTopAfter); expect(result.scrollLeftBefore).to.be.lessThan(result.scrollLeftAfter); }); - resetScrollInFrozen(); + resetScrollInPinned(); // bottom right - to bottomRight getScrollDistanceWhenDragOutsideGrid('.grid17-1', 'bottomRight', 'bottomRight', 3, 2).then((result: any) => { @@ -287,7 +287,7 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { expect(result.scrollTopBefore).to.be.lte(result.scrollTopAfter); expect(result.scrollLeftBefore).to.be.lessThan(result.scrollLeftAfter); }); - resetScrollInFrozen(); + resetScrollInPinned(); cy.get('.grid17-1 .slick-docking-horizontal-scroller').scrollTo(CELL_WIDTH * 3, 0); cy.get('.grid17-2 .slick-docking-horizontal-scroller').scrollTo(CELL_WIDTH * 3, 0); cy.get('.grid17-1 .slick-viewport:first').scrollTo(0, CELL_HEIGHT * 3); @@ -304,11 +304,11 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { expect(result.scrollTopBefore).to.be.equal(result.scrollTopAfter); expect(result.scrollLeftBefore).to.be.greaterThan(result.scrollLeftAfter); }); - resetScrollInFrozen(); + resetScrollInPinned(); }); it( - 'should have a frozen & grouping by Duration grid after click Set/Clear grouping by Duration button', + 'should have a pinned & grouping by Duration grid after click Set/Clear grouping by Duration button', { scrollBehavior: false }, () => { cy.get('[data-test="set-clear-grouping-btn"]').trigger('click'); @@ -348,33 +348,33 @@ describe('Example 17 - Auto-Scroll with Range Selector', () => { } ); - it('should reset to default grid when click Set/Clear Frozen button and Set/Clear grouping button', () => { - cy.get('[data-test="set-clear-frozen-btn"]').trigger('click'); + it('should reset to default grid when click Set/Clear Pinned button and Set/Clear grouping button', () => { + cy.get('[data-test="set-clear-pinned-btn"]').trigger('click'); cy.get('[data-test="set-clear-grouping-btn"]').trigger('click'); cy.get('.grid17-1 .slick-row-pinned-top').should('not.exist'); cy.get('.grid17-2 .slick-row-pinned-top').should('not.exist'); }); - describe('Frozen Columns', () => { - it('should set 3 frozen columns in first grid', () => { - cy.get('[data-test="frozen-column-count"]').clear().type('3'); - cy.get('[data-test="set-frozen-columns-btn"]').click(); + describe('Pinned Columns', () => { + it('should set 3 pinned columns in first grid', () => { + cy.get('[data-test="pinned-column-count"]').clear().type('3'); + cy.get('[data-test="set-pinned-columns-btn"]').click(); cy.get('.grid17-1 .slick-header-columns-left .slick-header-column').should('have.length', 4); cy.get('.grid17-1 .slick-header-columns-center .slick-header-column').should('have.length', 34); }); - it('should try to set frozen columns wider than possible and expect an error and abort of the execution', () => { + it('should try to set pinned columns wider than possible and expect an error and abort of the execution', () => { cy.window().then((win) => { cy.stub(win, 'alert').as('alertStub'); }); - cy.get('[data-test="frozen-column-count"]').clear().type('12'); - cy.get('[data-test="set-frozen-columns-btn"]') + cy.get('[data-test="pinned-column-count"]').clear().type('12'); + cy.get('[data-test="set-pinned-columns-btn"]') .click() .then(() => { cy.get('@alertStub').should( 'have.been.calledWith', - '[SlickGrid] You are trying to freeze/pin more columns than the grid can support. ' + + '[SlickGrid] You are trying to pin more columns than the grid can support. ' + 'Make sure to have less columns pinned (on the left) than the actual visible grid width.' ); diff --git a/test/cypress/e2e/example32.cy.ts b/test/cypress/e2e/example32.cy.ts index abacda29d2..9814f00c48 100644 --- a/test/cypress/e2e/example32.cy.ts +++ b/test/cypress/e2e/example32.cy.ts @@ -35,7 +35,7 @@ describe('Example 32 - colspan/rowspan - Employees Timesheets', { retries: 0 }, it('should expect 1st column to be pinned', () => { // Pinning uses one live canvas and splits each row into left/center/right - // regions; the old grid-canvas-left/right frozen panes no longer exist. + // regions; the old grid-canvas-left/right pinned panes no longer exist. cy.get('.slick-pinned-left-cells .slick-cell.slick-cell-pinned-left').should('have.length', 10); cy.get('.slick-scrolling-cells .slick-cell').should('have.length.above', 60); }); diff --git a/test/cypress/e2e/example45.cy.ts b/test/cypress/e2e/example45.cy.ts index c6944c9b58..d215ed1c2b 100644 --- a/test/cypress/e2e/example45.cy.ts +++ b/test/cypress/e2e/example45.cy.ts @@ -1,6 +1,6 @@ describe('Example 45 - Variable Row Height (item metadata)', { retries: 1 }, () => { const BASE_ROW_HEIGHT = 40; - const FROZEN_ROW_COUNT = 2; + const PINNED_ROW_COUNT = 2; // mirrors example45 metadata fallback output pattern const hDefault = (r: number) => { @@ -19,7 +19,7 @@ describe('Example 45 - Variable Row Height (item metadata)', { retries: 1 }, () return t; }; - const frozenTopHeight = (hOf: (row: number) => number) => topOf(FROZEN_ROW_COUNT, hOf); + const pinnedTopHeight = (hOf: (row: number) => number) => topOf(PINNED_ROW_COUNT, hOf); const relativeTopInCanvas = (r: number, hOf: (row: number) => number) => { // Pinned rows are moved into the overlay, but center rows retain their @@ -27,7 +27,7 @@ describe('Example 45 - Variable Row Height (item metadata)', { retries: 1 }, () return topOf(r, hOf); }; - const rowHostSelector = (r: number) => (r < FROZEN_ROW_COUNT ? '.slick-docking-overlay' : '.grid-canvas-top'); + const rowHostSelector = (r: number) => (r < PINNED_ROW_COUNT ? '.slick-docking-overlay' : '.grid-canvas-top'); const assertRowStyle = (row: number, hOf: (row: number) => number) => { const expectedHeight = hOf(row); @@ -73,7 +73,7 @@ describe('Example 45 - Variable Row Height (item metadata)', { retries: 1 }, () cy.get('h3').should('contain', 'Example 45 - Variable Row Height (item metadata)'); }); - it('should render frozen and scrollable rows with expected transform and row heights from metadata fallback', () => { + it('should render pinned and scrollable rows with expected transform and row heights from metadata fallback', () => { for (const r of [0, 1, 2, 3, 4, 5, 6]) { assertRowStyle(r, hDefault); } @@ -91,8 +91,8 @@ describe('Example 45 - Variable Row Height (item metadata)', { retries: 1 }, () } }); - it('should scroll row 90 to top of scrollable pane with frozen top rows', () => { - const expectedScrollTop = topOf(90, hDefault) - frozenTopHeight(hDefault); + it('should scroll row 90 to top of scrollable pane with pinned top rows', () => { + const expectedScrollTop = topOf(90, hDefault) - pinnedTopHeight(hDefault); cy.get('[data-test="scroll-row-90-example45"]').click(); diff --git a/test/cypress/e2e/example47.cy.ts b/test/cypress/e2e/example47.cy.ts index 2034146d05..96640699a0 100644 --- a/test/cypress/e2e/example47.cy.ts +++ b/test/cypress/e2e/example47.cy.ts @@ -121,6 +121,50 @@ describe('Example 47 - Sticky Financial Report', { retries: 1 }, () => { } }); + it('should continue resizing a sticky column through multiple pointer moves', () => { + cy.get(scrollOwner).scrollTo(0, 0, { ensureScrollable: false }); + cy.get('.slick-header-column[data-id="q2"] .slick-resizable-handle') + .should('exist') + .then(($handle) => { + const header = $handle.closest('.slick-header-column')[0] as HTMLElement; + const initialRect = header.getBoundingClientRect(); + const initialWidth = initialRect.width; + + // Use explicit coordinates so the move is delivered to the resize + // handler even though the sticky header is positioned at the trailing + // edge of the viewport. + cy.wrap($handle).trigger('mousedown', { which: 1, pageX: 100, clientX: 100, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 125, clientX: 125, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 150, clientX: 150, force: true }); + cy.get('body').trigger('mouseup', { which: 1, pageX: 150, clientX: 150, force: true }); + + cy.get('.slick-header-column[data-id="q2"]').should(($updatedHeader) => { + expect($updatedHeader[0].getBoundingClientRect().width).to.be.greaterThan(initialWidth); + }); + }); + }); + + it('should resize a sticky column docked at the leading edge', () => { + cy.get(scrollOwner).scrollTo('right'); + cy.get('.slick-header-column[data-id="account"]') + .should('have.class', 'slick-column-sticky') + .and('have.class', 'slick-column-pinned-left') + .find('.slick-resizable-handle') + .then(($handle) => { + const header = $handle.closest('.slick-header-column')[0] as HTMLElement; + const initialWidth = header.getBoundingClientRect().width; + + cy.wrap($handle).trigger('mousedown', { which: 1, pageX: 100, clientX: 100, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 125, clientX: 125, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 150, clientX: 150, force: true }); + cy.get('body').trigger('mouseup', { which: 1, pageX: 150, clientX: 150, force: true }); + + cy.get('.slick-header-column[data-id="account"]').should(($updatedHeader) => { + expect($updatedHeader[0].getBoundingClientRect().width).to.be.greaterThan(initialWidth); + }); + }); + }); + it('should scroll to the natural position before activating a sticky column with ArrowRight', () => { // At the initial position Q2 is docked at the trailing edge. ArrowRight // must reveal its natural position before making it active. diff --git a/test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed) (attempt 2).png b/test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed) (attempt 2).png deleted file mode 100644 index 4dd56c34ff7d364d0ab6bfb02b2aba1cfdc3ca30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 180494 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 g`Cpa9~~qAbVZFd7mN~S96L&-duyx zgZa7q>rU~qhGq-PY1_8-4w}9S$PH|c4dXHrBCvrNW-}hMynJu`&O4_G{9lSADfdh!>B`NT)H*m9iltkfys1-3Y2&0Id*&cXUw@xTf-?gu7`U+JJ+3bsAX)$oj%6$#o=LS4PIQ^4d^2bknhy45w>rn#vQY-8W zneYLwoyGLy%}(nE#>Wb#MRCATG5C#`+52eXCQ|tS66m+`V#wTKvp<7z449CCqRI@T z)STK_b$#2~_ZhM+bW|B;9v~WvDkEGs%-uIQlrwPg3Tk70T)THM!zQ|aJ&IdhLcuT) zMpP6OP=KC13?h@~A|_xI!Ib@kO)M0cm)Gl^7YP0zzta{$2x?C7hhc6%Q^7Ao-4x`x zir(nU#LPYQ;Q#;ZhYB;MEiNpe6y@jU=9ZV2*VSF&LUX($(eKxK3Wa%4-UTL1_l~83pdhC;lWtcdsbx!qbvhhAYW;$ww^Z;11j@^rxZ#3VJ~3&!dwFQG2W9u{|gL& z*wR3KgGq;=e?ofq#taGyES=mdTq3qfdtu|CQW#Y^XVdFl8YwkGWB z3-bwlg>9;((zjk_S|9k!6-Wme^e{S988cc^)QRe|L7EjbPQh!PsJDApl#2stNy!*_ zdBjb(6+&X(4YV)c6siAvP>XDsw6>BajqyzVyQZWCm(#7BYV6^HO!$U7&E%~&E<2R~ z0R$v^J>`?p@I4JBAUA|$*sFUWmFuJ(OZ*B(TShxx~Xke z#60A59pOk+HxCsX3vj~A!x+K_(svlUi;{!O&CmfJ56Nt$O-xfcJ+DLZSs(7lyPiWK zr0a)@MY?ewNdM_TW#qpWu9?bJCN0Q6_ioaWu%S4mcB`mg=dNfh&M;CA^G`F;cA9uj z$?dw)sn5HszPK&luQdHgo4oj@sRm=svnROV&cvtQ`zPZ&JU|Xb`4^<2m)yXQX?@6twrG(*E49E@6FPBPQYxidP-0$u{0`pP9fI%YOB01E z&KF8n-yq+di3k~E?63hWT9FY&nc-Xh^Qdbjv#K}G^X+V&d)qe+$lm+1uA$gd!;=;j zdXO{c-kKDI?GPU?G)UwlqP6qHc8BKdBe#R01I3<{_N~2XZ<_m0DCsBGXLfv!SS|ry z6`lbL2MLh({;blbJ&PFrys*?!3re8PkV&1_yKBt^Pv@8IPUwJb5cSnBoVPz(1};76 zzjGMXddJvQ(G$6OAS2K}?aO(5#Q(YvO!oviTW!GyB8>YsKZ|MTP#OM&F65bIqo7^V zLB+{QCXc6v$JDjV%fB>YvPIv5sS6}nA|qXT4*5l4ZOxy69==aM6<_9Lm}BZ{`!w>^ zMAi8RdND#crf#a@H?wf>JUpN;d!*h1_L{_vTlB1a6o@rLPu0*e}=cVQ@x zqwp1uX-aOV5(uzTP?Jff72(x9v|XH5{HlPc-;q}f8F^695Y|-&zU7<`Lopz+lzdDQ zU!RP@9XU%KtEKHc`J^~eSVIzqgKC?0S^bC87>@(B^U_Uwne9v9#q})+=~dVo1cMTcng9m@t z?i;ogx%599>L|BAo!~@=y!&r*^qXYhKVILJ@V;FA(j?(Y0n;GMW;k8yQK1?)al}Zz zkmp~k#6fC6j8;H^2FfOOQGB)#AiidlLXz^rJ+8)D$(4%^%Y#$Afp~;RBdegGT6BvN z_+TQffm$r4{Zk}?{*zNR4VfR>r}lf1J6C+`ZhIckkqkMX$S8*&tvgLpYl+~2Z&lwE z9Co9;{&3w(oK79%QdB2Kc+$Lpcwktr`1E;;u-vlc(E-lvZ_na&Zq1aJ(6kZ1^g-2%TgXh; zDGe$GzG11ulV8M6ZZ~R`ob~+Oo@g-@d}fb-R%D@Xq=9mFy^HP;jszu|leeIw;3Hjz zcpV2^4U%xb>=+tblObZo%Rhp)Fhjc^QM2x$?mS5{5u|U!um#+Lf}L?EC`<|)*k0Rz zNMSO<^XloesH5WG8wXqsv82{s?3g4(PMa?oKd7lGA@JkwrvN1|qP@kKZ3 z8q4yq1gP0eCM`Py$A-EyGsQt5=Vza=^5AwFJ#B4)^}@HT#_@yTlcNvFfFtbNii%^F zSPYR+NpQT&%N_vk>J1HSxWV?`v>G`xi-1IkHk%njK3Iqu>tlFSLV>BZacWNX0SVq1 z9S|Olh>4Bb3kL#v5fH9^nznMY1(GX4pr(tkr1`h|owt%e0m(j`S)0{GIl~B1Z9Z?$ zZDNA^D_ozDdc-ViKU{ePqa^?U_3ClEX7)~Ht$EO}+fU$o?Pn-JI5njg?Y=vzVC(Ct zDWW<6tqZ<_$uKtV+87lu(}w3gfFq{=ZsDBy2^e38%6>T?SDTCMC{sVowAcC~w-X5j zY;B!PIzJerK?o>j6Mg?ZLj-o#`5q(#j?4lrklMEQ0w>_NRogA11 zrHccoIdQRL06Mb?(()%h;W>yX zhuu5enJTxRJ5Xz~S{z^y|E2%}D0~-3Qv(0Jm?Sx3x<*0bxD*vNSfeD^uij+W{4;rZ?eBmw!mUn#)F$vbEf4wbtINa()t_G6a;pPC_t zru2LK&g1hKfU+N_;hUjC3J0>DOrcwrn3~JS)_-DUz20h@DG?lF7ntZs_q^V!86F65E<KpJ0>=Ku;omH z8p=YxoxW^G>pk@uc*6to(SMdAssX*1pRbw*0A>U_dQL%byK?%4m?Pd~(ax;|0GK9q z^D?r5>i1X6&wfbq6CALy(9U&o4E6U{yjC~33NB{uO zq_0%T#+j&ZblUd2%a0x*K;`#NN%j&+`wZ8klg@X0sWKi`fRs5{)Q7N4jqf4;(uk`B z96Uf9GR!6_yxI8uduf1g07~0a;UfVFW+dBO3Ifmu1&V9r4mgrLAd~qifU1Ay>%jt; zaM-5b-u9dOl$8U!4Y~u3MJB}oAVjWYu8mnG&0BbkOyvgv?B^PPnN$PN*>*jBz|F+< zXBtO}xQCMmGaR545mJvMFJ=T|Q)>%n>0v)u9Y4PvFowYTL<{cV0164p!^1Ulbo9*r zFCDSY#PX`kN0%#QK{>JjxnWt$yrm}Nz0u31@q792@Ju7>l;i)wW zP+?*}63cSe@G7e*BRxqR`{pW%kDI}Ih{QWE_pKWBij}qg z<|h<|!|^d(d3g)W1UBRQJMsN9nE^Pi51M*rVxyKXjF!a`gD00{^%s-D1Y8YzMbVMq*=)2sQN^Fkx%cDassL7?J8E+D~2)U4v< z_aUXdR`$3f*eYHE4S;h%7){?3wECu{`&JS?huFz*BjyCZ-UlQc3aV^Z6pADvXv21@tW}r$RG7qW#r3qCgUc5BTh-Zv@Fkeec=o9>6Bq z)SQa-wQlMLA#$w4@Q636MXCZarAn^~5@7alI<)2PW3Dlap1j^n+r`l6m6!ha(*=e# z{%UD;!Pyt(j}$1u?9a*@O@n*2>&kpld#))KCVGLpvW2wQ@9o=jk`c_&ul4UWkp_mk zAzEZm0I3e2E5C?blZ7_METv4m^lyY;XZ+9zbr)k|Z*rHy(AadRRtjt@r*fZ>)f(=-kHRP=Q2rrTUhVhvyGBesv zZ=vR<@oS6abS71~C{!pF=QxNl`stn%$p8;GuwD9gYcysxh}Aj?ZQbuZQa$$`{BoAJ z#sKdl8-+SG({BwAT1}(#8;#P*zI!sw`Bf-#Ri`;!saiNWPqCVCfO*T5xz-Xhl1y7# z>u0bI`C`zO3?0ui-j-y~8aj)YaJsrRg+7G=FNFk}#F!CT(T8$zO_h3hW_JyRr?Q$3 zI1yOS>>W?Jl^Wx=imx$pcx>_zxg6#vo3M!?F)?pyPyh%G2oQDJ(>2Iw9huuO&G|?a zq^;A`i+qnQtuxN#r>juH)#l;VVaGYZ5`e;Y`P1ISh%G5U|G7r5Jwa2W^our&fJUve zd3-5rj-|yu{<#G9uWQc&a(w>Hze?IAA++VqT@++La58Y15Y`mu^yma{d+;wrL$Xy`M zdMUMCwMv3dnOwr$gAvU@2(WUnm1?;`*f_u<)XsIM@Db94WHnH!?_KW^Xreguq;)w= zKtleru$CppNI-m&2#*aW0BLH^V&6sxS@R#|_ulJH(%4#q)Qrn)a zN-2nADcn(!5CMgxH2mxe`7s zXtz$++<$z9lIG1K*gD??afa_Bd9_^s6x8{3LnmprG%NrLr+Qb;#xE)6 z!RrD~vJLpVX4ZRo$@))!WTN%DciKYQo6*vYWpiG^sr*r8{2Zx$#|Mnr#KM+5F6Ajt z^E5AstrTodaOS}~K(Z%W%xsh@1dZ;z#w*Kv7&OIdxXLBa`ke+%j!eUfvDkotjHg;_$n$<{^lVNI{j7l!ym{&GPZqDufm!w) z8^MQ2BDN)iwT8raa{!P>czY1g8^Oa1(#svR{%+&9Qu@n#b2CaU zwXZAr6P4~FU%VA3m68s<*S-zQWl^s#nc&{~)Z5jX#7I)R(_8niv`1-7Q5vlb2sjezU(SO_ zGb_1D;T2SapP3!?9XR7fmn76gX3Qf~9*+RPnsqZ~9_a20^qTMOv4U$`T5{>vYK_j` zhtx{|;w@bx)7`EBkl17yV2O~%0If9qn>#}E3naA&RU)+$sWIRQqWT)c1mTj}<4^K7 z3MIw*Q&0T`_m}Wb%*$jzyrdzRL3U4gd^^Gu5&%MjV_(?xW1XpAPKtMkQIp8_uM{uU zWeikUH|CD+M0yDDkeCThf>HjUXhjdN3z^E1iuYC6S{)RE;?XL-J2I zADoQ&ZN1{@G`?Zs2i&VohX2E_kD@{UbbWnu&E_{e1={*phQ-;B#44og+@=%SwFSVy zB<-YjatI9#Om*A*wjhsk(U;K1+YE>#$)L!YY$W1Yzq8nRPSqHsT_Y2~NZfKs>kzSZ ze&X4kZo#WP-R98EeDi8F@ zI_}laYu-O>*5$ZBq@zn@6ILW0rVU(uLPCO{OHLyQ^#C5|1qCMEK$eO)M3e6PPdW%H zDmvPS&&5dHo+WQ8?N$qJsW0;<^|uJwG^^5K_|PBZdKtSudXcqatpicFlEwWH`J^#9 zJhaQT6m&cjV{5Z|sVVVbi%rVi%BSRYucu7(DqugmEE@llWLYAx8bS{BXM<^yb6Foa z0P4p0IK$MLozhU8DY+_2*bD!I?FhGesH3`cAtZzy9hB8k|c6G_itL~1sT|F#kx zI3z7y)H*BTL*C`RNLFAP-ul>`3xr7GO zg`DE;*xx}yQ40N+EBAF9048sactPtbnx{`nBuNKbEbgA8{v=PmkVHPDkqZA?jyIQN z8n2r;8L>)m!gvl5HO&eEwK$vyHcZj4#xEiVc51#v_z|LHK$$Sr*4}RXshvHIuRW({ zdX8nhBEm-}G{q6Tp1(#uk*nZYg-W@TQnE|Z3|z(=Bw#V=+6>!`DxChyrIc(bO(O9- zPY}*m5O`fUvar6)FQ5-ue@iY^HQ-}cj!XT!lcMHc675f_Xn$4CTQGJK;GGgHm_d&{<|D1j8~s{hBl&DR)VX!XRxekcNt$9n~XoY=mn8_pOII z&}F%!+rg(gGiy#Nz1fq8j(ZeW7oKy5gVhFw-WBy|bP#o-p*aTpa4a)fW16I>jB z&wp_KF7=uBRTp*WUROkmfn^84x4=oKT{IwUm}e%#25^*d`~?jGA{)^EV|@cSU(Q}% z&PD?z4j!bkFYZzP+&nIK?XR_BegROkgT;J&09KdS%jEv#R`w}ee?AX56KqBJ{^Y;r zH2Me?YV`>*sww6ECk#q34^aB~A0$+IWaz)1oONB~v){oO#{)|9$F@llU5oOKo<=zh7Kg=UQ&uMC>z zgoSTbk}VYT$ANkLn5I%?K3BLdz0 z#H*zj(0VSr`C|y&uWz`I;u2Q_4=$84BBM2NDh59I+ooY6piMUv0rd52<=@9>N-Eo0+0gCWHLVcPQ;zv@VvvIC=@fnk!-Af-@g#xs_Lt&gmmqk-# zocuJ?C`ViMMB<}5+*teNqAGo9+irNt>-!6Xh`s?(iVCHQWOyR$Y{&qB z8;^&iS{83}I1U=mfNgQK{1zxUJ2;mwEAb5b{H&yzph;htaa^+iWR4P>?OLWSRNool zFThu1iR4+BFe^g9JLv^ zop8}&dtZ5y(-QMc!}TSw7820?KvlFwYPd(bYJmZ;guVpQu{{V7`cn^2q>2KhN_Oi( zg+YyD_z`1&$d3?8T47*XmsLIq=P$Lx5XwZ)&*#ayEoZp8*OEldJjH(rIcniz5p>py zI^EvWrqP23P}ASv#}g*}V8Cg(l?>kql-#Owc)XOok{o2+{+&zPBLn5If$Sz}_`rTA zL_W+t$}09kSyh86PQ%S+3s-N32sDr%|4qzmTTLou;d}*=$ZYLLG9=8r0PVYH`q$a) zeBTWLBU`N(QK`afgW05qz++-85{%s{eVFKBr+dU1#>@Gq;agPjYVy2^?a6Ce+1yELL1oGiQ8;ptJv4y)|>E^Y&HciHJ-J# zkJx+>rLr85?aa*P=H}GEB^5a6r}BjI*sW*cKf{YiMiAR)>Mt%1s;+!I>^7hB0(yKs zW#!U~MKa*tH8p|syHg8aHte)*uXykQ-`I(L$~QJ@rYFI` z9<`>7X|!qdgW+EQwuE*(v9~y1Wj4epL zll>>Q`R8y*QXeBv-Sdm*g4U)OVa{;*hSaR5G=wS6S(%@Q0T^kFMnDk8k6&D_Y|S3Q z9gYk}e;IIg^3zf3Q~#@L@olubs&7L3uDw!TFEAh~$vyi>FdF`^{mZtgW>mpvSjkY? zJWhZ0;nK>}&G5XjPczn!@R!~fT{t-4x7!q(nVZA;-M&1mWA|J%b6*y1Zg%^INfzZi zpX=hJ38qSU&KhCcZcpVH*t2YWM+z8U@jL2wUnqNh30eFkGYVbIL9VN5k}i28JZSJ> zbroqyf*?~#Fj)L=kT4MMHjY3_f9QN17uP5l?`bVqvWS&gIJgRoXu~pI5DYVEd1mIz zyQEBK6Bvjcy12u)GA?aNgIMnG$CQ_Py~a&vJ;gJd+aAucNEgcoLu@yuJfMV4OjP7f zxpFRqd>)xiZc-U^IY0c~hFMRJ{xfG*RWM@e>f<}8V^gZSd$C1-% z?;7sH1}^^->i7>tHyx&|9r%(6`4Hnl_#eb0#rPh0d9KP=p%D`D{LArjb~~^6^H zd3=1_`_J{eO^^x8g z@b4PF>xO?U7}t|r8Eb#aQ8A2nc)8}dBOusGIvscQQwWXSlTpN_S4nA#eGFMxyV^wO z&EH~yiz!=czo2Tf4Q-{nfGGbX)rlx2hNF9{v)X-8Gzg|g=%1>oLR~rBj)u`ofdN)! z@{42li}}@_o~*BQJOsRH%uaj0q;>s@Z*(RbYWq|U))`g19d4=6`V7lOq&EXaT+ku^ z4RGx@5hIbWxBd`R5>c?}VO$(SR{Ou zDrvs%_~35zf-NC3XW%B_Z`jdj&9K#TAin4gikNd(3)Fg4Q5^dsR7=4^5KK{cCKbN4~up#`r_HHAn$j)0{;j|J+SgX;5*5hb6x z<4exe&{EdQ@jkzeib^#)LMKf7?*|QrZ!dyoS2MZOGlob@R7~8t33tApy;q3Z^E34} z5DzcwvV66S8h@3~%0aFVBtsr~j%m$GwVA-u#{?O7xdAzbUp1}re9X_u5(SB**BtpnhcCs7jZ6LVHk(YJt9}=0fP0b6I&GzS zpUcZoH>@!@ZuJ=o*@uaG9uFYcrSzGsag)Vi-1J3EB^LZuo;TfGQIVA(vg#;pTl1q; z#ftMB?q+Z4bZbd9OwUFk zN8I(*ICz*cylpL!)5U>Qy(e=OjbP3;`YK{RYx8DdwZ`P=D`}I%^#<5=Sj?h>w!J=U zwuEOb9S2*+R{uJkUTWTHKcmSdc5?FQl`Ct_VhrH1<+p2Gok^F)!CN%bqf0*F zN5uj-=SM8zb33O^%PpZxji>j;&~Ao-0Buha{^vGTH=pphm-ZsaF?q{vE~aBI+B!Tx zZ+Ci#Ny#{v*fEdahGUiS+W_=_JJk0A>>$&uH380?DyP?MUshL#huzunyPF>Z#1q-S z_5=;Q9q?-fpWgzD*@618eYSG%PU|?8d+{G<3Z1m577u9li$Fz&2eOy}9)Zim) zSpK^IRX!wOe-+ybB}>3)=VAI(rC8>MaQ&V6+)c*q_usp@Lta#*;pP)gkgTHb*1v{# zt%ALF(=-got2v@`#=wq83%`yQZ(Vd)+V17g?OfdN)HJRGIBnOFbI3gf%UV^^v>@$) z&XE3of<;ECfYP*!Aef7upIu~NO9wt`tBp|t8GUxIF6%Z13d7ntQB#v419lvVUdn%? z4ggSAWeE#vng%|uIN;z90O<7!y5Fvd5$2OofntVA9xM`6i#h$q4%qcpUtwOlG4m#U?M#vyCl^6)E(8g$$eyXL%K zkmyVy{r^Jov6R>Xhz@6hskgJ_grs*{EMXF*uL~1VQzPe9MYTm#T3(MJU5y-smBy3O zNr^T1LBJ?{bGe)RG%wBxh{mcYy7mr^sqJFeKeUndld6GF&zN=A)RFMot)mOV5Ae@@ zP2BSu>^S%r#9|RH65J@MqwSlL^BDlt0IQnTr$2$o9UMUM*B&Qrr@_^s2=|8i6SE^v zH&{M{7fT7BYy3E5h1A`j*)In!R|8`><0e9wTNL}(FoP+}nlK25nl2Le)21{YbPzI% zIj-9HL)rEr>r8d7$Xd`X%_Q=p1W# zAt`lg1|vQTNt;UZJucfm$<*%A@frSa9r~6Rx(vl{Ovb+Bl|(h`YX5g(0RbGxz<4Pa z&rI^X#(%7vWmEn9gE8-_&>y9?{4m=Te&~DeFmC8Hy#?M5R#3$~J3^Y~Wn}>8z^JE^ z?JB{HUG!Cjo=8fwj^1;b%8C>d7U(=u;OgL72ElwL?5QqSHLO)8gJmKVmV=ThsFs$x zFNVPq*Q5MrZP8$rg*;dC{^nWADTgd#?e(fDvD{uTOKU2HuC@tskT>NSO`V>OP6*dS zne-QS!k@Qz5fNPJt-h9E}}N1Woi+7 zg5r~wk@YnxHNt=8h8TGO3F0!d1LuNkT&LZN3SxZUlZmym)w@9$<*om_wQ)$}4UB`8 zU8u2<9gicneLKlZx10`DQmTO2i!NSLsk=bhV-eyIIt<&x+AxoYR}0BlA^@b|F>d0j^AlpM^`jE3#_=>X95#3PlcmSZW?sh%T%wVs2hG}xDceNi)k%q7 z>E$116Q$0?h@JE{kOl{Fjm%{6+u90$6}}OU$v&zh3uNg_A%!w281}#g?ab68*jH{l z6Als3t{&|56I@#KM#t0zMm|h$?VP;7t*#MRudsXWS1s=lBL5FC{u+V60@6;r3=ZIX zd2qEivb046BqaX4%?Am|#9ZcdS4?bf`i%i!z zJdU+?C;<4uVL`IWUde0yK-JRLKqS_x_(D7qaYu;APvJ|<0SovN8L`F2JzLH6wU{m5 z_rF-NbYAZ>Ixd22V=asXrxzXGmljvPac_XP4Ovw2xF2V^Sp@x9r_(3&->H95c$xU5 zPAMeN0*cKGi`lCxjIqp(;&EY!j>nvESt*g~aZ zg|^NDV9{Wa*hFG9h^^7(r7)twmOjX#8ArtZG-W4(jH{dJMls{*!WLjGwGow}t&`Z{ zVbyFIhl~}1WyEP)qCF%hI!k0>oFib@9@j2UTV}(V%M!I<=y<uU#-t-d1g8QJ7K53>vVx+3~r8O0^*$w8NOGNI@HH4R={fV$_U*kaR(d=WCTYnu$Dh-4N_9!2il8t1^+@V{? ze`R;L&YsIhc_D8O7OPVto6)5rBSW#$UEcf!ozajnGSO7Cnlb9LC;iBnFlg}gIPj5a zZEP_JV-8Wp>tx=o7mUMoJs!T63=P+Yf#dnpx+; z!p|N)-Ge2XMTzMop$-OT-v)qT&T(yvi5XTNu=i%w}G!+atceA_y3UfmQis8Ti5X58r(g&ySoGr?he7- z-62@8;0}So-QC??g1fsrypwzHv%Vi6YZlB5-P3cbs;jH_K4)*au_9SEyX3oeqE_PY zi#tT73HkBgeBuO8Z(@?NYzPNC0RnCBsq0idH%Q2Ohd%0?Vp&@rT}*SV+l>~}vQ8Q#t=MokQ~d?WbT~B*h2?q&VNL(xxgupK z6Z8*KkDw4XmIU9ucUMgB0ROA@ne8&7?B0*PIn8+^1nfnRZ%xDa5fhj&Z=?zOzNB-M zG3jzYUW4+4s;wWhsT8vBG!Qt~X?9fAkHxh1-^WB8KDI{6lHL)2fDqygE~4?Gkj(MD z%{P`cQ3spb#(7C@fWGH#$B?q*%Z^p_~-E+ z9?#+YdO8vQcG7YY+6_HfJh6JRfZ3}QlY`T0f-IDf+0qY7rm`o`(Tok9PuofB@;h|u$Pr13&nNG`Q81G+Ip+{;;ElhEW}J!{C2EzlRdm_ zh2u(g>Zi5aqR^wqV=FL#(6^c!hJ99BZx!`<~;pES|;_N&mlK}{f%Njt7^y0eS#aLF&NjE{k-PWSWqgZ;TLFvPal zvpF%L=Odi@&h(Kve!PiZRQDTm`eIYA1Jez;(EPvszI1TwdsA)CGx%8EHM}A(e=ynN zlIyJUoG)LxCcgh~%>yw9l&d>^S@Dp0?Jcq!tAyxoxQ$FtPi%$vezPy;F;-D=_QtX` z1c-F0WGGmdRV1CSeh#d+@fZ{E-@xZpMjR64QB-Pkoi#WVZ(L_`kKG|J67C{DL0;j< zFw;?AD>wTrrB7hLv~k_3r`qw9mDV0cUDnx{!$5v)58jVGULKG8z0wzR#O|vrG0uOi zD{h{nI^r!DUe)NeBy&7P8$hYg$qn&A{-@$i-}V28!yPZPJ>cOUuu5s;e#Yeda9Vbs z_&Pp);ECQ+AkK8z)urF*@))*fQXp!+5=HXfO@HWFH%xJs5!58`3e`RQJ>_`Nqp^P5 zcFNC{237Fw$L7Oz%zw%rK_HNA6g3!hzzgq4n*Uqw?S3p760E1S5e^)10`6~z_m?$< zHuWcXd5$-<--j=@`eIlnYf}t8742M?ns(}O>FcLjM@@0s(Wj^A?CH7d2-8bc#S8X_ z1wa2gr{5XngX+qn(0a@NsBj@M_RBU)l0E1$_{zTWI}#Ln*?c%d;sVm~N1asMFL^zj zQEoFkp7HRLKv>)Ujky@DDSO1<`EUu~|1J>cphBpq$`yoMJM2&uMGC2>VN#CS=wml+D28Ta)RV=xj5Cxs{Sl4swWkio=tN12pcFM>nRR1=R)Fu z%3_7Bts~3TMal70isuj!p|j1jD+V+0l4`Z3L6wrSa3~`B3=9<&4Ykm|?{Kyi#z|nH zA~H}3ZCW~>g0PU7fxpwhZK2u1Pq;rY#!j5~lt(2e7?dhfp@07@?TP8@{5q7I%HcXb zRlq63ckue7-h`77kr1VS&>K0N0YT9Xf>DV;fbkxvX$YaBN$iWDpPk9bz#Zdf{<_B3 zH#E>BM$4FzrLlWPxdpZp&^8pdXo+)BD5NrvFR;J>3knJ(7MX2nq1Kr^>qSpJB<}aPx z{8286imHx4r;1r$$SW!;s;W}xMs!%v;T;+}HO~M1D4xe76AMmp;KBU?-7F&j%@EN3 ztbY#qBp395plEiS`hubsH426x;oB^?8G=|746k&;64RprZuS!|J=0H(w--f4?e&1d zOYz^=Gnt=DAu0r)nOd9s_w27i=8IgLucx&OMZZqQ6d3CCpO(DuSCG5(J6=xTVhk20 z#HBx{HlByLl=h-O6FB7>nAKfU#BfEAN#opvH{t6o9&Xr8;aF(Zz^#%N;E9X*BNFw`uOgn21lLT7=U$L zWog~oa#-r!y`zv2Da#hdtEKGsuJ=pk+WDQ8m05~`9Bhy!jU4r@TXr8h4jH&m#%>6? z#Iyy2qwoyv3|lr8E3kMkdepIY;tpwQC)vYKE3gYG2yPi>*xYT)JMcQ%i&piGB{~A( zC*D@a_GR%Wt6Vh)HdgT@G{5o^tyH}Gh8i|5IH#5;U=Nv3802h8s z5{8LSW{GLU)%ZT>kabkvN3CksHnv}1tP7pBANu;?pm$tsj8eZ*4CibC*syhn&5wuN zhF`fk!1G55FDrb+Fg%_XUqvQ-x!V2)n||--R(m#`yzDLm20$zHYyRAOfI1dI_}d@$ z+3x$9R*PX`)xbkD6a! z$?a|WA?ZZvhSO9QZOG!IaS3<)cJr3Tl3GFf035QXZ0QkK6 zphp#{po+mqXw9DXPX2|5{>hv=tm?DH=UT`a@7!r0XjbL2M_fhC=f`?j*F~>)KK#5_ z6BvJRur{lN;C4`Hz{zKl?89pQ&R5DOM%s_)YqS3%Hb6x?ibVKgwO^ZLKvWBwZ^31i z&Hdf@9}|Y4Wk7*eF5mZsr9%<>`L;Lw{E9Y2|Fon*M@jzRy$7_H9dqko3(agbTSBHI zPUH1&Ap7)~CgnAIBD?fyI5oMB_|>b&w?v=K`wbirp`uRA#HTd#r12Et^=B_Vqhnpd zdi6{?wqn`;t;}$MuT={cGLNdq;TPpPCk!3HLB1@*FcETbr&Y9o^6dOOPFSL%&Sfl7VrtatZgG6F9lG#nYx=kdFahuyVEs)fAzm$X?-f``J;VtfQH`y&mhF z&Vg8=%9_4-D-twTKBq&7l{L9@uxx0CQNhBML5#Jg-tkMF*0Mp6o3E#hK|55PR!ss1 zhNNCS5}AV=P)3^L?&aZh6>ZYe6}=uDX0lKT zRL}5{XM4m%2?Nv3BZn8NJ{z@owg*-;A7xnA0@Xaj`yJ(b?raGpD9p+mw_ACjjIk+H zk7>STRxoG|Q!j8<1-p{33ss`Ej2;<*xROsu2Q_m6D=)VNk3PplO=B}zyV#J0jc#zz z%dTICvdB!-;MtC-a!6=dG8tmEdV^?gzFMn9WH^?94D94y>(A+w^a%q_H^j>QJ|fni zsG4orxzrRumKKiyBxo#9GqNK_gk=|PDPoH~`BoC6&+t(eEeRv`X+O37Kg`Dw&704`9OMunT?4tFg#uj%{o%{92KYGu z!1QGh-5ww)!Z!4D+39bffiQK-^<`-=h>;4?2vC*((OHnl^)pUPu@xcO0kko{Nw zUVx^5tw8^d+s4E1tuH};v~pDUZc_GGtU03Y6%*wcT#bjYX8AoB*LK>ywM$Kudwa`YvPDIeAsSptF_Zf-xRWSXW{ zZgY42w9oYPj$H}sxih)N^{h>LNbngI*Z?{;nGG{@&qX6sOj^@ZT*@fJJMA^Ade^7P zU=_TM85?B;b-FV4^t&h-dsb?#vSqo9EW4t(+O%>gQ1O^?_3}pgYpQZJpXSE7!xm!j zEq^7;Y8DdNK64X2IIx})n7)|$9dFHu{o1N*?wp;5a8M#T)P0$rab?dz(>1aC8UWxn zuI4$T4C%8m*Yb{3Eef4!9vg9z=c99QqZ(_~O;uiIM~H}{t>`gXOz5dMqbqD`)8*u_ z=WKCk(4WX)$IHr^WzxG5DUNX|uHbGPLFCOCJ7%U`M{*a5te-4(Tg`I5=Ei+l6sB(Q z>KOS0PF2L7Iw66n$snKHkVcr_b5V_JvBeF>97N8|MTZhKGk<;d`|_+XR#4ZDnf~)7 zamv|dF6XoT>|Q8?z}$P55|#`Xx^7jM|FFIlwlG)CBV|2;ja{6rdF}#!gH^XgZP$(~ zS4l|@6Gay*A-IuS&#g`(5JHt_!c5=Twe`$v+^RkYeqhJ*yJzQ4kGxvJt*Z^JP zmoH2JiD~WN5u5yVtc(n6n!0YB`eyO6MJ>lHzsb45WWrE)ya~I8g=3dg+{t5Fi>6Kn zRBo;?nxJ>6KI&+Bdsa)>CY@ZiG&#IpJGbicK9jzB^|1B)UQc-dB9(N{#Lcu?*vN?f zr`AIco8VOqpE}-0d;isTGPr`X|D#I+ry+MB9KHJmtvy$zBCSVQq$^)0_+?`ocT+GG zt~fnFqBM%LNG^7j0cp{eZCM)Fw_nRFpD=K2&7#lY7M1M2r>mnS9X(<;6VDhKqLD_3 zR2RC%lWon@%UuQrt8L$qrYbUq(`g!OWqVD?RpVH{rz3C%Qr_5SPtyd&RFAtr4&B{A zpvlYXf&x$}zLQ8;mTLP>>3&-yyL9F7@b_t#$846LXE&{73b(Zn4K1+Q%xea6Ma5(5 zu=E?P@1Jk!+%}@6_OY&AOrWBJZxJpeN!glN{#)NT00C7e-786_ofAwip0BMO845%B zvA6^f%*<@oL3h(9nD`Gwd}&?AeJ6wz??XbF8z6?}C%Qp}_Pg4~NH0W&hQF7TRKcuq zhQcoyWme+JaGp-LB+u6xwh)-w&DUwq%7M0|%h!EbechVtGGC#T=RYrhxvT}>DT|K2 zf5bGDJ!Z$sb+D<^O5-LZeHG~RdS2-mn%uiwy?O|H#dne#Rur1=q6Vc4*xxARjB7PH z?Z?C;>3{KkK2@(+yLL93>4_$`P-|>> zInYEZ!r%r-SVr-lIF;3(bF~39`!c_51%Mgi#Oy*s18@RJ4B;D;WdGi2cO+3^LP7@^ zO&25qr;zu62+8CAiZ8O)B|z^rGBbwv95{!qp;;myTug^}GlwvconkBIN$A729w*<> zsvLAe*5O&>ALwzu33dJ4E&4{Ys!RSuEGei6h){IM(C4WZJpyYsTtrH~*xeu$48lIg z9;4agJBf^pLokg}jPi-?YUgvGdbp-OL!4X^=5A(0h}_aBQ- z1Vi!J`pxXt!WYOFpVl|nkJB-v;8Soo%PCzetI;a#BYXsI*d+@znr-WoJM5rZWGp-= zW|W^+lLJn6ks|ih&CCRl2Hv}BWqiGN_GH!$hG3bC__&k#3u(HkqJa$;W(xsE7iT6KnhhuAIe}BYv?iEkgDEVDY4<1eLEc@~r$3efA+D?yt{e zO{7GUPELN$RKa=kIIiOLtxX$*5?#JN_oVm&7C-Np9^19z+78P0UG9-P+0!ghg`Z8G z_tRV+>1Fk&4$BKA$Y+5-H~zV^JCJ*~5aLOpc8-M5c{>n||Kr(5@b@5S0{Qzhu%>h8 zEq=N^po+Zf$tNZFSJjz^qt-4mG1h7o9hs4V+IAC7U$;!@bxG)uSkMIdI9z{*r`;YCx{X(^xw zty;P5!1t_74hrCipovh(8lJ0ri@1!{+in&HPXxpHm_u}R^XlOkvuE5V_8Gd|0Qi{< zzrAu5m(1RPSrWaDx;H&Zd^z2{mfdG9VP^z;>m*u_!v@`!ZcpQ{eW`N>w2w{Fele?? zv0%^mBC~k06X>2EEkA15a3rn62K35QG|bGv6yD;NM5}gRpA7R zE8@#Zbfou+0D_ch)+7?jT9j9)6*LJC9L7FbXEoy?n~!XBy$) z*_ctu)A4Es(%Dkffoo=rKx3G3HE;wCIm83F#=u}R`>dQJS1Rl?*i*aRSB$ue5;RUG zupo9h@y-YUQ?~)TdgZu`rXB9)6Iyr0J~?EcnJ{}!5vq&{9*mwDZvu0!kWCXNm=?Sh zpK6Zs3ceI0RBU} znS=J#5gK;0NTrFiFIZUi3ll$9Ojc_p?^Ax@IJ5)6hZ_F^+s^xt04VsGz1W~u(YN5E zcAaew0+2W`V!?vj+n=43T*IIh4_vmlyba8Bnc4HssuV)N_G3ioHU9DIKC8N47EWIaFxkkn`7 zna(iyslBGdu*-tX1x7X(8Ai%i-XG2^#loVpEiKD(p`6R?v`i#F4thEtFn5z3Tg#ly5PiLlGZWVq#%!s?a`dYzmNVvBXL#i|mM26>Pz2y4X00f472Rve;0qP; zT}GKXzxTk#J7m;Dr-<4sw|ucmFI0o}MxX$YS%&sw&{BZmNYg;pfo?F_v9QM){mA;H zq5isRBPg7@=F5#S({@C1THqH2z-BgCs79?EDO;)9{-jD(FJV@e-sTpkgH@JB#~Eb%GTCkyX`m>&d?tWokc}Nc2?0D!Oy~?7MX@ zYdU*{Jl{quRgg}JN$VQi%ieqWf4Kl|$_Mo=c=zJEBD;*IO`kP5w)yf?DCEE;LoBgi z_$Vd3j@g1Dnvl_>9k3TRXtMKZHum*uY8=2Hp@M!Jyn=Xjd_wv$1Yv4583Lg8QJ2QN zk+yYuO}%`P%ZX}~jCs+WS9&*WL3}qxR1irbdqkXz&UT>i`Ng ze-5eq8*ZL(3a2aY?!#Nz-D^QPKdr~fJvh6JWOWqog2M&qFCnNJ{%vr-C>`SqrGkD? zN9C_-n+GJ$rq{P?^-Z$_>)hr8DlUdeWD35P<585%H{!Lzj@!;MM-7rQ<6%lS;(>x$ z@;D_DpT$dOn}!deZ7yyys&{$O2CXi0kxHs9EuacG+d>wDm4|#qwz4QWD)JlLl#77P zSjHL_!=UqENJHP{uAF$!(S3Nu4kcLl4m|H0X~pxUCcjJXiRK^@fREE$zw_+8W_BN# zT=~BAblbvAzgX&fGlzbR!1X4pmxbK-!`;HX(m`z~>SGMac`$}&PNTUr_|?p}K}4nA z;!Jk(L!n}$meh0i%siRc;)6yyo_Dp~H1|>=mI2<%6omO)=(@IPahS`^)hbJ$*xXJQ z%s#3*FA`XX*O}dAFKbm@l-Z@}{@cl!rM+TS`_~&uhe{peYc$U7-O%ET>Hz~?e%a~> zSYhSJ2e<3vr7E3MDVT6}3;*k8Y?g=PXkJz6g=Kl9{d`cxX>AOn&%uK4K7fSq>8Z<& z9yQtQ-Ov7pk8fP%Hu<}sr8_R5A5qU_y?VIGt9C{C$hs+pCAMP3kIL~pN)giY;O5Kc z=$l)imD;U9UV#|VXNqk^HT&sfBO3sYAwddSu)*u_SL$GEf0TCh2=C$AZ24gqcMk9B z+hs^+(?Vo*!hUHau+F+pRNGSqN zY5VYYBIbRaL3=7bo?vN}l!Q05?$iY-X;~_)se;MDtz)t$HL8NrZxZC>^!;eH`M9#G z-C>|DL--`*6`Q_ev+a0}0m?7D=&)TFxfzRtQc=e{@bNAZS!MAQ;*$9!vtD|05d0PcN!n!8p9iaIE%oU1>YPl+ouFZu#*bI*gxW zY@QxxcXjp1e3XEm=No({*bZKuh#t%_(PS3`$7QheQ0@;K)DaHn7_P}tTc zs+?w6#bRpXdTu6f78PM*4$KZL#AKUso|)qnH2>?3iPbUnDXp5b zJLTJlx!F4wWYZs&9H&SjO2u6u#GukX&Fwm~7jspfO0eGKx(~FPD|nv5%;BLT%*tL- z&~_12Yp|Gk!m(roJVhd#9KNVqrcBW2cAmTJ^vNd6%l1q{0E9g&?^m5q3mLS0judh} z2>8q<@lSeEFTXXPEBX4|#-L4{r6lva`neLBjO(|PkU=~3%K552jUm}==rk&~*~}d8 zz6~|!Xw%H zFi-(*?z(najnuCL&$I687N4$$aFmHYw;1AU7K8p(j9dn3#mBzz1Kq90VP;CK z_SN{$L=(-1HWy3)05(iN=Vme6c?qIXVVBof`pEDBwwu`*OC48AKyh#c=(c2eZ z94_9>SN`tHD4SQU*H*WtIbJ4_biCJa;BQ25Oexyax9=}@xUE^h_Pc_o36t3wX{QN4 zqHyKIvG4q{v<6=ny9)q44FwXSA7z832^+xJsi7ZjtU;cti_8l7TEMI3RN5J*(?v@Y zClH*{F_pEJPM{gbv#Gp3kB;)Kk$h4X?n;P}pn$>&6ed9l&(2Me`(v;EgL*<1S!|<; z=b6e-YOyv%2E=7>A}Xr9tPE6vdp4sC((dlx$z}j|Bn-!h z0ADKj^V0ef4$FS;6p4ocJxWMt z>e`7^fsz`L{k*9NZQQ(=uL8zbvT8myZGmDUV{ z_}_QzXAs>FIROc_78et}P5WHOlSH8H*02J&L5^G?w~HJ>4WBzHUtfW-T5{+rdTM=QDpCA=^kznYB`FRXXObT-HHCQW8E|3RL7a~35 z1PRKLFjeq^0B}&>-4Ue=^Nk}AbbzPkzv1CTx>l+h8X8Jkgs}6!2I7cfslI;QKH1o^ zwz7g0Y39J#io|B3q@d6n2n(>2MERLOr2w75MN3=i|M}6upv0vDDr38S&I1BLUq3ND z9WV1sXUmr72PiCn!}HBPD18bMhxtzvMQg-1cV&~fFiCF#0+c2JO=T5K9ISTB;bd(b z>Q3Xf!1;^c^4(&dT=dr^73j;jiBJ9&98%CQylf@IZW-=?PoXpA!yCFm{zO5?_wRt# z>9>y*{-aJnaiMh1W#NWHf%&c5_t)v*q6j?a%gl0gGzEx<$<9twQ^6oUKK< zTz9o}5`808(!mymywg=E1*vGj1YTTKWq$#3 zgR|ixc#b1}j{6L~f%xWv0b{!~!_nSX+_dHOk1NJcLNa!yKvS{!?v;K-QqawN&Zt|` z{l}RCUZr^4t!Qq)zSI-gGl&Xek(CVB$pp=pP6=h|=JwriXxe&?j!@+u7sVX-2>t-V zf^9Dk;L61-Sl!;Vf}K;x+gDCpc+Vw9?dn}_z{q{BjwnC%;I0PfaYTgICz_7Y{>=nl zIgA)vtzO00B7}f&?I*Br=b>amH=xI+;zu~FlBDA|(tLn>V0WvgZb2?cOo2LU-6+4O zM1z((eI~VQwZNQR9m^v{3#h7CFJo_A$YewgTqvPt)m6hHqo(MoIYW)qHg(B%IQLrRo<-7fDv3eLxP$~QBoK$SHSi6;dI2C@(`J^MIk4eW zZFG^XR}F~}SmOu?8p~JCSTC0Ys+QhMWJy#kp15jPsFy9I5fD_XbBJ5tc~!_1G&Dn^ zaJMPR!4&bXgI_WquVk5bj-ad|!BoyzJA|z>030|OfX-LB&-eG6srAqq>-h`I+n zEEolUEGh~t3$dv%Qdg~{a51m)z$1)(rcNFxoh77_yZPG7y~^3aOii0jq3oAa3Q+ zqFX9@q!0)hMg;J&%b1bCA!IBtTqw5HFu*u;a)Oi_WVhhQDVfD%|0!F=xx63&&K#m` zuWIC$wX$RAuwg2jfXq##+`}T83z#hA01;9ua?KNX!D+R{ zd?+w|)D7}ehHhD-<;*$_`D4YbJgO1^5RDnlZognrAuqAZ$tM-OE?VQpH)K>l`ORHP zf%tX3@C%<)f>}oeE|K#>1twM}8_XHs?WB6}8+`ifr0yaaB9U14qe$*U z1X4=Hd@|e7T~uYq>S1W@iAI(WrSRqGWuc4zYAt{;$6c>Y(HuvmrisMz&7-!haq{Ai zTYJKaB19inn(O14~oFsX}jEe-?l2Nq3h!UOIikem^C|_gRCM8NA9QH z;_ulk#*tLGU-G-8C|rbk)ZOd< z4KU)Syb_fHVu6TC)f$4zd{{*0_&i9q)oUAuP?2hGL@EbF00g2(Pna=Ipb-EkCh$MZ zg*D4*gL>xTQMiLyTfsHPE%oE6t7iMju9G`q3Ufv@VQ;H)H3?*siGfNco3)?8+=vtz0Gr*t|s|q&g&~-R$bzkh6huvbT8>Pb;1{?B@=j{XBh2oecLk-h#Uh zq7{HJQegQIL_QBmxYFkC7~f~4kv%T zo-ui&6;7nT9$i5KeityOr=*ex%nHb2ok^!Y?0p+)S_I_|(Kr}NUN8+g|1}!h^tu{O z>A}h<9%lO_Q_a%ICPa;4UCLWkJ-B4Hw|#$-($p9TF!~@4vZZ@=JzNBzLPGw2%?|K1mM< zu$kxA>NK{!lMr)Q&%6&D*etI-)9_r{N4zjyE`b~$;$=3Ad6<3=Nd8S-MQ&f!%sTda zEj<&B$gd63y~?IkgWoFSZaxO8WVAcpiG2_=PHOU>Je}AqBUJI@*tFuNYv4ZDObM-@ z?oIm9{d^fud__*<1ZUgrjPyPqqCg`d_WNG@uU#Jyz@9_k-1~hWK9VJR)Z6c~-Wa7r z?C*9oDAzV#hoZltXozW82b_YPoUFE@=D`+MVK0ac+cF{jXTA2&N(ZhasX<2e8_osW zFh3r;NWfy97FLumlBeJ&_@OR6GWb-Dk4Bk*qqcd?vhBfdklPSW?mS);W!-`q2kSQ8 zcUea?szkit84K17xs`~L^RWOri(b>3wvgW<6(fXlXzgDh#vZ|jdpdZI3?XYMHw(4cs5Ka zcM(IEGIWv0qgyK7NNU5k1UaIHo(ga!pRusqERGwRK;EpE2>?HIf+xpa-EX@Sy4|Ww zawvQm#S^_g8m|Xhr{F9SC82c!b)00t{?u*_@1rIZM@b{EF~9)qX--pyWC6n2B#)UL zTPgjfCKFn%4o_7)oo!S~xCul!e)s^@eLnch?9ZWnrj6UO-6Ky=U9-hZajlZB+9drh zA;I?^^TR2eugD5trJC(8$p7AT8tpUM$0x2fl?@|r8O3JFx7+)?SeVTR9JEN8I(9pW z!{C!o(NP`GkR8#axqq3+-ex z{XZ260E}5{DO`pZ#9w47pC8{HCO0+P@>r`33UTfTe=S8o9|}7ApusC?|&8dcg@i*GUSU!DK-plWf(_yS?da6 zik1b4y2$kpjJtHY?8Y|U5d8i%>s>voIZubfenuUJ%j z0QghFOZ9o#T@v+NML&v1xXD~CwHr2(`8gUd{o%tz!^Ewxs7Ot?aO0WMKjK@|jO8mK zA)n6UyK_{NOEmBRw^1&F1}Nsf(R1$EbC*ly^Pl9lG%BHL{|FFX^KfB-(1n@)5!2K| z>{C!47Rn~R5*hq99Q!uB`T7wajx@x={_kpAFf)f&5fA=-3rg9#@HMzj9FeAEw(Q;u zbD*|8fJSXeoSvqD!}5#77vN!_D!$yxgK0O0Rq$e^a=e6$v=VJ_TnoO+(Hb;^F&4A3 zDKo9(#uosJYzg~Mc_6z3e?Snt4iX|2WQM*ennTd+5w%0Ai(P?8nav~h#yGt#Nap(I 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 S@Kcu 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 z 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 diff --git a/test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed) (attempt 3).png b/test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed) (attempt 3).png deleted file mode 100644 index 73c21d6c860a9ffb12f6ba76b8f4840bc0637821..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181553 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%0DBjABQG>nuFGJf#e@me% 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

?Y zR2BG}2<8Ynp35Otb#B&}z3aelw1{Pd%ZWpo2Sj~V72Z_U(A$k{NicxA*H{%!Hwhwp zxTj0Z8S_dfFPx*^gS&yQsgUf{LpCxvkGiLcd7v5g_QRJHsd|Ue?7kb_jqF_GLlTvx z8?1fadvmo;({WC%=TI=knwqiWsVh#B1=Ts@4pQgTjza`I#py05WWP;HvX~qvw;_XD zPAR1K2IMHz6@iQI5f&+zx60$zOr)hoZTzIi+XTrzdGbYtyCo$FOSZdzRq3bSY*>0n z-Hog4>#V*2znP;mUJ!0IHyrRm{cTAQ&tn$eO1$qiOkHWh(p`G}5wqi$WUG+8nYTut z^UPgZ%cS|CMtOn!eL@5%#|&caZkEPfCejt4nM@qGsodtMNRMsnBdiVp1CB zzWVdTv#HMd-9O2GT!5eUpJ1q`2X|@Yx{3lfe4k^8l$G0F{?Nm_Oc6?|VuIG&i5jPb zDf*TtrlhEy1i!qd!C9fI;tSMoV+X^=%+*WYdh^{@vOqZ5D3u}nvMrBgXu_*>`fB}! zg{VQ$L(hn)H6t}!&*G%qJAI~6#CrZzf&1}la*vVa0p?)ulVaW&fzv`8Sbwy|lrU#| z-7d_%y0fjLyvR@1w5A@YU{ot*w`w?{s#LuBO*aGsB(u=H|yohkFMG zdIyFlZHi0En1w3j7%458C3=Pee=Q81vXTXn^5Jg|&b|?<*h(l&Mk`r|(DMK(@;)Qr^}7TiwC`!pE0W%U_62#n^x|_YPVsWlhu9Lnk=%oY1<;dCPGuxe#n<|4=Vi3RGz)%cE14RBv z1|lzC1P)tKD$Y3xk$^8p@t!+>EF8{qWc)arqRpvTx-}5xSz%RIBI+mt3p!KY|03ex zqbdvO0h(EI)$h3Xe8B>cT|p#|-&w_F(O3ihydmIx=K619Jvp-V_b(35(_ssx4o(bgE|Tk3qk6-dg)wWyy7wqIoQ0; zPPC)@c$^~^QE9Ilugtbl6}rm-Z|XH4_gnKTBwM*JT5uqFl7=}$XNlLyn1yw!B!|KH zpzQs;&Br@vB1IfNbKZn8_vXe%?JlMv&6AjZfyDL&)xh{jQ1{pxfe6Q^Z2YotgFF5A zgK5x(4-L3Ay2BMtC)o8hoh69W3U-~r-e-I8B|eXW&Elu!J9x(J;WbH!+=#$rOS|i5 zdxY<-Yrn|-g9D-kdSmQ!2iBwIaE=UPxe!)*oQq}Sn&LA08ZP7IaYK-b9Cl8wvQpOm zAEe!nI-5jrL5Wm5ymWYh+l{#s_V%YVEfrNA(^k&5iFz#jPg5S42n#PQ9}kdJs7~M| zc1D$N4Sq~NEqu*7J*A6>^UT#ts3E9G#C^T;S5{=coWNtGd8_m-X%hQ6|KBp!TT|Qq zy6kS>$ZyR%^%9E41l^sVp~_n`WR4C@IRX2(!u550%8gl(F+2bAThz~s+r@u2bR}Bk z4RSeBC&j z_{9`kJV%`wX66p>dE>{EC~HG}eVx%@+rJR<{U!4&9Q4jv`f>y1_h{|-Vw55#W7eRr z>D{s@>!wBb}3fKK!3E6J!`9EOwMMMAq diff --git a/test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed).png b/test/cypress/screenshots/example17.cy.ts/Example 17 - Auto-Scroll with Range Selector -- should auto scroll to display the selecting element when dragging in frozen grid (failed).png deleted file mode 100644 index 06dee3149d9a6afaf59a894e08d2ce2aa05af4a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 181751 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

<9hK%j!_SGp5E7?I`;7dIRiDR>= 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 diff --git a/test/cypress/screenshots/example37.cy.ts/Example 37 - Hybrid Selection Model -- Grid 1 -- should export Grid 1 to Excel without hidden columns and keep expected data shapes (failed).png b/test/cypress/screenshots/example37.cy.ts/Example 37 - Hybrid Selection Model -- Grid 1 -- should export Grid 1 to Excel without hidden columns and keep expected data shapes (failed).png deleted file mode 100644 index 7f1387c9c396a78499aaf907a26fdfa830a107c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 170665 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 d*z+fkMDu= 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 z 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 Xko>a?OsXLrd7`+8jBo)|@5_GyulY6< diff --git a/test/translateServiceStub.ts b/test/translateServiceStub.ts index 53dbd28454..a7bb5d03a4 100644 --- a/test/translateServiceStub.ts +++ b/test/translateServiceStub.ts @@ -39,7 +39,7 @@ export class TranslateServiceStub implements TranslaterService { output = this._locale === 'en' ? 'Clear all Sorting' : 'Supprimer tous les tris'; break; case 'CLEAR_PINNING': - output = this._locale === 'en' ? 'Unfreeze Columns/Rows' : 'Dégeler les colonnes/rangées'; + output = this._locale === 'en' ? 'Unpin Columns/Rows' : 'Désépingler les colonnes/rangées'; break; case 'CLONE': output = this._locale === 'en' ? 'Clone' : 'Cloner'; @@ -113,11 +113,23 @@ export class TranslateServiceStub implements TranslaterService { case 'FORCE_FIT_COLUMNS': output = this._locale === 'en' ? 'Force fit columns' : 'Ajustement forcé des colonnes'; break; - case 'FREEZE_COLUMNS': - output = this._locale === 'en' ? 'Freeze Columns' : 'Geler les colonnes'; + case 'PIN_COLUMN': + output = this._locale === 'en' ? 'Column Pinning' : 'Épinglage de colonnes'; break; - case 'UNFREEZE_COLUMNS': - output = this._locale === 'en' ? 'Unfreeze Columns' : 'Dégeler les colonnes'; + case 'PIN_COLUMNS': + output = this._locale === 'en' ? 'Pin Through Here' : "Épingler jusqu'ici"; + break; + case 'PIN_LEFT': + output = this._locale === 'en' ? 'Pin Left' : 'Épingler à gauche'; + break; + case 'PIN_RIGHT': + output = this._locale === 'en' ? 'Pin Right' : 'Épingler à droite'; + break; + case 'UNPIN_COLUMN': + output = this._locale === 'en' ? 'Unpin Column' : 'Désépingler la colonne'; + break; + case 'UNPIN_COLUMNS': + output = this._locale === 'en' ? 'Unpin All Columns' : 'Désépingler toutes les colonnes'; break; case 'GREATER_THAN': output = this._locale === 'en' ? 'Greater than' : 'Plus grand que'; From d790054ea4b104cbf1255f1fa9cf1cd730c47def Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Wed, 9 Sep 2026 01:20:52 -0400 Subject: [PATCH 06/48] chore: fix all framework demos & horizontal/vertical scrollers --- PINNING-POC-PROGRESS.md | 47 +- .../src/assets/i18n/en/aurelia-slickgrid.json | 10 +- .../src/assets/i18n/fr/aurelia-slickgrid.json | 10 +- demos/aurelia/src/examples/locales/fr.ts | 9 +- .../src/examples/slickgrid/example07.ts | 2 - .../src/examples/slickgrid/example14.html | 10 +- .../src/examples/slickgrid/example14.scss | 8 +- .../src/examples/slickgrid/example14.ts | 12 +- .../src/examples/slickgrid/example15.ts | 5 +- .../src/examples/slickgrid/example20.html | 146 +-- .../src/examples/slickgrid/example20.scss | 8 +- .../src/examples/slickgrid/example20.ts | 488 ++++---- .../src/examples/slickgrid/example38.ts | 3 +- .../src/examples/slickgrid/example43.ts | 5 +- .../src/examples/slickgrid/example55.ts | 2 +- .../src/examples/slickgrid/example56.ts | 4 +- .../aurelia/test/cypress/e2e/example03.cy.ts | 20 +- .../aurelia/test/cypress/e2e/example07.cy.ts | 10 +- .../aurelia/test/cypress/e2e/example10.cy.ts | 4 +- .../aurelia/test/cypress/e2e/example12.cy.ts | 4 +- .../aurelia/test/cypress/e2e/example14.cy.ts | 185 +-- .../aurelia/test/cypress/e2e/example15.cy.ts | 71 +- .../aurelia/test/cypress/e2e/example16.cy.ts | 4 +- .../aurelia/test/cypress/e2e/example19.cy.ts | 6 +- .../aurelia/test/cypress/e2e/example20.cy.ts | 1063 ++++++++++++++--- .../aurelia/test/cypress/e2e/example24.cy.ts | 4 +- .../aurelia/test/cypress/e2e/example27.cy.ts | 4 +- .../aurelia/test/cypress/e2e/example28.cy.ts | 14 +- .../aurelia/test/cypress/e2e/example38.cy.ts | 16 +- .../aurelia/test/cypress/e2e/example39.cy.ts | 10 +- .../aurelia/test/cypress/e2e/example40.cy.ts | 20 +- .../aurelia/test/cypress/e2e/example43.cy.ts | 346 +++--- .../aurelia/test/cypress/e2e/example44.cy.ts | 22 +- .../aurelia/test/cypress/e2e/example45.cy.ts | 18 +- .../aurelia/test/cypress/e2e/example47.cy.ts | 4 +- .../aurelia/test/cypress/e2e/example48.cy.ts | 20 +- .../aurelia/test/cypress/e2e/example55.cy.ts | 2 +- .../aurelia/test/cypress/e2e/example56.cy.ts | 37 +- .../aurelia/test/cypress/e2e/example57.cy.ts | 6 +- demos/aurelia/test/cypress/support/drag.ts | 21 +- .../src/examples/slickgrid/Example05.tsx | 77 +- .../src/examples/slickgrid/example05.scss | 8 +- .../src/assets/locales/en/translation.json | 10 +- .../src/assets/locales/fr/translation.json | 10 +- .../src/examples/slickgrid/Example07.tsx | 2 - .../src/examples/slickgrid/Example14.tsx | 26 +- .../src/examples/slickgrid/Example15.tsx | 5 +- .../src/examples/slickgrid/Example20.tsx | 450 ++++--- .../src/examples/slickgrid/Example43.tsx | 5 +- .../src/examples/slickgrid/Example55.tsx | 2 +- .../src/examples/slickgrid/Example56.tsx | 4 +- .../src/examples/slickgrid/example14.scss | 8 +- .../src/examples/slickgrid/example20.scss | 8 +- demos/react/test/cypress/e2e/example03.cy.ts | 20 +- demos/react/test/cypress/e2e/example07.cy.ts | 10 +- demos/react/test/cypress/e2e/example10.cy.ts | 4 +- demos/react/test/cypress/e2e/example12.cy.ts | 4 +- demos/react/test/cypress/e2e/example14.cy.ts | 185 +-- demos/react/test/cypress/e2e/example15.cy.ts | 71 +- demos/react/test/cypress/e2e/example16.cy.ts | 4 +- demos/react/test/cypress/e2e/example19.cy.ts | 6 +- demos/react/test/cypress/e2e/example20.cy.ts | 1063 ++++++++++++++--- demos/react/test/cypress/e2e/example24.cy.ts | 4 +- demos/react/test/cypress/e2e/example27.cy.ts | 4 +- demos/react/test/cypress/e2e/example28.cy.ts | 14 +- demos/react/test/cypress/e2e/example38.cy.ts | 16 +- demos/react/test/cypress/e2e/example39.cy.ts | 10 +- demos/react/test/cypress/e2e/example40.cy.ts | 20 +- demos/react/test/cypress/e2e/example43.cy.ts | 348 +++--- demos/react/test/cypress/e2e/example44.cy.ts | 22 +- demos/react/test/cypress/e2e/example45.cy.ts | 18 +- demos/react/test/cypress/e2e/example47.cy.ts | 4 +- demos/react/test/cypress/e2e/example48.cy.ts | 20 +- demos/react/test/cypress/e2e/example55.cy.ts | 2 +- demos/react/test/cypress/e2e/example56.cy.ts | 37 +- demos/react/test/cypress/e2e/example57.cy.ts | 6 +- demos/react/test/cypress/support/drag.ts | 21 +- demos/vanilla/public/i18n/en.json | 10 +- demos/vanilla/public/i18n/fr.json | 10 +- demos/vanilla/src/examples/example03.ts | 1 + demos/vanilla/src/examples/example04.html | 10 +- demos/vanilla/src/examples/example04.ts | 2 +- demos/vanilla/src/examples/example08.html | 8 +- demos/vanilla/src/examples/example08.ts | 2 +- demos/vanilla/src/examples/example17.html | 8 +- demos/vanilla/src/examples/example17.scss | 12 +- demos/vanilla/src/examples/example17.ts | 3 +- demos/vanilla/src/examples/example26.ts | 1 + demos/vanilla/src/examples/example32.ts | 2 +- demos/vanilla/src/examples/example44.ts | 2 +- demos/vanilla/src/examples/example45.ts | 4 +- .../src/assets/locales/en/translation.json | 10 +- .../src/assets/locales/fr/translation.json | 10 +- demos/vue/src/components/Example07.vue | 2 - demos/vue/src/components/Example14.vue | 36 +- demos/vue/src/components/Example15.vue | 5 +- demos/vue/src/components/Example20.vue | 608 +++++----- demos/vue/src/components/Example38.vue | 3 +- demos/vue/src/components/Example43.vue | 4 +- demos/vue/src/components/Example55.vue | 2 +- demos/vue/src/components/Example56.vue | 4 +- demos/vue/test/cypress/e2e/example03.cy.ts | 16 +- demos/vue/test/cypress/e2e/example07.cy.ts | 10 +- demos/vue/test/cypress/e2e/example10.cy.ts | 4 +- demos/vue/test/cypress/e2e/example12.cy.ts | 4 +- demos/vue/test/cypress/e2e/example14.cy.ts | 185 +-- demos/vue/test/cypress/e2e/example15.cy.ts | 71 +- demos/vue/test/cypress/e2e/example16.cy.ts | 4 +- demos/vue/test/cypress/e2e/example19.cy.ts | 6 +- demos/vue/test/cypress/e2e/example20.cy.ts | 1063 ++++++++++++++--- demos/vue/test/cypress/e2e/example24.cy.ts | 4 +- demos/vue/test/cypress/e2e/example27.cy.ts | 4 +- demos/vue/test/cypress/e2e/example28.cy.ts | 14 +- demos/vue/test/cypress/e2e/example38.cy.ts | 16 +- demos/vue/test/cypress/e2e/example39.cy.ts | 10 +- demos/vue/test/cypress/e2e/example40.cy.ts | 20 +- demos/vue/test/cypress/e2e/example43.cy.ts | 346 +++--- demos/vue/test/cypress/e2e/example44.cy.ts | 22 +- demos/vue/test/cypress/e2e/example45.cy.ts | 18 +- demos/vue/test/cypress/e2e/example47.cy.ts | 4 +- demos/vue/test/cypress/e2e/example48.cy.ts | 20 +- demos/vue/test/cypress/e2e/example55.cy.ts | 2 +- demos/vue/test/cypress/e2e/example56.cy.ts | 37 +- demos/vue/test/cypress/e2e/example57.cy.ts | 44 +- demos/vue/test/cypress/support/drag.ts | 21 +- demos/vue/vite.config.mts | 8 +- docs/migrations/migration-to-11.x.md | 57 +- .../angular-slickgrid/src/assets/i18n/en.json | 10 +- .../angular-slickgrid/src/assets/i18n/fr.json | 10 +- .../src/demos/app.component.html | 2 +- .../src/demos/examples/example07.component.ts | 2 - .../demos/examples/example14.component.html | 10 +- .../demos/examples/example14.component.scss | 8 +- .../src/demos/examples/example14.component.ts | 12 +- .../src/demos/examples/example15.component.ts | 5 +- .../demos/examples/example20.component.html | 94 +- .../demos/examples/example20.component.scss | 8 +- .../src/demos/examples/example20.component.ts | 494 ++++---- .../src/demos/examples/example38.component.ts | 3 +- .../src/demos/examples/example43.component.ts | 5 +- .../src/demos/examples/example55.component.ts | 2 +- .../src/demos/examples/example56.component.ts | 4 +- .../test/cypress/e2e/example03.cy.ts | 16 +- .../test/cypress/e2e/example07.cy.ts | 10 +- .../test/cypress/e2e/example10.cy.ts | 4 +- .../test/cypress/e2e/example11.cy.ts | 2 +- .../test/cypress/e2e/example12.cy.ts | 4 +- .../test/cypress/e2e/example14.cy.ts | 185 +-- .../test/cypress/e2e/example15.cy.ts | 71 +- .../test/cypress/e2e/example16.cy.ts | 4 +- .../test/cypress/e2e/example19.cy.ts | 6 +- .../test/cypress/e2e/example20.cy.ts | 1063 ++++++++++++++--- .../test/cypress/e2e/example24.cy.ts | 4 +- .../test/cypress/e2e/example25.cy.ts | 2 +- .../test/cypress/e2e/example27.cy.ts | 4 +- .../test/cypress/e2e/example28.cy.ts | 14 +- .../test/cypress/e2e/example38.cy.ts | 16 +- .../test/cypress/e2e/example39.cy.ts | 10 +- .../test/cypress/e2e/example40.cy.ts | 20 +- .../test/cypress/e2e/example43.cy.ts | 346 +++--- .../test/cypress/e2e/example44.cy.ts | 22 +- .../test/cypress/e2e/example45.cy.ts | 18 +- .../test/cypress/e2e/example47.cy.ts | 4 +- .../test/cypress/e2e/example48.cy.ts | 20 +- .../test/cypress/e2e/example55.cy.ts | 2 +- .../test/cypress/e2e/example56.cy.ts | 37 +- .../test/cypress/e2e/example57.cy.ts | 6 +- .../test/cypress/support/drag.ts | 21 +- .../test/translateServiceStub.ts | 21 +- .../test/translaterServiceStub.ts | 23 +- .../src/assets/i18n/en/aurelia-slickgrid.json | 10 +- .../src/assets/i18n/fr/aurelia-slickgrid.json | 10 +- .../src/assets/locales/en/translation.json | 10 +- .../src/assets/locales/fr/translation.json | 10 +- .../core/__tests__/slickGrid-pinning.spec.ts | 16 + .../src/core/__tests__/slickGrid.spec.ts | 49 +- packages/common/src/core/slickGrid.ts | 53 +- packages/common/src/interfaces/index.ts | 1 - test/cypress/e2e/example04.cy.ts | 22 +- test/cypress/e2e/example05.cy.ts | 4 +- test/cypress/e2e/example06.cy.ts | 14 +- test/cypress/e2e/example07.cy.ts | 20 +- test/cypress/e2e/example11.cy.ts | 6 +- test/cypress/e2e/example13.cy.ts | 10 +- test/cypress/e2e/example17.cy.ts | 20 +- test/cypress/e2e/example20.cy.ts | 10 +- test/cypress/e2e/example21.cy.ts | 10 +- test/cypress/e2e/example26.cy.ts | 16 +- test/cypress/e2e/example27.cy.ts | 10 +- test/cypress/e2e/example28.cy.ts | 20 +- test/cypress/e2e/example32.cy.ts | 8 +- test/cypress/e2e/example33.cy.ts | 10 +- test/cypress/e2e/example34.cy.ts | 14 +- test/cypress/e2e/example36.cy.ts | 4 +- test/cypress/e2e/example37.cy.ts | 20 +- test/cypress/e2e/example44.cy.ts | 2 +- test/cypress/e2e/example45.cy.ts | 3 +- test/cypress/e2e/example46.cy.ts | 8 +- test/cypress/e2e/example47.cy.ts | 6 +- test/cypress/support/drag.ts | 8 +- 200 files changed, 7303 insertions(+), 3702 deletions(-) diff --git a/PINNING-POC-PROGRESS.md b/PINNING-POC-PROGRESS.md index da9ee941d3..209309392d 100644 --- a/PINNING-POC-PROGRESS.md +++ b/PINNING-POC-PROGRESS.md @@ -1,12 +1,14 @@ # Single-viewport pinning/stickiness POC — progress handoff -Last updated: 2026-09-08 (legacy runtime removal and Header Menu pinning structure updated; structural alias audit remains) +Last updated: 2026-09-09 (framework cell-menu parity and migration documentation reviewed; legacy runtime removal and structural alias audit remain) ## Goal Replace SlickGrid's multi-pane column/row architecture with an AG Grid-style docking model: -- exactly one live body viewport with one native horizontal scrollbar and one native vertical scrollbar; +- exactly one live body viewport with one native vertical scrollbar; ordinary grids use the + viewport for horizontal scrolling, while pinning/sticky grids use one dedicated docking + horizontal scrollbar; - one virtualized DOM row per data row; - each rendered row contains stable sibling left, center, and right cell regions; - permanent pinning and scroll-activated stickiness use the same internal docking resolver; @@ -54,11 +56,11 @@ remain visible regardless of the current pin state. The old bulk command ids and translation keys are documented only in the v11 migration guide; none recreates the old two-pane layout. -The POC has gone through visual hardening, selected Cypress migration, and removal of the -legacy pane options/interfaces and runtime branches. The common unit suite and focused -coverage checks pass; framework and browser validation remain follow-up work. The remaining -cleanup is limited to internal pane-shaped aliases, historical CSS variable names, and -intentional migration-facing terminology/documentation. +The POC has gone through visual hardening, selected Cypress migration, framework demo parity, +and removal of the legacy pane options/interfaces and runtime branches. The common unit suite +and focused coverage checks pass; framework browser validation remains follow-up work. The +remaining cleanup is limited to internal pane-shaped aliases, historical CSS variable names, +and intentional migration-facing terminology/documentation. ## Refactoring status and immediate follow-up @@ -122,7 +124,13 @@ documentation work may continue while the follow-up deletion audit is underway. sticky-row configuration. Once row docking is configured, the overlay remains a stable row layer even when no row is currently active. - rowspan z-index is problematic with new docking overlay, need further investigation -- using Cypress with `.scrollTo()` doesn't actually scroll the header titles & scrollbar, but the data is being scrolled +- [x] Restored the original `.slick-viewport` horizontal scroll element for ordinary grids. The + active horizontal scroll element always receives the generic `.slick-horizontal-scroller` + class: ordinary grids apply it to `.slick-viewport`, while grids with pinning/sticky docking + apply it to `.slick-docking-horizontal-scroller`. The docking-specific class remains available + for code that needs to identify the docking scrollbar. +- [x] Added `.slick-vertical-scroller` as the stable selector for the native vertical scroll + element. It currently points to the single `.slick-viewport` in all grid configurations. - [x] Pinning validation now uses the canonical `invalidColumnPinning*` and `skipPinningValidation` options. Requests that pin every visible column or whose permanent left/right bands consume the viewport are rejected and preserve the previous state. @@ -428,6 +436,13 @@ permanent-pinning controls. - The stable docking-region DOM is now implemented and the old 1000px header offset has been removed. Header/row/footer regions and per-row body regions should now be selected by their explicit left/center/right classes rather than by legacy pane roots. +- Horizontal scrolling is conditional: the active horizontal scroll element is always + exposed as `.slick-horizontal-scroller`. Ordinary grids apply that class to the legacy + `.slick-viewport.slick-viewport-top.slick-viewport-left`, while grids with permanent pinning + or sticky docking apply it to `.slick-docking-horizontal-scroller`. The docking scroller is + materialized lazily if pinning/sticky state is enabled after initialization. +- The native vertical scroll element is always exposed as `.slick-vertical-scroller` and remains + separate from the docking horizontal scroller when pinning or sticky docking is active. ## Files changed @@ -508,6 +523,22 @@ requirements are the two largest sources of variance. ## Known limitations and likely breakage +### Framework parity and recent Cypress regressions (2026-09-09) + +- Angular and React Example 20 no longer install the obsolete hover-selection handlers that + selected a row and called `preventDefault()` on mouse enter/leave. Those handlers were tied to + the old split-pane renderer and could interfere with opening a Cell Menu from a pinned Action + cell. Their behavior now matches Vue and Aurelia. +- The Example 20 cell-menu option callback uses each framework's grid service to update the + selected item. Angular no longer calls the removed SlickGrid `updateItem()` method directly. +- Angular Example 25's grid-menu regression was caused by a stale Cypress double-click pattern; + its menu-opening step now uses one click, matching Vue. The subsequent French metrics failure + was a cascade from the filters not being cleared. +- These framework/demo fixes preserve the single horizontal scroll-owner contract: use + `.slick-horizontal-scroller` for horizontal scrolling and `.slick-vertical-scroller` for + vertical scrolling. The more specific `.slick-docking-horizontal-scroller` remains available + for docking grids. + ### Latest visual fixes (2026-09-03) - The dedicated horizontal scrollbar now reserves its measured height from the live body viewport. Unlike the native scrollbar it replaces, the proxy is absolutely positioned and otherwise covered the last fully scrolled row. diff --git a/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json b/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json index b412eea6d6..600e68cdc1 100644 --- a/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json +++ b/demos/aurelia/src/assets/i18n/en/aurelia-slickgrid.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Clear all Filters", "CLEAR_ALL_GROUPING": "Clear all Grouping", "CLEAR_ALL_SORTING": "Clear all Sorting", - "CLEAR_PINNING": "Unfreeze Columns/Rows", + "CLEAR_PINNING": "Unpin Columns/Rows", "CLONE": "Clone", "COLLAPSE_ALL_GROUPS": "Collapse all Groups", "COLUMNS": "Columns", @@ -28,7 +28,10 @@ "FILTER_SHORTCUTS": "Filter Shortcuts", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items", "FORCE_FIT_COLUMNS": "Force fit columns", - "FREEZE_COLUMNS": "Freeze Columns", + "PIN_COLUMN": "Column Pinning", + "PIN_COLUMNS": "Pin Through Here", + "PIN_LEFT": "Pin Left", + "PIN_RIGHT": "Pin Right", "INVALID_FLOAT": "The number must be valid and have a maximum of {{maxDecimal}} decimals.", "GREATER_THAN": "Greater than", "GREATER_THAN_OR_EQUAL_TO": "Greater than or equal to", @@ -63,7 +66,8 @@ "SYNCHRONOUS_RESIZE": "Synchronous resize", "TOGGLE_FILTER_ROW": "Toggle Filter Row", "TOGGLE_PRE_HEADER_ROW": "Toggle Pre-Header Row", - "UNFREEZE_COLUMNS": "Unfreeze Columns", + "UNPIN_COLUMN": "Unpin Column", + "UNPIN_COLUMNS": "Unpin All Columns", "X_OF_Y_SELECTED": "# of % selected", "X_OF_Y_MASS_SELECTED": "{{x}} of {{y}} selected", "BILLING": { diff --git a/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json b/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json index c957bb46b8..832f0207b7 100644 --- a/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json +++ b/demos/aurelia/src/assets/i18n/fr/aurelia-slickgrid.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Supprimer tous les filtres", "CLEAR_ALL_GROUPING": "Supprimer tous les groupes", "CLEAR_ALL_SORTING": "Supprimer tous les tris", - "CLEAR_PINNING": "Dégeler les colonnes/rangées", + "CLEAR_PINNING": "Désépingler les colonnes/rangées", "CLONE": "Cloner", "COLLAPSE_ALL_GROUPS": "Réduire tous les groupes", "COLUMNS": "Colonnes", @@ -28,7 +28,10 @@ "FILTER_SHORTCUTS": "Raccourcis de filtre", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments", "FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes", - "FREEZE_COLUMNS": "Geler les colonnes", + "PIN_COLUMN": "Épinglage de colonnes", + "PIN_COLUMNS": "Épingler jusqu'ici", + "PIN_LEFT": "Épingler à gauche", + "PIN_RIGHT": "Épingler à droite", "GREATER_THAN": "Plus grand que", "GREATER_THAN_OR_EQUAL_TO": "Plus grand ou égal à", "GROUP_BY": "Grouper par", @@ -63,7 +66,8 @@ "SYNCHRONOUS_RESIZE": "Redimension synchrone", "TOGGLE_FILTER_ROW": "Basculer la ligne des filtres", "TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête", - "UNFREEZE_COLUMNS": "Dégeler les colonnes", + "UNPIN_COLUMN": "Désépingler la colonne", + "UNPIN_COLUMNS": "Désépingler toutes les colonnes", "X_OF_Y_SELECTED": "# de % sélectionnés", "X_OF_Y_MASS_SELECTED": "{{x}} de {{y}} sélectionnés", "BILLING": { diff --git a/demos/aurelia/src/examples/locales/fr.ts b/demos/aurelia/src/examples/locales/fr.ts index 134f9c5793..f6ec4bb391 100644 --- a/demos/aurelia/src/examples/locales/fr.ts +++ b/demos/aurelia/src/examples/locales/fr.ts @@ -19,6 +19,10 @@ export const localeFrench = { TEXT_EXPORT_TO_TEXT_FORMAT: 'Exporter en format texte', TEXT_FILTER_SHORTCUTS: 'Raccourcis de filtre', TEXT_FORCE_FIT_COLUMNS: 'Ajustement forcé des colonnes', + TEXT_PIN_COLUMN: 'Épinglage de colonnes', + TEXT_PIN_COLUMNS: "Épingler jusqu'ici", + TEXT_PIN_LEFT: 'Épingler à gauche', + TEXT_PIN_RIGHT: 'Épingler à droite', TEXT_GROUP_BY: 'Grouper par', TEXT_HIDE_COLUMN: 'Cacher la colonne', TEXT_LAST_UPDATE: 'Dernière mise à jour', @@ -32,10 +36,11 @@ export const localeFrench = { TEXT_SORT_ASCENDING: 'Trier par ordre croissant', TEXT_SORT_DESCENDING: 'Trier par ordre décroissant', TEXT_STARTS_WITH: 'Commence par', - TEXT_UNFREEZE_COLUMNS: 'Dégeler les colonnes', + TEXT_UNPIN_COLUMN: 'Désépingler la colonne', + TEXT_UNPIN_COLUMNS: 'Désépingler toutes les colonnes', TEXT_X_OF_Y_SELECTED: '# de % sélectionné', TEXT_X_OF_Y_MASS_SELECTED: '{{x}} de {{y}} sélectionnés', - TEXT_CLEAR_PINNING: 'Dégeler les colonnes/rangées', + TEXT_CLEAR_PINNING: 'Désépingler les colonnes/rangées', TEXT_EQUAL_TO: 'Égal à', TEXT_GREATER_THAN: 'Plus grand que', TEXT_GREATER_THAN_OR_EQUAL_TO: 'Plus grand ou égal à', diff --git a/demos/aurelia/src/examples/slickgrid/example07.ts b/demos/aurelia/src/examples/slickgrid/example07.ts index e5cc17a3e6..5803422afe 100644 --- a/demos/aurelia/src/examples/slickgrid/example07.ts +++ b/demos/aurelia/src/examples/slickgrid/example07.ts @@ -68,8 +68,6 @@ export class Example07 { ...this.gridOptions1, enableHeaderMenu: true, enableFiltering: true, - // frozenColumn: 2, - // frozenRow: 2, headerButton: { onCommand: (_e: any, args) => this.handleOnCommand(_e, args, 2), }, diff --git a/demos/aurelia/src/examples/slickgrid/example14.html b/demos/aurelia/src/examples/slickgrid/example14.html index d88969481c..612e4a4783 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.html +++ b/demos/aurelia/src/examples/slickgrid/example14.html @@ -47,14 +47,14 @@


-

Grid 2 (with Header Grouping & Frozen/Pinned Columns)

+

Grid 2 (with Header Grouping & Pinned Columns)

- -
diff --git a/demos/aurelia/src/examples/slickgrid/example14.scss b/demos/aurelia/src/examples/slickgrid/example14.scss index 6e31054384..e41265a0e7 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.scss +++ b/demos/aurelia/src/examples/slickgrid/example14.scss @@ -1,8 +1,8 @@ -/** You can change the pinned/frozen border styling through this css override */ +/** You can change the pinned/pinned border styling through this css override */ -.slick-row .slick-cell.frozen:last-child, -.slick-headerrow-column.frozen:last-child, -.slick-footerrow-column.frozen:last-child { +.slick-row .slick-cell.pinned:last-child, +.slick-headerrow-column.pinned:last-child, +.slick-footerrow-column.pinned:last-child { border-right: 1px solid #969696 !important; } diff --git a/demos/aurelia/src/examples/slickgrid/example14.ts b/demos/aurelia/src/examples/slickgrid/example14.ts index 746d24938f..1c536de1e2 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.ts +++ b/demos/aurelia/src/examples/slickgrid/example14.ts @@ -56,6 +56,7 @@ export class Example14 { preHeaderPanelHeight: 28, gridHeight: 275, gridWidth: 800, + rowHeight: 33, enablePdfExport: true, enableExcelExport: true, excelExportOptions: { @@ -104,15 +105,16 @@ export class Example14 { explicitInitialization: true, gridHeight: 275, gridWidth: 800, - frozenColumn: 2, + rowHeight: 33, + pinning: { columns: { left: 2 } }, enablePdfExport: true, enableExcelExport: true, excelExportOptions: { exportWithFormatter: false, }, externalResources: [new ExcelExportService(), new PdfExportService()], - gridMenu: { hideClearFrozenColumnsCommand: false }, - headerMenu: { hideFreezeColumnsCommand: false }, + gridMenu: { hideClearPinningCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, }; } @@ -134,8 +136,8 @@ export class Example14 { return mockDataset; } - setFrozenColumns2(frozenCols: number) { - this.gridObj2.setOptions({ frozenColumn: frozenCols }); + setPinnedColumns2(pinnedCols: number) { + this.gridObj2.setOptions({ pinning: { columns: { left: pinnedCols } } }); this.gridOptions2 = this.gridObj2.getOptions(); } diff --git a/demos/aurelia/src/examples/slickgrid/example15.ts b/demos/aurelia/src/examples/slickgrid/example15.ts index 5d4bf60e7d..2b3f7f42ab 100644 --- a/demos/aurelia/src/examples/slickgrid/example15.ts +++ b/demos/aurelia/src/examples/slickgrid/example15.ts @@ -193,10 +193,11 @@ export class Example15 { }, gridMenu: { hideForceFitButton: true, - hideClearFrozenColumnsCommand: false, + hideClearPinningCommand: false, }, headerMenu: { - hideFreezeColumnsCommand: false, + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, }, enablePagination: true, pagination: { diff --git a/demos/aurelia/src/examples/slickgrid/example20.html b/demos/aurelia/src/examples/slickgrid/example20.html index 1eecb2421b..3ba0e7f903 100644 --- a/demos/aurelia/src/examples/slickgrid/example20.html +++ b/demos/aurelia/src/examples/slickgrid/example20.html @@ -1,77 +1,81 @@ -
- -
- This example demonstrates the use of Pinned (aka frozen) Columns and/or Rows (Wiki docs) -
    -
  • Option to pin any number of columns (left only) or rows
  • -
  • Option to pin the rows at the bottom instead of the top (default)
  • -
  • You can also dynamically any of these options, through SlickGrid "setOptions()"
  • -
  • Possibility to change the styling of the line border between pinned columns/rows
  • -
-
- -
- -
-
- - - - - - - - - - + >Wiki docs)
-
- -
-
- - - -
+
+
+
+ +
+
+ - : ${ isFrozenBottom ? 'Bottom' : 'Top' } - +
+
+
+
+ + + + + + +
-

- Example 20: Pinned (frozen) Columns/Rows - - +

+ Example 20: Pinned Columns/Rows + +

+

- -

+
- - - diff --git a/demos/aurelia/src/examples/slickgrid/example20.scss b/demos/aurelia/src/examples/slickgrid/example20.scss index 6e31054384..e41265a0e7 100644 --- a/demos/aurelia/src/examples/slickgrid/example20.scss +++ b/demos/aurelia/src/examples/slickgrid/example20.scss @@ -1,8 +1,8 @@ -/** You can change the pinned/frozen border styling through this css override */ +/** You can change the pinned/pinned border styling through this css override */ -.slick-row .slick-cell.frozen:last-child, -.slick-headerrow-column.frozen:last-child, -.slick-footerrow-column.frozen:last-child { +.slick-row .slick-cell.pinned:last-child, +.slick-headerrow-column.pinned:last-child, +.slick-footerrow-column.pinned:last-child { border-right: 1px solid #969696 !important; } diff --git a/demos/aurelia/src/examples/slickgrid/example20.ts b/demos/aurelia/src/examples/slickgrid/example20.ts index d655caebce..6215b4e6db 100644 --- a/demos/aurelia/src/examples/slickgrid/example20.ts +++ b/demos/aurelia/src/examples/slickgrid/example20.ts @@ -1,9 +1,9 @@ +import { ExcelExportService } from '@slickgrid-universal/excel-export'; import { Editors, Filters, formatNumber, Formatters, - SlickEventHandler, type AureliaGridInstance, type Column, type ColumnEditorDualInput, @@ -11,337 +11,305 @@ import { type SlickGrid, } from 'aurelia-slickgrid'; import { showToast } from './utilities.js'; -import './example20.scss'; // provide custom CSS/SASS styling +import './example20.scss'; export class Example20 { aureliaGrid!: AureliaGridInstance; - columns: Column[] = []; gridObj!: SlickGrid; + columns: Column[] = []; gridOptions!: GridOption; - frozenColumnCount = 2; - frozenRowCount = 3; - hideSubTitle = false; - isFrozenBottom = false; dataset: any[] = []; - slickEventHandler: any; - + pinnedColumnCount = 2; + pinnedRightColumnCount = 1; + pinnedRowCount = 3; + hideSubTitle = false; + isPinnedBottom = false; + isSelectAllShownAsColumnTitle = false; + checkboxSelectorInstance: any; constructor() { this.defineGrid(); - this.slickEventHandler = new SlickEventHandler(); } - - aureliaGridReady(aureliaGrid: AureliaGridInstance) { - this.aureliaGrid = aureliaGrid; - this.gridObj = aureliaGrid && aureliaGrid.slickGrid; - - // with frozen (pinned) grid, in order to see the entire row being highlighted when hovering - // we need to do some extra tricks (that is because frozen grids use 2 separate div containers) - // the trick is to use row selection to highlight when hovering current row and remove selection once we're not - this.slickEventHandler.subscribe(this.gridObj.onMouseEnter, (event: Event) => this.colorizeHoveringRow(event, true)); - this.slickEventHandler.subscribe(this.gridObj.onMouseLeave, (event: Event) => this.colorizeHoveringRow(event, false)); - } - - colorizeHoveringRow(event: Event, isMouseEnter: boolean) { - const cell = this.gridObj.getCellFromEvent(event); - const rows = isMouseEnter ? [cell?.row ?? 0] : []; - this.gridObj.setSelectedRows(rows); // highlight current row - event.preventDefault(); - } - attached() { - // populate the dataset once the grid is ready - this.getData(); + this.dataset = Array.from({ length: 500 }, (_v, i) => ({ + id: i, + title: `Task ${i}`, + duration: `${Math.round(Math.random() * 100)}`, + percentComplete: Math.round(Math.random() * 100), + start: new Date(2009, 0, 1), + finish: new Date(2009, 4, 5), + completed: i % 5 === 0, + cost: i % 33 === 0 ? null : Math.random() * 10000, + cityOfOrigin: i % 2 ? 'Vancouver, BC, Canada' : 'Boston, MA, United States', + })); } - - detaching() { - // unsubscribe every SlickGrid subscribed event (or use the Slick.EventHandler) - this.slickEventHandler.unsubscribeAll(); + aureliaGridReady(g: AureliaGridInstance) { + this.aureliaGrid = g; + this.gridObj = g.slickGrid; } - - /* Define grid Options and Columns */ defineGrid() { + const dual = { + leftInput: { field: 'cost', type: 'float', decimal: 2, minValue: 0, maxValue: 50000 }, + rightInput: { field: 'duration', type: 'float', minValue: 0, maxValue: 100 }, + } as ColumnEditorDualInput; this.columns = [ - { - id: 'sel', - name: '#', - field: 'id', - minWidth: 40, - width: 40, - maxWidth: 40, - cannotTriggerInsert: true, - resizable: false, - unselectable: true, - }, - { - id: 'title', - name: 'Title', - field: 'title', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, - }, + { id: 'title', name: 'Title', field: 'title', width: 120, minWidth: 100, sortable: true, filterable: true }, { id: 'percentComplete', name: '% Complete', field: 'percentComplete', - resizable: false, - minWidth: 130, width: 140, - formatter: Formatters.percentCompleteBar, + minWidth: 130, type: 'number', + sortable: true, filterable: true, filter: { model: Filters.slider, operator: '>=' }, - sortable: true, + editor: { model: Editors.singleSelect, collection: Array.from({ length: 101 }, (_v, i) => ({ value: i, label: i })) }, }, { id: 'start', name: 'Start', field: 'start', - minWidth: 100, - width: 120, - filterable: true, + type: 'dateIso', sortable: true, + filterable: true, formatter: Formatters.dateIso, + filter: { model: Filters.compoundDate }, }, { id: 'finish', name: 'Finish', field: 'finish', - minWidth: 100, - width: 120, - filterable: true, + type: 'dateIso', sortable: true, + filterable: true, formatter: Formatters.dateIso, + filter: { model: Filters.compoundDate }, }, { - id: 'cost', - name: 'Cost | Duration', - field: 'cost', - formatter: this.costDurationFormatter.bind(this), - minWidth: 150, - width: 170, + id: 'completed', + name: 'Completed', + field: 'completed', sortable: true, - // filterable: true, - filter: { - model: Filters.compoundSlider, - }, - editor: { - model: Editors.dualInput, - // the DualInputEditor is of Type ColumnEditorDualInput and MUST include (leftInput/rightInput) in its params object - // in each of these 2 properties, you can pass any regular properties of a column editor - // and they will be executed following the options defined in each - params: { - leftInput: { - field: 'cost', - type: 'float', - decimal: 2, - minValue: 0, - maxValue: 50000, - placeholder: '< 50K', - errorMessage: 'Cost must be positive and below $50K.', - }, - rightInput: { - field: 'duration', - type: 'float', // you could have 2 different input type as well - minValue: 0, - maxValue: 100, - title: 'make sure Duration is withing its range of 0 to 100', - errorMessage: 'Duration must be between 0 and 100.', - - // Validator Option #1 - // You could also optionally define a custom validator in 1 or both inputs - /* - validator: (value, args) => { - let isValid = true; - let errorMsg = ''; - if (value < 0 || value > 120) { - isValid = false; - errorMsg = 'Duration MUST be between 0 and 120.'; - } - return { valid: isValid, msg: errorMsg }; - } - */ - }, - } as ColumnEditorDualInput, - - // Validator Option #2 (shared Validator) - this is the last alternative, option #1 (independent Validators) is still the recommended way - // You can also optionally use a common Validator (if you do then you cannot use the leftInput/rightInput validators at same time) - // to compare both values at the same time. - /* - validator: (values, args) => { - let isValid = true; - let errorMsg = ''; - if (values.cost < 0 || values.cost > 50000) { - isValid = false; - errorMsg = 'Cost MUST be between 0 and 50k.'; - } - if (values.duration < 0 || values.duration > 120) { - isValid = false; - errorMsg = 'Duration MUST be between 0 and 120.'; - } - if (values.cost < values.duration) { - isValid = false; - errorMsg = 'Cost can never be lower than its Duration.'; - } - return { valid: isValid, msg: errorMsg }; - } - */ - }, - }, - { - id: 'effortDriven', - name: 'Effort Driven', - field: 'effortDriven', - minWidth: 100, - width: 120, - formatter: Formatters.checkmarkMaterial, filterable: true, + formatter: Formatters.checkmarkMaterial, + editor: { model: Editors.checkbox }, filter: { + model: Filters.singleSelect, collection: [ { value: '', label: '' }, { value: true, label: 'True' }, { value: false, label: 'False' }, ], - model: Filters.singleSelect, }, - sortable: true, - }, - { - id: 'title1', - name: 'Title 1', - field: 'title1', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, }, { - id: 'title2', - name: 'Title 2', - field: 'title2', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, - }, - { - id: 'title3', - name: 'Title 3', - field: 'title3', - minWidth: 100, - width: 120, - filterable: true, + id: 'cost', + name: 'Cost | Duration', + field: 'cost', + formatter: this.costDurationFormatter.bind(this), sortable: true, + filter: { model: Filters.compoundSlider }, + editor: { model: Editors.dualInput, params: dual }, }, + { id: 'cityOfOrigin', name: 'City of Origin', field: 'cityOfOrigin', minWidth: 100, sortable: true, filterable: true }, { - id: 'title4', - name: 'Title 4', - field: 'title4', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, + id: 'action', + name: 'Action', + field: 'action', + width: 100, + maxWidth: 100, + excludeFromExport: true, + formatter: () => '
Action
', + cellMenu: { + commandTitle: 'Commands', + commandItems: [ + { command: 'command1', title: 'Command 1' }, + { command: 'command2', title: 'Command 2', itemUsabilityOverride: (args: any) => !args.dataContext.completed }, + { command: 'delete-row', title: 'Delete Row', itemVisibilityOverride: (args: any) => !args.dataContext.completed }, + { divider: true, command: '' }, + { command: 'help', title: 'Help' }, + { command: 'something', title: 'Disabled Command', disabled: true }, + ], + optionTitle: 'Change Complete Flag', + optionItems: [ + { option: true, title: 'True' }, + { option: false, title: 'False' }, + ], + }, }, ]; - this.gridOptions = { - autoResize: { - container: '#demo-container', - rightPadding: 10, - }, - gridWidth: 920, + autoResize: { container: '#demo-container', rightPadding: 10 }, + // Keep the left-pinned columns compact so two right-pinned columns fit + // beside the framework demo's route sidebar. + autoFitColumnsOnFirstLoad: false, enableCellNavigation: true, editable: true, autoEdit: true, + enableFiltering: true, enableExcelCopyBuffer: true, - frozenColumn: this.frozenColumnCount, - frozenRow: this.frozenRowCount, - // frozenBottom: true, // if you want to freeze the bottom instead of the top, you can enable this property - - // show both Frozen Columns in HeaderMenu & GridMenu, these are opt-in commands so they're disabled by default - gridMenu: { hideClearFrozenColumnsCommand: false }, - headerMenu: { hideFreezeColumnsCommand: false }, + enableExcelExport: true, + externalResources: [new ExcelExportService()], + enableSelection: true, + enableCheckboxSelector: true, + selectionOptions: { selectActiveRow: false }, + checkboxSelector: { + hideInColumnTitleRow: true, + hideInFilterHeaderRow: false, + name: 'Sel', + onExtensionRegistered: (i: any) => (this.checkboxSelectorInstance = i), + }, + pinning: { columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, rows: { top: [0, 1, 2] } }, + enableCellMenu: true, + cellMenu: { + onCommand: (_e: any, a: any) => this.executeCommand(a), + onOptionSelected: (_e: any, a: any) => { + if (a?.dataContext) { + a.dataContext.completed = a.item.option; + this.aureliaGrid?.gridService?.updateItem(a.dataContext); + } + }, + }, + enableContextMenu: true, + contextMenu: this.getContextMenuOptions(), + gridMenu: { hideClearPinningCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, }; } - - getData() { - // Set up some test columns. - const mockDataset: any[] = []; - for (let i = 0; i < 500; i++) { - mockDataset[i] = { - id: i, - title: 'Task ' + i, - cost: i % 33 === 0 ? null : Math.random() * 10000, - duration: i % 8 ? Math.round(Math.random() * 100) + '' : null, - percentComplete: Math.round(Math.random() * 100), - start: new Date(2009, 0, 1), - finish: new Date(2009, 4, 5), - effortDriven: i % 5 === 0, - title1: `Some Text ${Math.round(Math.random() * 25)}`, - title2: `Some Text ${Math.round(Math.random() * 25)}`, - title3: `Some Text ${Math.round(Math.random() * 25)}`, - title4: `Some Text ${Math.round(Math.random() * 25)}`, - }; - } - this.dataset = mockDataset; + getContextMenuOptions(): any { + const p = [ + { option: 0, title: 'Not Started (0%)' }, + { option: 50, title: 'Half Completed (50%)' }, + { option: 100, title: 'Completed (100%)' }, + ]; + return { + optionShownOverColumnIds: ['percentComplete'], + hideCloseButton: true, + dropSide: 'right', + optionTitle: 'Change Percent Complete', + optionItems: [...p, 'divider', { option: null, title: 'Sub-Options (demo)', subMenuTitle: 'Set Percent Complete', optionItems: p }], + commandItems: [ + { command: '', divider: true, positionOrder: 98 }, + { + command: 'export', + title: 'Exports', + positionOrder: 99, + commandItems: [ + { command: 'exports-txt', title: 'Text (tab delimited)' }, + { + command: 'sub-menu', + title: 'Excel', + subMenuTitle: 'available formats', + commandItems: [ + { command: 'exports-csv', title: 'Excel (csv)' }, + { command: 'exports-xlsx', title: 'Excel (xlsx)' }, + ], + }, + ], + }, + { + command: 'feedback', + title: 'Feedback', + positionOrder: 100, + commandItems: [ + { command: 'request-update', title: 'Request update from supplier' }, + 'divider', + { + command: 'sub-menu', + title: 'Contact Us', + subMenuTitle: 'contact us...', + commandItems: [ + { command: 'contact-email', title: 'Email us' }, + { command: 'contact-chat', title: 'Chat with us' }, + { command: 'contact-meeting', title: 'Book an appointment' }, + ], + }, + ], + }, + ], + onOptionSelected: (_e: any, a: any) => { + if (a?.dataContext) { + a.dataContext.percentComplete = a.item.option; + this.gridObj?.updateRow(a.row || 0); + } + }, + onCommand: (_e: any, a: any) => this.executeCommand(a), + }; } - - /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - changeFrozenColumnCount() { - if (this.gridObj && this.gridObj.setOptions) { - this.gridObj.setOptions({ - frozenColumn: this.frozenColumnCount, - }); + executeCommand(a: any) { + if (a.command === 'delete-row' && confirm(`Do you really want to delete row (${a.row + 1}) with "${a.dataContext.title}"?`)) { + this.aureliaGrid?.gridService?.deleteItemById(a.dataContext.id); + } else if (['command1', 'command2', 'help'].includes(a.command)) { + alert(a.item.title); + } else if (['exports-csv', 'exports-txt', 'exports-xlsx'].includes(a.command)) { + alert(`Exporting as ${a.item.title}`); + } else { + alert(`Command: ${a.command}`); } } - - /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - changeFrozenRowCount() { - if (this.gridObj && this.gridObj.setOptions) { - this.gridObj.setOptions({ - frozenRow: this.frozenRowCount, - }); - } + setPinnedColumns(left: number, right = this.pinnedRightColumnCount) { + const n = Math.max(0, Number(right) || 0); + this.gridObj?.setOptions({ + pinning: { + columns: { + left: left >= 0 ? ['_checkbox_selector', 'title', 'percentComplete'].slice(0, left + 1) : [], + right: ['cityOfOrigin', 'action'].slice(Math.max(0, 2 - n)), + }, + }, + }); + this.pinnedColumnCount = left; + this.pinnedRightColumnCount = n; } - - costDurationFormatter(_row: number, _cell: number, _value: any, _columnDef: Column, dataContext: any) { - const costText = this.isNullUndefinedOrEmpty(dataContext.cost) ? 'n/a' : formatNumber(dataContext.cost, 0, 2, false, '$', '', '.', ','); - let durationText = 'n/a'; - if (!this.isNullUndefinedOrEmpty(dataContext.duration) && dataContext.duration >= 0) { - durationText = `${dataContext.duration} ${dataContext.duration > 1 ? 'days' : 'day'}`; - } - return `${costText} | ${durationText}`; + changePinnedColumnCount() { + this.setPinnedColumns(this.pinnedColumnCount); } - - isNullUndefinedOrEmpty(data: any) { - return data === '' || data === null || data === undefined; + changePinnedRowCount() { + const rows = Array.from({ length: Math.max(0, this.pinnedRowCount) }, (_v, i) => i); + this.gridObj?.setOptions({ pinning: { rows: this.isPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } } }); + } + toggleRightPinning() { + this.setPinnedColumns(this.pinnedColumnCount, this.pinnedRightColumnCount > 0 ? 0 : 1); + } + togglePinnedBottomRows() { + this.isPinnedBottom = !this.isPinnedBottom; + this.changePinnedRowCount(); + } + toggleSelectAllRow() { + this.isSelectAllShownAsColumnTitle = !this.isSelectAllShownAsColumnTitle; + this.checkboxSelectorInstance?.setOptions({ + hideInColumnTitleRow: !this.isSelectAllShownAsColumnTitle, + hideInFilterHeaderRow: this.isSelectAllShownAsColumnTitle, + }); + } + setLargePinnedColumns() { + this.aureliaGrid?.gridStateService?.applyColumnLayout?.( + [ + { columnId: '_checkbox_selector', cssClass: 'slick-cell-checkboxsel', headerCssClass: '', width: 40 }, + { columnId: 'title', cssClass: '', headerCssClass: '', width: 220 }, + { columnId: 'percentComplete', cssClass: '', headerCssClass: '', width: 280 }, + { columnId: 'start', cssClass: '', headerCssClass: '', width: 150 }, + { columnId: 'finish', cssClass: '', headerCssClass: '', width: 280 }, + { columnId: 'completed', cssClass: '', headerCssClass: '', width: 180 }, + { columnId: 'cost', cssClass: '', headerCssClass: '', width: 220 }, + { columnId: 'cityOfOrigin', cssClass: '', headerCssClass: '', width: 180 }, + { columnId: 'action', cssClass: '', headerCssClass: '', width: 110 }, + ], + false, + false + ); + this.setPinnedColumns(2, this.pinnedRightColumnCount); } - onCellValidationError(_e: Event, args: any) { showToast(args.validationResults.msg, 'danger'); } - - setFrozenColumns(frozenCols: number) { - this.gridObj.setOptions({ frozenColumn: frozenCols }); - this.gridOptions = this.gridObj.getOptions(); - this.frozenColumnCount = frozenCols; - } - - /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - toggleFrozenBottomRows() { - if (this.gridObj && this.gridObj.setOptions) { - this.gridObj.setOptions({ - frozenBottom: !this.isFrozenBottom, - }); - this.isFrozenBottom = !this.isFrozenBottom; // toggle the variable - } + costDurationFormatter(_r: number, _c: number, _v: any, _d: Column, x: any) { + const cost = x.cost == null ? 'n/a' : formatNumber(x.cost, 0, 2, false, '$', '', '.', ','); + const duration = x.duration != null && x.duration >= 0 ? `${x.duration} ${x.duration > 1 ? 'days' : 'day'}` : 'n/a'; + return `${cost} | ${duration}`; } - toggleSubTitle() { this.hideSubTitle = !this.hideSubTitle; - const action = this.hideSubTitle ? 'add' : 'remove'; - document.querySelector('.subtitle')?.classList[action]('hidden'); - this.aureliaGrid.resizerService.resizeGrid(0); + document.querySelector('.subtitle')?.classList[this.hideSubTitle ? 'add' : 'remove']('hidden'); + this.aureliaGrid?.resizerService?.resizeGrid(0); } } diff --git a/demos/aurelia/src/examples/slickgrid/example38.ts b/demos/aurelia/src/examples/slickgrid/example38.ts index 26a733fa97..68197fb7a6 100644 --- a/demos/aurelia/src/examples/slickgrid/example38.ts +++ b/demos/aurelia/src/examples/slickgrid/example38.ts @@ -96,7 +96,8 @@ export class Example38 { enableSelection: true, enableGrouping: true, headerMenu: { - hideFreezeColumnsCommand: false, + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, }, presets: { // NOTE: pagination preset is NOT supported with infinite scroll diff --git a/demos/aurelia/src/examples/slickgrid/example43.ts b/demos/aurelia/src/examples/slickgrid/example43.ts index 9307b653d3..5c4c673c01 100644 --- a/demos/aurelia/src/examples/slickgrid/example43.ts +++ b/demos/aurelia/src/examples/slickgrid/example43.ts @@ -144,7 +144,6 @@ export class Example43 { autoResize: { container: '#demo-container', bottomPadding: 30, - rightPadding: 50, }, enableCellNavigation: true, enableColumnReorder: true, @@ -156,7 +155,7 @@ export class Example43 { autoEdit: true, editable: false, datasetIdPropertyName: 'employeeID', - frozenColumn: 0, + pinning: { columns: { left: 0 } }, gridHeight: 348, rowHeight: 30, dataView: { @@ -451,7 +450,7 @@ export class Example43 { newMetadata[row].columns[Number(col) + colDirIdx] = (this.metadata as any)[row].columns[col]; } } - this.aureliaGrid.slickGrid?.setOptions({ frozenColumn: newShowEmployeeId ? 0 : 1 }); + this.aureliaGrid.slickGrid?.setOptions({ pinning: { columns: { left: newShowEmployeeId ? 0 : 1 } } }); this.aureliaGrid.slickGrid?.updateColumnById('employeeID', { hidden: !newShowEmployeeId }); this.aureliaGrid.slickGrid?.updateColumns(); */ diff --git a/demos/aurelia/src/examples/slickgrid/example55.ts b/demos/aurelia/src/examples/slickgrid/example55.ts index 866e0ab26b..27a506af5c 100644 --- a/demos/aurelia/src/examples/slickgrid/example55.ts +++ b/demos/aurelia/src/examples/slickgrid/example55.ts @@ -83,7 +83,7 @@ export class Example55 { const owners = ['Alex', 'Priya', 'Mia', 'Sam', 'Chris']; const fragments = [ 'Refactor keyboard shortcut handling for better readability.', - 'Adjust frozen rows when view-model updates after grouping.', + 'Adjust pinned rows when view-model updates after grouping.', 'Improve screen-reader labels on grid menu actions.', 'Align batch editor validation with backend constraints.', 'Capture edge-case around hidden columns and row-span.', diff --git a/demos/aurelia/src/examples/slickgrid/example56.ts b/demos/aurelia/src/examples/slickgrid/example56.ts index 2d47583e09..e9bd506f48 100644 --- a/demos/aurelia/src/examples/slickgrid/example56.ts +++ b/demos/aurelia/src/examples/slickgrid/example56.ts @@ -80,7 +80,7 @@ export class Example56 { includeColumnWidth: true, }, rowHeight: 40, - frozenRow: 2, + pinning: { rows: { top: [0, 1] } }, gridHeight: 560, gridWidth: 1080, dataView: { @@ -111,7 +111,7 @@ export class Example56 { const statuses: Array = ['Todo', 'In Progress', 'Done']; const notesPool = [ 'Short note.', - 'Need to validate keyboard navigation and ensure screen reader output remains stable across frozen panes.', + 'Need to validate keyboard navigation and ensure screen reader output remains stable across pinned panes.', 'Review row height invalidation path when data changes quickly due to live updates from backend polling.', 'Longer QA note: validate scrolling behavior at top and bottom boundaries, compare rendered range against expected rows, and confirm no visual clipping for wrapped cells.', ]; diff --git a/demos/aurelia/test/cypress/e2e/example03.cy.ts b/demos/aurelia/test/cypress/e2e/example03.cy.ts index f488efd517..a08337a57c 100644 --- a/demos/aurelia/test/cypress/e2e/example03.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example03.cy.ts @@ -61,8 +61,8 @@ describe('Example 3 - Grid with Editors', () => { .click(); // change Title & Custom Title - cy.get('.editor-title > textarea').type('Task 2222'); - cy.get('.editor-title .btn-save').click(); + cy.get('.editor-title:visible > textarea').type('Task 2222'); + cy.get('.editor-title:visible .btn-save').click(); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 2222'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(3)`).should('contain', 'Task 2222'); @@ -98,7 +98,7 @@ describe('Example 3 - Grid with Editors', () => { `[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(11) > input.editor-checkbox.editor-effort-driven` ).check(); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); }); it('should dynamically add 2x new "Title" columns', () => { @@ -147,8 +147,8 @@ describe('Example 3 - Grid with Editors', () => { .click(); // change Title & Custom Title - cy.get('.editor-title > textarea').type('Task 0000'); - cy.get('.editor-title .btn-save').click(); + cy.get('.editor-title:visible > textarea').type('Task 0000'); + cy.get('.editor-title:visible .btn-save').click(); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 0000'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(3)`).should('contain', 'Task 0000'); @@ -180,7 +180,7 @@ describe('Example 3 - Grid with Editors', () => { .blur(); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(10)`).click(); // the blur seems to not always work, so just click on another cell cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(11)`).find('.mdi-check.checkmark-icon'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); }); it('should be able to filter and search "Task 2222" in the new column and expect only 1 row showing in the grid', () => { @@ -258,7 +258,7 @@ describe('Example 3 - Grid with Editors', () => { }); it('should open the "Prerequisites" Filter and expect to have Task 500 & 101 in the Filter', () => { - cy.get('div.ms-filter.filter-prerequisites').trigger('click', { force: true }); + cy.get('div.ms-filter.filter-prerequisites').trigger('click'); cy.get('.ms-drop').find('span:nth(1)').contains('Task 101'); @@ -270,7 +270,7 @@ describe('Example 3 - Grid with Editors', () => { it('should open the "Prerequisites" Editor and expect to have Task 100 & 101 in the Editor', () => { cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(12)`) .should('contain', '') - .click({ force: true }); + .click(); cy.get('.ms-drop').find('span:nth(1)').contains('Task 101'); @@ -284,11 +284,11 @@ describe('Example 3 - Grid with Editors', () => { }); it('should delete the last item "Task 101" and expect it to be removed from the Filter', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('right').wait(50); + cy.get('.slick-horizontal-scroller').scrollTo('right').wait(50); cy.get('[data-test="delete-item-btn"]').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).wait(50); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0).wait(50); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 100'); cy.get('div.ms-filter.filter-prerequisites').trigger('click'); diff --git a/demos/aurelia/test/cypress/e2e/example07.cy.ts b/demos/aurelia/test/cypress/e2e/example07.cy.ts index 048c260d9b..446c389b24 100644 --- a/demos/aurelia/test/cypress/e2e/example07.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example07.cy.ts @@ -75,7 +75,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should go over the last "Column J" and expect to find the button to have the disabled class and clicking it should not turn the negative numbers to red neither expect console log after clicking the disabled button', () => { - cy.get('#grid7-1 .slick-viewport-top.slick-viewport-left').scrollTo('right').wait(50); + cy.get('#grid7-1 .slick-horizontal-scroller').scrollTo('right').wait(50); cy.get('#grid7-1 .slick-header-columns') .children('.slick-header-column:nth(9)') @@ -109,7 +109,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should resize 1st column and make it wider', () => { - cy.get('#grid7-1 .slick-viewport-top.slick-viewport-left').scrollTo('left').wait(50); + cy.get('#grid7-1 .slick-horizontal-scroller').scrollTo('left').wait(50); cy.get('#grid7-1 .slick-header-columns').children('.slick-header-column:nth(0)').should('contain', 'Resize me!'); @@ -220,7 +220,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should go over the last "Column J" and expect to find the button to have the disabled class and clicking it should not turn the negative numbers to red neither expect console log after clicking the disabled button', () => { - cy.get('#grid7-2 .slick-viewport-top.slick-viewport-left').scrollTo('right').wait(50); + cy.get('#grid7-2 .slick-horizontal-scroller').scrollTo('right').wait(50); cy.get('#grid7-2 .slick-header-columns') .children('.slick-header-column:nth(9)') @@ -254,7 +254,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should resize 1st column and make it wider', () => { - cy.get('#grid7-2 .slick-viewport-top.slick-viewport-left').scrollTo('left').wait(50); + cy.get('#grid7-2 .slick-horizontal-scroller').scrollTo('left').wait(50); cy.get('#grid7-2 .slick-header-columns').children('.slick-header-column:nth(0)').should('contain', 'Resize me!'); @@ -364,7 +364,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should expect first few items of "Column C" to be negative numbers and be red', () => { - cy.get('#grid7-2 .slick-viewport-top.slick-viewport-left').scrollTo('top').wait(50); + cy.get('#grid7-2 .slick-vertical-scroller').scrollTo('top').wait(50); cy.get('#grid7-2 .slick-row').each(($row, index) => { if (index > 10) { diff --git a/demos/aurelia/test/cypress/e2e/example10.cy.ts b/demos/aurelia/test/cypress/e2e/example10.cy.ts index f65cc30eeb..fb67e74664 100644 --- a/demos/aurelia/test/cypress/e2e/example10.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example10.cy.ts @@ -389,14 +389,14 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should scroll to the bottom of 2nd Grid and still have 5 rows (Task 1,Task 3,Task 12,Task 13,Task 522) selected and find 2 row selected because we now have 2 rows that got rendered (first and last)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(10); + cy.get('@grid2').find('.slick-vertical-scroller').scrollTo('bottom').wait(10); cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should have 2 rows (Task 3,Task 13) selected in 2nd grid after typing in a search filter (3)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); cy.get('@grid2').find('.filter-title').type('3'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(10); + cy.get('@grid2').find('.slick-vertical-scroller').scrollTo('top').wait(10); cy.get('@grid2').find('.slick-row').should('not.have.length', 0); cy.wait(50); diff --git a/demos/aurelia/test/cypress/e2e/example12.cy.ts b/demos/aurelia/test/cypress/e2e/example12.cy.ts index ec11ba1405..7ed1cab4e7 100644 --- a/demos/aurelia/test/cypress/e2e/example12.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example12.cy.ts @@ -229,7 +229,7 @@ describe('Example 12: Localization (i18n)', () => { it('should scroll to bottom of the grid then select "Task 4"', () => { cy.get('#slickGridContainer-grid12').as('grid12'); - cy.get('@grid12').find('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(10); + cy.get('@grid12').find('.slick-vertical-scroller').scrollTo('bottom').wait(10); cy.get('#grid12').contains('Task 4').parent().children('.slick-cell-checkboxsel').find('input[type=checkbox]').click({ force: true }); @@ -259,7 +259,7 @@ describe('Example 12: Localization (i18n)', () => { cy.get('.grid-canvas').find('.slick-row').should('be.visible'); - cy.get('@grid12').find('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(10); + cy.get('@grid12').find('.slick-vertical-scroller').scrollTo('top').wait(10); cy.get('@grid12').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 1); diff --git a/demos/aurelia/test/cypress/e2e/example14.cy.ts b/demos/aurelia/test/cypress/e2e/example14.cy.ts index 91f7db34b6..32b529fd3b 100644 --- a/demos/aurelia/test/cypress/e2e/example14.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example14.cy.ts @@ -1,5 +1,4 @@ describe('Example 14 - Column Span & Header Grouping', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const fullPreTitles = ['', 'Common Factor', 'Period', 'Analysis']; const fullTitles = ['#', 'Title', 'Duration', 'Start', 'Finish', '% Complete', 'Effort Driven']; @@ -20,17 +19,18 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should have a frozen grid on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('#grid2').find('[data-row=0]').should('have.length', 2); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); + it('should have a pinned grid on page load with 3 pinned columns and 4 scrolling columns', () => { + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell`).should('have.length', 3); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell`).should('have.length', 4); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(2)').should('contain', '5 days'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(0)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid again', () => { @@ -45,17 +45,18 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.get('[data-test="remove-frozen-column-button"]').click(); + it('should click on the "Remove Pinned Columns" button to switch to a regular grid without pinned columns', () => { + cy.get('[data-test="remove-pinned-column-button"]').click(); - cy.get('#grid2').find('[data-row=0]').should('have.length', 1); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-cell`).should('have.length', 7); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`${firstRow} .slick-cell:nth(3)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-cell:nth(4)`).should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid once again', () => { @@ -70,19 +71,20 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { - cy.contains('Set 3 Frozen Columns').click({ force: true }); + it('should click on the "Set 3 Pinned Columns" button to pin 3 columns and leave 4 scrolling columns', () => { + cy.contains('Set 3 Pinned Columns').click({ force: true }); - cy.get('#grid2').find('[data-row=0]').should('have.length', 2); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell`).should('have.length', 3); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell`).should('have.length', 4); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(0)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); it('should have still exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -97,56 +99,49 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { + it('should click on the Grid Menu command "Unpin Columns/Rows" to switch to a regular grid without pinned columns', () => { cy.get('#grid2').find('button.slick-grid-menu-button').click({ force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + cy.contains('Unpin Columns/Rows').click({ force: true }); - cy.get('#grid2').find('[data-row=0]').should('have.length', 1); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-cell`).should('have.length', 7); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`${firstRow} .slick-cell:nth(3)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-cell:nth(4)`).should('contain', '01/05/2009'); }); - it('should reapply 3 frozen columns on 2nd grid', () => { - cy.contains('Set 3 Frozen Columns').click({ force: true }); + it('should reapply 3 pinned columns on 2nd grid', () => { + cy.contains('Set 3 Pinned Columns').click({ force: true }); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 3); - - cy.get('#grid2') - .find('.slick-pane-right .slick-header.slick-header-right .slick-header-columns .slick-header-column') - .should('have.length', 4); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-column-pinned-left').should('have.length', 3); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column:not(.slick-column-pinned-left)').should( + 'have.length', + 4 + ); }); - it('should be able to "Unfreeze Columns" from header menu', () => { + it('should be able to "Unpin All Columns" from header menu', () => { cy.get('#grid2') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') + .find('.slick-header.slick-header-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') .click(); - cy.get('.slick-header-menu .slick-menu-command-list') - .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Unfreeze Columns') - .click(); + cy.get('.slick-header-menu .slick-menu-command-list').should('be.visible').find('[data-command="pin-column"]').click(); + cy.get('.slick-submenu [data-command="unpin-columns"]').should('contain', 'Unpin All Columns').click(); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 7); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should be able to "Freeze Columns" back from header menu', () => { + it('should be able to "Pin Through Here" back from header menu', () => { cy.get('#grid2') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') + .find('.slick-header.slick-header-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') @@ -154,57 +149,72 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') .click(); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 3); + cy.get('.slick-submenu [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); - cy.get('#grid2') - .find('.slick-pane-right .slick-header.slick-header-right .slick-header-columns .slick-header-column') - .should('have.length', 4); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-column-pinned-left').should('have.length', 3); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column:not(.slick-column-pinned-left)').should( + 'have.length', + 4 + ); }); describe('Basic Key Navigations', () => { it('should remove any freezing', () => { - cy.get('[data-test="remove-frozen-column-button"]').click(); + cy.get('[data-test="remove-pinned-column-button"]').click(); + + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); + }); + + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and land on a full colspan', () => { + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}'); + cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + }); + + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and land on a colspan of 3', () => { + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}'); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + }); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 7); + it('should navigate PageUp twice from a colspan of 3 back to the starting colspan of 3', () => { + cy.get('#grid1 [data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}{pageup}'); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); - cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); - cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}'); - cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); }); @@ -219,7 +229,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { .contains(/(true|false)+$/); cy.get('#grid1') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') + .find('.slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') @@ -232,6 +242,11 @@ describe('Example 14 - Column Span & Header Grouping', () => { .should('contain', 'Hide Column') .click(); + // The colspan still spans the logical Duration/Start/Finish range, but + // the hidden Finish track is zero-width. The host cell must remain + // rendered and visibly cover the two remaining columns. + cy.get('#grid1 [data-row=1] .slick-cell.l1.r3').should('contain', '5 days').and('be.visible'); + // goto right cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').click(); cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0.active').should('contain', 'Task 1').type('{rightArrow}'); @@ -272,7 +287,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { .contains(/(true|false)+$/); cy.get('#grid1') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') + .find('.slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') diff --git a/demos/aurelia/test/cypress/e2e/example15.cy.ts b/demos/aurelia/test/cypress/e2e/example15.cy.ts index 2792dbeac6..5a7e09da9d 100644 --- a/demos/aurelia/test/cypress/e2e/example15.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example15.cy.ts @@ -1,7 +1,6 @@ import { format } from '@formkit/tempo'; describe('Example 15: Grid State & Presets using Local Storage', () => { - const GRID_ROW_HEIGHT = 35; const fullEnglishTitles = ['', 'Title', 'Description', 'Duration', '% Complete', 'Start', 'Completed']; beforeEach(() => { @@ -12,16 +11,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.saveLocalStorage(); }); - it('should display Example title', () => { - cy.visit(`${Cypress.config('baseUrl')}/example15`); + it('should display Example title from a clean local-storage state', () => { + cy.visit(`${Cypress.config('baseUrl')}/example15`, { + onBeforeLoad: (window) => window.localStorage.clear(), + }); cy.get('h2').should('contain', 'Example 15: Grid State & Presets using Local Storage'); - - cy.clearLocalStorage(); - cy.get('[data-test=reset-button]').click(); - }); - - it('should reload the page', () => { - cy.reload().wait(50); }); it('should have exact Column Titles in the grid', () => { @@ -348,7 +342,7 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { .each(($child, index) => expect($child.find('.slick-column-name').text()).to.eq(expectedTitles[index])); }); - it('should be able to freeze "Description" column', () => { + it('should be able to pin "Description" column', () => { cy.get('.slick-header-columns') .children('.slick-header-column:nth(1)') .trigger('mouseover') @@ -359,10 +353,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Geler les colonnes') - .click(); + .find('[data-command="pin-column"]') + .should('contain', 'Épinglage de colonnes') + .trigger('mouseover'); + + cy.get('.slick-submenu:visible [data-command="pin-columns"]').should('contain', "Épingler jusqu'ici").click(); }); it('should reload the page', () => { @@ -423,10 +418,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { }); }); - it('should have a persisted frozen column after "Description" and a grid with 4 containers on page load with 2 columns on the left and 3 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]').children().should('have.length', 3); + it('should have a persisted pinned column after "Description" with 2 pinned and 3 scrolling columns', () => { + const firstRow = '#grid15 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells > .slick-cell`).should('have.length', 2); + cy.get(`${firstRow} .slick-scrolling-cells > .slick-cell`).should('have.length', 3); }); it('should click on the reset button and have exact Column Titles position as in beginning', () => { @@ -453,7 +449,7 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); }); - it('should be able to freeze "Description" 3rd column', () => { + it('should be able to pin "Description" 3rd column', () => { cy.get('.slick-header-columns') .children('.slick-header-column:nth(2)') .trigger('mouseover') @@ -464,10 +460,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') - .click(); + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') + .trigger('mouseover'); + + cy.get('.slick-submenu:visible [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); }); it('should swap "Duration" and "% Complete" columns', () => { @@ -481,7 +478,7 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); }); - it('should be able to freeze "% Complete" and expect 4th column to be freezed', () => { + it('should be able to pin "% Complete" and expect 4th column to be pinned', () => { cy.get('.slick-header-columns') .children('.slick-header-column:nth(3)') .trigger('mouseover') @@ -492,16 +489,18 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') - .click(); + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') + .trigger('mouseover'); + + cy.get('.slick-submenu:visible [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); }); - it('should have a persisted frozen column after "Description" and a grid with 4 containers on page load with 2 columns on the left and 3 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 4); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]').children().should('have.length', 3); + it('should have a persisted pinned column after "Description" with 4 pinned and 3 scrolling columns', () => { + const firstRow = '#grid15 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells > .slick-cell`).should('have.length', 4); + cy.get(`${firstRow} .slick-scrolling-cells > .slick-cell`).should('have.length', 3); }); describe('Filter Shortcuts', () => { @@ -599,9 +598,9 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { expect(Number($span.text())).to.gt(80); }); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).contains('desc'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).contains('desc'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(2)`).contains('desc'); + cy.get('#grid15 .slick-row[data-row="0"] .slick-cell.l2').contains('desc'); + cy.get('#grid15 .slick-row[data-row="1"] .slick-cell.l2').contains('desc'); + cy.get('#grid15 .slick-row[data-row="2"] .slick-cell.l2').contains('desc'); }); }); }); diff --git a/demos/aurelia/test/cypress/e2e/example16.cy.ts b/demos/aurelia/test/cypress/e2e/example16.cy.ts index d9eb35e061..87d69f920c 100644 --- a/demos/aurelia/test/cypress/e2e/example16.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example16.cy.ts @@ -39,7 +39,7 @@ describe('Example 16 - Row Move & Checkbox Selector Selector Plugins', () => { }); it('should expect the row to have moved to another row index', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 0'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 1'); @@ -76,7 +76,7 @@ describe('Example 16 - Row Move & Checkbox Selector Selector Plugins', () => { cy.get('@moveIconTask5').trigger('mousemove', 'bottomRight').trigger('mouseup', 'bottomRight', { which: 1, force: true }); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 0'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 1'); diff --git a/demos/aurelia/test/cypress/e2e/example19.cy.ts b/demos/aurelia/test/cypress/e2e/example19.cy.ts index babe74c51a..d17f2c9055 100644 --- a/demos/aurelia/test/cypress/e2e/example19.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example19.cy.ts @@ -80,7 +80,7 @@ describe('Example 19 - Row Detail View', () => { cy.get('@detailContainer').find('[data-test=delete-btn]').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('#grid19') .find('.slick-row') @@ -111,7 +111,7 @@ describe('Example 19 - Row Detail View', () => { cy.get('[data-test=collapse-all-btn]').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('#grid19').find('.dynamic-cell-detail .innerDetailView_0 .container_0').should('not.exist'); @@ -175,7 +175,7 @@ describe('Example 19 - Row Detail View', () => { cy.get('#grid19').find('.slick-header-column:nth(1)').find('.slick-sort-indicator-asc').should('have.length', 1); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('#grid19').find('.dynamic-cell-detail .innerDetailView_0 .container_0').should('not.exist'); diff --git a/demos/aurelia/test/cypress/e2e/example20.cy.ts b/demos/aurelia/test/cypress/e2e/example20.cy.ts index 11481d0d77..91c8f4b076 100644 --- a/demos/aurelia/test/cypress/e2e/example20.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example20.cy.ts @@ -1,102 +1,172 @@ -describe('Example 20 - Frozen Grid', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows - - const fullTitles = [ - '#', - 'Title', - '% Complete', - 'Start', - 'Finish', - 'Cost | Duration', - 'Effort Driven', - 'Title 1', - 'Title 2', - 'Title 3', - 'Title 4', - ]; +describe('Example 20 - Pinned Grid', () => { + before(() => { + // The framework demos include a 250px route sidebar. Use enough width for + // the two-column pinning scenario to remain valid with that sidebar. + cy.viewport(1440, 900); + }); + + const withTitleRowTitles = ['Sel', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const withoutTitleRowTitles = ['', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const getCell = (rowIndex: number, columnIndex: number) => + cy.get(`#grid20 .slick-row[data-row="${rowIndex}"] .slick-cell.l${columnIndex}`); + const setRightPinning = (count: number) => { + cy.get('.pinned-right-column-count').clear().type(`${count}`); + cy.get('[data-test="set-pinned-right-column"]').click(); + }; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example20`); - cy.get('h2').should('contain', 'Example 20: Pinned (frozen) Columns/Rows'); + cy.get('h2').should('contain', 'Example 20: Pinned Columns/Rows'); }); it('should have exact column titles on 1st grid', () => { cy.get('#grid20') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + }); + + it('should hide sub-title to provide more space for the grid', () => { + cy.get('[data-test="toggle-subtitle"]').click(); }); it('should have exact Column Header Titles in the grid', () => { cy.get('#grid20') - .find('.slick-header-columns:nth(0)') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns:nth(0) .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should have a frozen grid with 4 containers on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + it('should have three top-pinned rows and left/center/right cells on page load', () => { + const row0 = '#grid20 .slick-row[data-row="0"]'; + + // Pinning uses one row node split into regions and a single docking overlay; + // it no longer duplicates rows into legacy left/right pinned canvases. + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + cy.get(`${row0} .slick-pinned-left-cells > .slick-cell`).should('have.length', 3); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell`).should('have.length', 5); + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 1); + + cy.get(`${row0} .slick-pinned-left-cells > .slick-cell:nth(0)`).should('contain', ''); + cy.get(`${row0} .slick-pinned-left-cells > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(0)`).should('contain', '2009-01-01'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(1)`).should('contain', '2009-05-05'); + + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell:nth(0) .cell-menu-dropdown`).should('contain', 'Action'); + }); + + it('should pin multiple columns on the right and render matching header and filter regions', () => { + setRightPinning(2); + + const row0 = '#grid20 .slick-row[data-row="0"]'; + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 2); + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell.l7`).should('contain', 'Boston'); + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell.l8 .cell-menu-dropdown`).should('contain', 'Action'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid20 .slick-header-columns-right .slick-header-column').should('have.length', 2); + cy.get('#grid20 .slick-header-columns-right [data-id="cityOfOrigin"]').should('contain', 'City of Origin'); + cy.get('#grid20 .slick-header-columns-right [data-id="action"]').should('contain', 'Action'); + cy.get('#grid20 .slick-headerrow-columns-right .slick-headerrow-column').should('have.length', 2); + cy.get('#grid20 .slick-headerrow-columns-right .slick-headerrow-column.l7 input').should('exist'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + setRightPinning(1); + }); + + it('should keep multiple right-pinned columns fixed while scrolling', () => { + setRightPinning(2); + // The default fixture fits in the viewport, so use the demo's wide layout + // to exercise an actual horizontal scroll rather than a no-op scroll. + cy.get('[data-test="set-large-pinned-columns"]').click(); + const actionCell = '#grid20 .slick-row[data-row="10"] .slick-pinned-right-cells .slick-cell.l8'; + + cy.get(actionCell).then(($cell) => { + const rightEdge = $cell[0].getBoundingClientRect().right; + cy.get('#grid20 .slick-horizontal-scroller').scrollTo('right'); + cy.get(actionCell).should(($scrolledCell) => { + expect(Math.abs($scrolledCell[0].getBoundingClientRect().right - rightEdge)).to.be.lessThan(2); + }); + }); + + cy.get('#grid20 .slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + // Restore the initial fixture after exercising the wide layout so the + // following serial tests do not inherit resized columns. + cy.visit(`${Cypress.config('baseUrl')}/example20`); + }); + + it('should resize columns while left and right pinning are active', () => { + cy.visit(`${Cypress.config('baseUrl')}/example20`); + + const resizeColumn = (columnSelector: string, pinClass: string) => { + cy.get(columnSelector).should('have.class', pinClass); + cy.get(`${columnSelector} .slick-resizable-handle`) + .should('exist') + .then(($handle) => { + const header = $handle.closest('.slick-header-column')[0] as HTMLElement; + const initialWidth = header.getBoundingClientRect().width; + + cy.wrap($handle).trigger('mousedown', { which: 1, pageX: 100, clientX: 100, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 125, clientX: 125, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 150, clientX: 150, force: true }); + cy.get('body').trigger('mouseup', { which: 1, pageX: 150, clientX: 150, force: true }); + + cy.get(columnSelector).should(($updatedHeader) => { + expect($updatedHeader[0].getBoundingClientRect().width).to.be.greaterThan(initialWidth); + }); + }); + }; + + resizeColumn('#grid20 .slick-header-columns-left [data-id="title"]', 'slick-column-pinned-left'); + + // Use City of Origin because Action has a maxWidth of 100px and would refuse a wider resize. + setRightPinning(2); + resizeColumn('#grid20 .slick-header-columns-right [data-id="cityOfOrigin"]', 'slick-column-pinned-right'); + + // Keep the following serial tests on the demo's default configuration. + cy.visit(`${Cypress.config('baseUrl')}/example20`); }); - it('should hide "Title" column from Grid Menu and expect last frozen column to be "% Complete"', () => { - const newColumnList = [ - '#', - '% Complete', - 'Start', - 'Finish', - 'Cost | Duration', - 'Effort Driven', - 'Title 1', - 'Title 2', - 'Title 3', - 'Title 4', - ]; + it('should disable and re-enable right pinning through the numeric grid control', () => { + setRightPinning(0); + cy.get('#grid20 .slick-row[data-row="0"] .slick-pinned-right-cells').should('exist'); + cy.get('#grid20 .slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell').should('not.exist'); + cy.get('#grid20 .slick-header-columns-right .slick-header-column').should('not.exist'); + + setRightPinning(1); + cy.get('#grid20 .slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell.l8').should('contain', 'Action'); + cy.get('#grid20 .slick-header-columns-right [data-id="action"]').should('contain', 'Action'); + }); + + it('should hide "Title" column from Grid Menu and expect last pinned column to be "% Complete"', () => { + const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const row0 = '#grid20 .slick-row[data-row="0"]'; cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); cy.get('#grid20') .get('.slick-grid-menu:visible') .find('.slick-column-picker-list') - .children('li:visible:nth(1)') + .children('li:visible:nth(0)') .children('label') .should('contain', 'Title') .click({ force: true }); cy.get('#grid20') - .find('.slick-header-columns') - .children() + .find('.slick-header-columns .slick-header-column') .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 2 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 2); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 5); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').children().should('have.length', 1); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(0)`).should('contain', '2009-01-01'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(1)`).should('contain', '2009-05-05'); }); - it('should show again "Title" column from Grid Menu and expect last frozen column to still be "% Complete"', () => { + it('should show again "Title" column from Grid Menu and expect last pinned column to still be "% Complete"', () => { cy.get('#grid20') .get('.slick-grid-menu:visible') .find('.slick-column-picker-list') - .children('li:visible:nth(1)') + .children('li:visible:nth(0)') .children('label') .should('contain', 'Title') .click({ force: true }); @@ -104,77 +174,60 @@ describe('Example 20 - Frozen Grid', () => { cy.get('#grid20').get('.slick-grid-menu:visible').find('.close').click({ force: true }); cy.get('#grid20') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 5); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').children().should('have.length', 1); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + cy.get('.slick-scrolling-cells .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-scrolling-cells .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should hide "Title" column from Header Menu and expect last frozen column to be "% Complete"', () => { - const newColumnList = [ - '#', - '% Complete', - 'Start', - 'Finish', - 'Cost | Duration', - 'Effort Driven', - 'Title 1', - 'Title 2', - 'Title 3', - 'Title 4', - ]; + it('should hide "Title" column from Header Menu and expect last pinned column to be "% Complete"', () => { + const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; - cy.get('#grid20') - .find('.slick-header-column:nth(1)') - .trigger('mouseover') - .children('.slick-header-menu-button') - .should('be.hidden') - .invoke('show') - .click(); + cy.get('#grid20').find('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(8)') + .children('.slick-menu-item:nth-of-type(9)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); cy.get('#grid20') - .find('.slick-header-columns') - .children() + .find('.slick-header-columns .slick-header-column') .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 2 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 2); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 5); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').children().should('have.length', 1); + + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + it('should toggle right pinned column and expect only 2 left/center containers to be visible', () => { + cy.get('[data-test="toggle-pinned-right"]').click(); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 2); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').should('exist').and('not.have.class', 'slick-pinned-right-cells-active'); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell').should('not.exist'); }); - it('should show again "Title" column from Column Picker and expect last frozen column to still be "% Complete"', () => { - cy.get('#grid20').find('.slick-header-column:nth(5)').trigger('mouseover').trigger('contextmenu').invoke('show'); + it('should show again "Title" column from Column Picker and expect last pinned column to still be "% Complete"', () => { + cy.get('#grid20').find('.slick-header-column:nth(4)').trigger('mouseover').trigger('contextmenu').invoke('show'); cy.get('.slick-column-picker') .find('.slick-column-picker-list') - .children('li:nth-child(2)') + .children('li:nth-of-type(2)') .children('label') .should('contain', 'Title') .click(); @@ -182,83 +235,769 @@ describe('Example 20 - Frozen Grid', () => { cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); cy.get('#grid20') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.get('[data-test=remove-frozen-column-button]').click({ force: true }); + it('should click on the "Remove Pinned Columns" button to switch to a regular grid view without pinned columns and expect 7 columns on the left container', () => { + cy.get('[data-test=remove-pinned-column-button]').click({ force: true }); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 1 * 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 11 * 2); + cy.get('#grid20 .slick-row[data-row="0"]').should('have.length.at.least', 1); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').should('exist').and('not.have.class', 'slick-pinned-left-cells-active'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell').should('have.length', 9); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + getCell(0, 0).should('contain', ''); + getCell(0, 1).should('contain', 'Task 0'); + getCell(0, 3).should('contain', '2009-01-01'); + getCell(0, 4).should('contain', '2009-05-05'); + }); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(3)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '2009-05-05'); + it('should expect to have exact Column Header Titles in the grid', () => { + cy.get('#grid20') + .find('.slick-header-columns:nth(0) .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should have exact Column Header Titles in the grid', () => { + it('should click on the "Set 3 Pinned Columns" button to switch pinned columns grid and expect 3 pinned columns on the left and 4 columns on the right', () => { + cy.get('[data-test=set-3pinned-columns]').click({ force: true }); + + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); + + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(1)').should('contain', 'Task 0'); + + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); + + it('should recheck again and still have exact Column Header Titles in the grid', () => { cy.get('#grid20') - .find('.slick-header-columns:nth(0)') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns:nth(0) .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { - cy.get('[data-test=set-3frozen-columns]').click({ force: true }); + it('should click on the Grid Menu command "Unpin Columns/Rows" to switch to a regular grid without pinned columns/rows', () => { + cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.contains('Unpin Columns/Rows').click({ force: true }); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid20 .slick-row[data-row="0"]').should('have.length.at.least', 1); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').should('exist').and('not.have.class', 'slick-pinned-left-cells-active'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell').should('have.length', 9); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + getCell(0, 0).should('contain', ''); + getCell(0, 1).should('contain', 'Task 0'); + getCell(0, 3).should('contain', '2009-01-01'); + getCell(0, 4).should('contain', '2009-05-05'); }); - it('should have exact Column Header Titles in the grid', () => { + it('should open the Cell Menu on 2nd and 3rd row and change the Effort-Driven to "True" and expect the cell to be updated and have checkmark icon', () => { + getCell(1, 1).should('contain', 'Task 1'); + getCell(1, 8).find('.checkmark-icon').should('have.length', 0); + getCell(2, 1).should('contain', 'Task 2'); + getCell(2, 8).find('.checkmark-icon').should('have.length', 0); + + getCell(1, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('True').click(); + getCell(2, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('True').click(); + + getCell(1, 5).find('.checkmark-icon').should('have.length', 1); + getCell(2, 5).find('.checkmark-icon').should('have.length', 1); + }); + + it('should open the Cell Menu on 2nd and 3rd row and change the Effort-Driven to "False" and expect the cell to be updated and no longer have checkmark', () => { + getCell(1, 5).find('.checkmark-icon').should('have.length', 1); + getCell(2, 5).find('.checkmark-icon').should('have.length', 1); + + getCell(1, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('False').click(); + getCell(2, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('False').click(); + + getCell(1, 5).find('.checkmark-icon').should('have.length', 0); + getCell(2, 5).find('.checkmark-icon').should('have.length', 0); + }); + + it('should open the Cell Menu and delete Row 3 and 4 from the Cell Menu', () => { + cy.window().then((win) => { + const stub = cy.stub(win, 'confirm').returns(true); + cy.wrap(stub).as('confirmStub'); + }); + + getCell(3, 1).should('contain', 'Task 3'); + getCell(4, 1).should('contain', 'Task 4'); + + getCell(3, 8).contains('Action').click({ force: true }); + + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item').contains('Delete Row').click(); + cy.get('@confirmStub').should('have.been.calledWith', 'Do you really want to delete row (4) with "Task 3"?'); + getCell(3, 1).should('contain', 'Task 4'); + }); + + it.skip('should filter autocomplete by typing Vancouver in the "City of Origin" and expect only filtered rows to show up', () => { + cy.get('.search-filter.filter-cityOfOrigin').type('Vancouver'); + + cy.get('.slick-autocomplete').should('be.visible'); + cy.get('.slick-autocomplete div').should('have.length', 2); + cy.get('.slick-autocomplete').find('div:nth(0)').click(); + + getCell(0, 1).should('contain', 'Task 1'); + getCell(1, 1).should('contain', 'Task 5'); + getCell(2, 1).should('contain', 'Task 7'); + getCell(3, 1).should('contain', 'Task 9'); + getCell(4, 1).should('contain', 'Task 11'); + }); + + it('should Clear all Filters', () => { + cy.get('#grid20').find('button.slick-grid-menu-button').trigger('click').click({ force: true }); + + cy.get(`.slick-grid-menu:visible`).find('.slick-menu-item').first().find('span').contains('Clear all Filters').click(); + }); + + it.skip('should edit first row (Task 1) and change its city by choosing it inside the autocomplete editor list', () => { + getCell(0, 7).click(); + cy.get('input.autocomplete.editor-cityOfOrigin').type('Sydney'); + + cy.get('.slick-autocomplete').should('be.visible'); + cy.get('.slick-autocomplete div').should('have.length', 3); + cy.get('.slick-autocomplete').find('div:nth(1)').click(); + + getCell(0, 1).should('contain', 'Task 0'); + getCell(0, 7).should('contain', 'Sydney, NS, Australia'); + }); + + it('should open Context Menu hover "% Complete" column then select "Not Started (0%)" option and expect Task to be at 0', () => { + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu .slick-menu-option-list').should('exist').contains('Not Started (0%)').click(); + + getCell(0, 2).should('contain', '0'); + }); + + it('should reopen Context Menu hover "% Complete" column then open options sub-menu & select "Half Completed (50%)" option and expect Task to be at 50', () => { + const subOptions = ['Not Started (0%)', 'Half Completed (50%)', 'Completed (100%)']; + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-option-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Sub-Options (demo)') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-option-list').as('subMenuList'); + cy.get('@subMenuList').find('.slick-menu-title').contains('Set Percent Complete'); + cy.get('@subMenuList') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.eq(subOptions[index])); + + cy.get('@subMenuList').find('.slick-menu-item .slick-menu-content').contains('Half Completed (50%)').click(); + + getCell(0, 2).should('contain', '50'); + }); + + it('should be able to open Context Menu and click on Export->Text and expect alert triggered with Text Export', () => { + const subCommands1 = ['Text', 'Excel']; + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list').find('.slick-menu-item').contains('Text').click(); + cy.get('@alertStub').should('have.been.calledWith', 'Exporting as Text (tab delimited)'); + }); + + it('should be able to open Context Menu and click on Export->Excel-> sub-commands to see 1 context menu + 1 sub-menu then clicking on Text should call alert action', () => { + const subCommands1 = ['Text', 'Excel']; + const subCommands2 = ['Excel (csv)', 'Excel (xlsx)']; + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Excel') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list').as('subMenuList2'); + + cy.get('@subMenuList2').find('.slick-menu-title').contains('available formats'); + + cy.get('@subMenuList2') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands2[index])); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Excel (xlsx)') + .click(); + cy.get('@alertStub').should('have.been.calledWith', 'Exporting as Excel (xlsx)'); + }); + + it('should open Export->Excel sub-menu & open again Sub-Options on top and expect sub-menu to be recreated with that Sub-Options list instead of the Export->Excel list', () => { + const subCommands1 = ['Text', 'Excel']; + const subCommands2 = ['Excel (csv)', 'Excel (xlsx)']; + const subOptions = ['Not Started (0%)', 'Half Completed (50%)', 'Completed (100%)']; + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Excel') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands2[index])); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-option-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Sub-Options') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-option-list').as('optionSubList2'); + + cy.get('@optionSubList2').find('.slick-menu-title').contains('Set Percent Complete'); + + cy.get('@optionSubList2') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($option, index) => expect($option.text()).to.contain(subOptions[index])); + }); + + it('should open Export->Excel context sub-menu then open Feedback->ContactUs sub-menus and expect previous Export menu to no longer exists', () => { + const subCommands1 = ['Text', 'Excel']; + const subCommands2 = ['Request update from supplier', '', 'Contact Us']; + const subCommands2_1 = ['Email us', 'Chat with us', 'Book an appointment']; + + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick({ force: true }); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + // click different sub-menu + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Feedback') + .should('exist') + .click(); + + cy.get('.slick-submenu').should('have.length', 1); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands2[index])); + + // click on Feedback->ContactUs + cy.get('.slick-context-menu.slick-menu-level-1.dropright') // right align + .find('.slick-menu-item .slick-menu-content') + .contains('Contact Us') + .should('exist') + .trigger('mouseover'); // mouseover or click should work + + cy.get('.slick-submenu').should('have.length', 2); + cy.get('.slick-context-menu.slick-menu-level-2.dropright') // right align + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.eq(subCommands2_1[index])); + + cy.get('.slick-context-menu.slick-menu-level-2'); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Chat with us') + .click(); + cy.get('@alertStub').should('have.been.calledWith', 'Command: contact-chat'); + + cy.get('.slick-submenu').should('have.length', 0); + }); + + it('should toggle Select All checkbox and expect back "Sel" column title to show when Select All checkbox is shown in the header row', () => { + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('contain', 'Sel'); + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('not.contain', 'Sel'); + + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); + }); + + it('should toggle back Select All checkbox and expect back "Sel" column title to show when Select All checkbox is shown in the header row', () => { + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('contain', 'Sel'); + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('not.contain', 'Sel'); + + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); + }); + + it('should open Column Picker and try unchecked all the columns on the right of the column pinning and expect an error and abort of the execution', () => { + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + cy.get('[data-test=set-3pinned-columns]').click({ force: true }); + + const leftColumns = ['', 'Title', '% Complete']; + const rightColumns = ['Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + cy.get('#grid20').find('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children() + .each(($child, index) => { + if (index >= leftColumns.length) { + if ($child.text() === rightColumns[index - leftColumns.length]) { + expect($child.text()).to.eq(rightColumns[index - leftColumns.length]); + if (index <= rightColumns.length + 1) { + cy.wrap($child).children('label').click(); + } else { + cy.wrap($child) + .children('label') + .click() + .then(() => { + cy.get('@alertStub').should( + 'have.been.calledWith', + '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + + 'You could alternatively unpin columns before trying again.' + ); + }); + } + } + } + }); + + cy.get('button[data-dismiss="slick-column-picker"]').click(); + }); + + it('should also not be able to "Hide Column" via the Header Menu', () => { + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + const newColumnList = ['', 'Title', '% Complete', 'Action']; + + cy.get('#grid20').find('.slick-header-column:nth(3)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + cy.get('.slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item') + .contains('Hide Column') + .click() + .then(() => { + cy.get('@alertStub').should( + 'have.been.calledWith', + '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + + 'You could alternatively unpin columns before trying again.' + ); + }); + cy.get('#grid20') - .find('.slick-header-columns:nth(0)') + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); + }); + + it('should be able to uncheck "Title" column without any alert', () => { + cy.get('#grid20').find('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + const updatedColumns = ['', '% Complete', 'Action']; + cy.get('.slick-column-picker-list li:not(.hidden) .checkbox-picker-label').first().click(); + cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); + cy.get('.slick-header-columns:nth(0) .slick-header-column').each(($child, index) => expect($child.text()).to.eq(updatedColumns[index])); + }); + + it('should be able to add back hidden "Title" column without any alert', () => { + const updatedColumns = ['', 'Title', '% Complete', 'Action']; + cy.get('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + cy.get('.slick-column-picker-list li:not(.hidden) .checkbox-picker-label').first().click(); + cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); + cy.get('.slick-header-columns:nth(0) .slick-header-column').each(($child, index) => expect($child.text()).to.eq(updatedColumns[index])); + }); + + it('should reset hidden column from the Column Picker and expect all columns to be back', () => { + const leftColumns = ['', 'Title', '% Complete']; + const rightColumns = ['Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + + cy.get('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => { + if (index >= leftColumns.length) { + if ($child.text() === rightColumns[index - leftColumns.length]) { + expect($child.text()).to.eq(rightColumns[index - leftColumns.length]); + if (index <= rightColumns.length + 1) { + cy.wrap($child).children('label').click(); + } + } + } + }); + + cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); + + cy.get('#grid20') + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); }); - it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); + describe('Test UI rendering after Scrolling with large columns', () => { + it('should unpin all columns/rows', () => { + cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); + + cy.contains('Unpin Columns/Rows').click({ force: true }); + }); + + it('should resize all columns and make them wider', () => { + // resize CityOfOrigin column + cy.get('.slick-header-columns .slick-header-column:nth(7)').should('contain', 'City of Origin'); + + cy.get('.slick-resizable-handle:nth(7)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(8)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + // resize Cost|Duration column + cy.get('.slick-header-columns .slick-header-column:nth(6)').should('contain', 'Cost | Duration'); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 1); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 11); + cy.get('.slick-resizable-handle:nth(6)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.slick-header-column:nth(8)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Completed column + cy.get('.slick-header-columns .slick-header-column:nth(5)').should('contain', 'Completed'); + + cy.get('.slick-resizable-handle:nth(5)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(7)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Finish column + cy.get('.slick-header-columns .slick-header-column:nth(4)').should('contain', 'Finish'); + + cy.get('.slick-resizable-handle:nth(4)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(6)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Start column + cy.get('.slick-header-columns .slick-header-column:nth(3)').should('contain', 'Start'); + + cy.get('.slick-resizable-handle:nth(3)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(6)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize %Complete column + cy.get('.slick-header-columns .slick-header-column:nth(2)').should('contain', '% Complete'); + + cy.get('.slick-resizable-handle:nth(2)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(3)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Title column + cy.get('.slick-header-columns .slick-header-column:nth(1)').should('contain', 'Title'); + + cy.get('.slick-resizable-handle:nth(1)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(3)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + }); + + it('should scroll horizontally completely to the right and expect all cell to be rendered', () => { + getCell(2, 1).contains(/Task [0-9]*/); + getCell(2, 2).contains(/[0-9]*/); + + getCell(15, 1).contains(/Task [0-9]*/); + getCell(15, 2).contains(/[0-9]*/); + + // horizontal scroll to right + // Pinning has one real horizontal scroll owner. Scrolling the old body + // viewport only exercises the compatibility bridge; target the proxy + // here to verify the user-facing scrollbar and all chrome move together. + cy.get('#grid20 .slick-horizontal-scroller').scrollTo('100%', '0%', { duration: 1500 }); + getCell(2, 3).should('contain', '2009-01-01'); + getCell(2, 4).should('contain', '2009-05-05'); + getCell(2, 7).contains(/[United State|Canada]*/); + getCell(2, 8).should('contain', 'Action'); + + getCell(15, 3).should('contain', '2009-01-01'); + getCell(15, 4).should('contain', '2009-05-05'); + getCell(15, 7).contains(/[United State|Canada]*/); + getCell(15, 8).should('contain', 'Action'); + }); + + it('should scroll vertically to the middle of the grid and expect all cell to be rendered', () => { + // vertical scroll to middle + cy.get('.slick-vertical-scroller').scrollTo('0%', '40%', { duration: 1500 }); + + getCell(200, 3).should('contain', '2009-01-01'); + getCell(200, 4).should('contain', '2009-05-05'); + getCell(200, 7).contains(/[United State|Canada]*/); + getCell(200, 8).should('contain', 'Action'); + + getCell(205, 3).should('contain', '2009-01-01'); + getCell(205, 4).should('contain', '2009-05-05'); + getCell(205, 7).contains(/[United State|Canada]*/); + getCell(205, 8).should('contain', 'Action'); + + // reset scroll + cy.get('.slick-vertical-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + }); + }); + + describe('accessibility sub-menus tests', () => { + beforeEach(() => { + // Open the context menu on a cell to start each test + cy.get('.slick-vertical-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('[data-row="0"] .slick-cell.l3.r3').rightclick({ force: true }); + cy.get('.slick-context-menu.slick-menu-level-0').should('be.visible'); + }); + + it('should open Exports sub-menu with ArrowRight, then Excel sub-menu with ArrowRight, and close with ArrowLeft', () => { + // Move down to "Exports" (4th item) + cy.focused(); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.get('.slick-context-menu.slick-menu-level-0 .slick-submenu-item[data-command="export"]').should('have.focus'); + + // Open "Exports" sub-menu with ArrowRight + cy.focused().type('{rightarrow}'); + cy.get('.slick-context-menu.slick-menu-level-1[data-sub-menu-parent="export"]').should('be.visible'); + + // Move down to "Excel" (2nd item in sub-menu) + cy.focused().type('{downarrow}'); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-submenu-item[data-command="sub-menu"]').should('have.focus'); + + // Open "Excel" sub-menu with ArrowRight + cy.focused().type('{rightarrow}'); + cy.get('.slick-context-menu.slick-menu-level-2[data-sub-menu-parent="sub-menu"]').should('be.visible'); + + // Move down to "Excel (xlsx)" (2nd item in Excel sub-menu) + cy.focused().type('{downarrow}'); + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-item[data-command="exports-xlsx"]').should('have.focus'); + + // Close Excel sub-menu with ArrowLeft + cy.focused().type('{leftarrow}'); + cy.get('.slick-context-menu.slick-menu-level-2').should('not.exist'); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-submenu-item[data-command="sub-menu"]').should('have.focus'); + + // close all context menus + cy.get('[data-row="0"] .slick-cell.l0.r0').click(); + }); + + it('should open sub-menus using Enter as well as ArrowRight', () => { + // Move down to "Exports" + cy.focused(); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.get('.slick-context-menu.slick-menu-level-0 .slick-submenu-item[data-command="export"]').should('have.focus'); + + // Open "Exports" sub-menu with Enter + cy.focused().type('{enter}'); + cy.get('.slick-context-menu.slick-menu-level-1[data-sub-menu-parent="export"]').should('be.visible'); + + // Move down to "Excel" + cy.focused().type('{downarrow}'); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-submenu-item[data-command="sub-menu"]').should('have.focus'); + + // Open "Excel" sub-menu with Enter + cy.focused().type('{enter}'); + cy.get('.slick-context-menu.slick-menu-level-2[data-sub-menu-parent="sub-menu"]').should('be.visible'); + + // close all context menus + cy.get('[data-row="0"] .slick-cell.l0.r0').click(); + }); + + it('should activate a sub-menu leaf item with Enter', () => { + // Move down to "Exports" + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + cy.focused(); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.ENTER); + cy.get('.slick-context-menu.slick-menu-level-1[data-sub-menu-parent="export"]').should('be.visible'); + + // "Text (tab delimited)" is first item, should have focus + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-item[data-command="exports-txt"]').should('have.focus'); + // Activate with Enter (add your assertion for the result) + cy.focused().type('{enter}'); + }); + + it('should reapply 3 Pinned Columns and expect to be able to focus on first filter and go left/right between both viewports without problems', () => { + cy.get('[data-test="set-3pinned-columns"]').click(); + cy.get('.slick-headerrow-column.l1 input').focus(); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l2 input').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l3 select').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l3 input').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l4 select').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l4 input').should('have.focus'); + + // Shift+Tab dosn't work in Cypress, so we can't go further with tests + }); + }); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(3)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '2009-05-05'); + describe('drag & drop column reordering with auto-scroll', () => { + it('should auto-scroll right viewport and reorder columns when "Start" is dragged well past the right edge (ending up after "Finish")', () => { + // Close the context menu opened by beforeEach + cy.get('body').type('{esc}'); + + // Control app timers to make drag auto-scroll deterministic in CI. + cy.clock(); + + // Normalize right viewport scroll so this test is isolated from previous test state. + cy.get('.slick-horizontal-scroller').then(($viewport) => { + $viewport[0].scrollLeft = 0; + $viewport[0].dispatchEvent(new Event('scroll', { bubbles: true })); + }); + cy.get('.slick-horizontal-scroller').its('0.scrollLeft').should('equal', 0); + cy.get('[data-test="set-large-pinned-columns"]').click(); + + // Step 1: call SortableJS onStart for the "Start" column (1st center-section column). + // This binds the document 'drag' auto-scroll listener for the shared proxy scrollbar. + cy.get('.slick-header-columns-center').then(($rightHeader) => { + let sortInstance: any; + Object.keys($rightHeader[0]).forEach((prop) => { + if (prop.startsWith('Sortable')) { + sortInstance = ($rightHeader[0] as any)[prop]; + } + }); + expect(sortInstance).to.exist; + const startColumnEl = $rightHeader[0].querySelectorAll('.slick-header-column')[0] as HTMLElement; + sortInstance.options.onStart({ item: startColumnEl }); + }); + + // Step 2: fire a document drag event well past the right edge (viewport-relative) + // to avoid CI flakiness caused by environment-dependent viewport widths. + cy.window().then((win) => { + const dragX = win.innerWidth + 1200; + cy.document().trigger('drag', { pageX: dragX, clientX: dragX, clientY: 50 }); + }); + + // Step 3: advance mocked time so the 30ms scroll interval ticks several times. + cy.tick(350); + + // Auto-scroll should have moved the right viewport to the right + cy.get('.slick-horizontal-scroller').its('0.scrollLeft').should('be.greaterThan', 0); + + // Step 4: simulate the drag result — "Start" was moved to the right, past "Finish". + // SortableJS reads the DOM order via toArray() inside onEnd, so physically reorder the children first. + cy.get('.slick-header-columns-center').then(($rightHeader) => { + let sortInstance: any; + Object.keys($rightHeader[0]).forEach((prop) => { + if (prop.startsWith('Sortable')) { + sortInstance = ($rightHeader[0] as any)[prop]; + } + }); + expect(sortInstance).to.exist; + const startColumnEl = $rightHeader[0].querySelector('[data-id="start"]') as HTMLElement; + const finishColumnEl = $rightHeader[0].querySelector('[data-id="finish"]') as HTMLElement; + expect(startColumnEl).to.exist; + expect(finishColumnEl).to.exist; + + // Move "Finish" before "Start" → mirrors dragging Start past Finish + $rightHeader[0].insertBefore(finishColumnEl, startColumnEl); + + // onEnd reads the new DOM order via toArray() and calls setColumns() if the order changed + sortInstance.options.onEnd({ item: startColumnEl, stopPropagation: () => {} }); + }); + + // The center region should now place Finish before Start. The exact + // region membership can vary when a large requested pin band does not + // fit the current viewport, so assert semantic order by column id. + cy.get('.slick-header-column').then(($headers) => { + const ids = [...$headers].map((header) => header.dataset.id); + expect(ids.indexOf('finish')).to.be.lessThan(ids.indexOf('start')); + }); + + // When a left band is active, its order must remain unchanged. + cy.get('.slick-header-columns-left').then(($leftRegion) => { + const left = $leftRegion.find('.slick-header-column'); + if (left.length) { + expect([...left].map((header) => header.dataset.id)).to.deep.equal(['_checkbox_selector', 'title', 'percentComplete']); + } + }); + }); }); }); diff --git a/demos/aurelia/test/cypress/e2e/example24.cy.ts b/demos/aurelia/test/cypress/e2e/example24.cy.ts index c2ea74764b..b73ab056b1 100644 --- a/demos/aurelia/test/cypress/e2e/example24.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example24.cy.ts @@ -232,7 +232,7 @@ describe('Example 24 - Cell Menu & Context Menu Plugins', () => { }); it('should check Context Menu "menuUsabilityOverride" condition and expect to not be able to open Context Menu from rows than are >= to Task 21', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(25); + cy.get('.slick-vertical-scroller').scrollTo('bottom').wait(25); cy.get('#grid24').find('.slick-row:nth(3) .slick-cell:nth(1)').rightclick({ force: true }); @@ -240,7 +240,7 @@ describe('Example 24 - Cell Menu & Context Menu Plugins', () => { }); it('should scroll back to top row and be able to open Context Menu', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(25); + cy.get('.slick-vertical-scroller').scrollTo('top').wait(25); cy.get('#grid24').find('.slick-row:nth(1) .slick-cell:nth(1)').rightclick({ force: true }); diff --git a/demos/aurelia/test/cypress/e2e/example27.cy.ts b/demos/aurelia/test/cypress/e2e/example27.cy.ts index 8b76749726..28873fbf4b 100644 --- a/demos/aurelia/test/cypress/e2e/example27.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example27.cy.ts @@ -160,7 +160,7 @@ describe('Example 27 - Tree Data (from a flat dataset with parentId references)' it('should be able to update the 1st row item (Task 0)', () => { cy.get('[data-test=update-item-btn]').contains('Update 1st Row Item').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); const now = new Date(); const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -201,7 +201,7 @@ describe('Example 27 - Tree Data (from a flat dataset with parentId references)' cy.get(`.slick-grid-menu:visible`).find('.slick-menu-item').first().find('span').contains('Clear all Filters').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); }); it('should be able to open "Task 1" and "Task 3" parents', () => { diff --git a/demos/aurelia/test/cypress/e2e/example28.cy.ts b/demos/aurelia/test/cypress/e2e/example28.cy.ts index 8f8f5e2228..123830953d 100644 --- a/demos/aurelia/test/cypress/e2e/example28.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example28.cy.ts @@ -84,7 +84,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { it('should expand "pdf" folder and expect all folders to be expanded', () => { cy.get('[data-row="4"] > .slick-cell:nth(0) .slick-group-toggle.collapsed').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('top', { force: true } as any); }); it('should have default Files list', () => { @@ -97,7 +97,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with aggregations of Sum(53.3MB) / Avg(26.65MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('center', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('center', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 151.3 MB / avg: 50.43 MB'); @@ -118,7 +118,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with updated aggregations including new pop songs of Sum(218.3MB) / Avg(54.58MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 316.3 MB / avg: 63.26 MB'); @@ -225,7 +225,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 400.3 MB / avg: 66.72 MB'); @@ -308,7 +308,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have again the pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('center', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('center', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 400.3 MB / avg: 66.72 MB'); @@ -336,7 +336,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with aggregation reflecting what is displayed, Sum(316.3MB) / Avg(63.26MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('center', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('center', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 316.3 MB / avg: 63.26 MB'); @@ -347,7 +347,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have documents with same Sum as the beginning since auto-recalc is disabled, aggregation should be Sum(14.46MB) / Avg(1.45MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('top', { force: true } as any); cy.get('[data-row="1"] > .slick-cell:nth(0)').should('contain', 'documents'); cy.get('[data-row="1"] > .slick-cell:nth(3)').should('contain', 'sum: 14.46 MB / avg: 1.45 MB (total)'); diff --git a/demos/aurelia/test/cypress/e2e/example38.cy.ts b/demos/aurelia/test/cypress/e2e/example38.cy.ts index a179cdc5cb..02b700180d 100644 --- a/demos/aurelia/test/cypress/e2e/example38.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example38.cy.ts @@ -20,7 +20,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -32,7 +32,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a new total of 90 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '60'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '90'); @@ -48,7 +48,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { cy.get('[data-test="data-loaded-tag"]').should('not.have.class', 'fully-loaded'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '100'); @@ -78,7 +78,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom again and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -104,7 +104,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom again and expect next batch to be only 20 females appended to current dataset for a total of 50 items found in DB', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '50'); @@ -119,7 +119,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-test=odata-query-result]').should(($span) => { expect($span.text()).to.eq(`$count=true&$top=30`); @@ -130,11 +130,11 @@ describe('Example 38 - Infinite Scroll with OData', () => { }); it('should scroll to the bottom "Group by Gender" and expect 30 more items for a total of 60 items grouped', () => { - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-test=odata-query-result]').should(($span) => { expect($span.text()).to.eq(`$count=true&$top=30&$skip=30`); diff --git a/demos/aurelia/test/cypress/e2e/example39.cy.ts b/demos/aurelia/test/cypress/e2e/example39.cy.ts index d7091726f6..a87b8ef667 100644 --- a/demos/aurelia/test/cypress/e2e/example39.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example39.cy.ts @@ -29,7 +29,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -44,7 +44,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a new total of 90 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '60'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '90'); @@ -63,7 +63,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { cy.get('[data-test="data-loaded-tag"]').should('not.have.class', 'fully-loaded'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '100'); @@ -100,7 +100,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom again and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -135,7 +135,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom again and expect next batch to be only 20 females appended to current dataset for a total of 50 items found in DB', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '50'); diff --git a/demos/aurelia/test/cypress/e2e/example40.cy.ts b/demos/aurelia/test/cypress/e2e/example40.cy.ts index 4f8eeb96e2..4ef56ccdec 100644 --- a/demos/aurelia/test/cypress/e2e/example40.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example40.cy.ts @@ -25,14 +25,14 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { it('should scroll to bottom of the grid and expect next batch of 50 items appended to current dataset for a total of 100 items', () => { cy.get('[data-test="totalItemCount"]').should('have.text', '50'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '100'); }); it('should scroll to bottom of the grid again and expect 50 more items for a total of now 150 items', () => { cy.get('[data-test="totalItemCount"]').should('have.text', '100'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '150'); }); @@ -42,7 +42,7 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { cy.get('[data-id="title"]').click(); cy.get('[data-test="totalItemCount"]').should('have.text', '150'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0)').should('contain', 'Task 0'); cy.get('[data-row="1"] > .slick-cell:nth(0)').should('contain', 'Task 1'); @@ -55,7 +55,7 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { cy.get('[data-id="title"]').click(); cy.get('[data-test="totalItemCount"]').should('have.text', '50'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0)').should('contain', 'Task 9'); cy.get('[data-row="1"] > .slick-cell:nth(0)').should('contain', 'Task 8'); @@ -68,17 +68,17 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { cy.get('[data-test="totalItemCount"]').should('have.text', '50'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-toggle.expanded').should('have.length', 1); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-title').contains(/Duration: [0-9]/); }); it('should scroll to the bottom "Group by Duration" and expect 50 more items for a total of 100 items grouped', () => { - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '100'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-toggle.expanded').should('have.length', 1); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-title').contains(/Duration: [0-9]/); }); @@ -103,12 +103,12 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { it('should load 200 items and filter "Start" column with <=2020-08-25', () => { cy.get('[data-test="set-dynamic-filter"]').click(); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '150'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '200'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get(`[data-row=0] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/); cy.get(`[data-row=1] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/); diff --git a/demos/aurelia/test/cypress/e2e/example43.cy.ts b/demos/aurelia/test/cypress/e2e/example43.cy.ts index bfda594730..ca0c3b17cb 100644 --- a/demos/aurelia/test/cypress/e2e/example43.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example43.cy.ts @@ -27,15 +27,23 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 0 }, cy.get('h2').should('contain', 'Example 43: colspan/rowspan - Employees Timesheets'); }); + it('should hide sub-title', () => { + cy.get('[data-test=toggle-subtitle]').click(); + }); + it('should have exact column titles', () => { cy.get('.slick-header-columns') .children() .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should expect 1st column to be frozen (frozen)', () => { - cy.get('.grid-canvas-left .slick-cell.frozen').should('have.length', 10); - cy.get('.grid-canvas-right .slick-cell:not(.frozen)').should('have.length.above', 50); + it('should expect 1st column to be pinned', () => { + // Pinning uses one live canvas and splits each row into left/center/right + // regions; the old grid-canvas-left/right pinned panes no longer exist. + cy.get('.slick-pinned-left-cells .slick-cell.slick-cell-pinned-left').should('have.length', 10); + // The shared Vue Cypress viewport is 1200px wide, but its route sidebar + // leaves less room for center-column virtualization than the Vanilla app. + cy.get('.slick-scrolling-cells .slick-cell').should('have.length.above', 50); }); it('should not display any Column Picker in the Grid Menu', () => { @@ -47,32 +55,32 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 0 }, describe('Spanning', () => { it('should expect "Davolio", "Check Mail", and "Development" to all have rowspan of 2 in morning hours', () => { - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should(($el) => + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should(($el) => + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); }); it('should expect "Lunch Break" to span over 3 columns and over all rows', () => { - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10) ); }); it('should expect a large "Development" section that spans over multiple columns & rows in the afternoon', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should(($el) => + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) ); }); @@ -80,334 +88,340 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 0 }, describe('Basic Key Navigations', () => { it('should start at Employee 10001, then type "End" key and expect to be in "Team Meeting" between 4:30-5:00pm', () => { - cy.get('[data-row=0] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l0.r0.active').should('contain', '10001'); + cy.get('[data-row=0] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l0.r0.active').should('contain', '10001'); cy.get('@active_cell').type('{end}'); - cy.get('[data-row=0] > .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); }); it('should start at Employee 10002, then type "End" key and also expect to be in "Team Meeting" between 4:30-5:00pm', () => { - cy.get('[data-row=1] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=1] > .slick-cell.l0.r0.active').should('contain', '10002'); + cy.get('[data-row=1] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=1] .slick-cell.l0.r0.active').should('contain', '10002'); cy.get('@active_cell').type('{end}'); - cy.get('[data-row=0] > .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); }); it('should start at Employee 10004, then type "ArrowRight" key twice and expect to be in "Check Mail" between 9:00-10:30am', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}'); - cy.get('[data-row=2] > .slick-cell.l2.r4.active').should('contain', 'Check Mail'); + cy.get('[data-row=2] .slick-cell.l2.r4.active').should('contain', 'Check Mail'); }); it('should start at Employee 10004, then type "ArrowRight" key 4x times and expect to be in "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); - cy.get('[data-row=3] > .slick-cell.l6.r9.active').should('contain', 'Testing'); + cy.get('[data-row=3] .slick-cell.l6.r9.active').should('contain', 'Testing'); }); it('should start at Employee 10004, then type "ArrowRight" key 5x times and expect to be in "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, then type "ArrowRight" key 6x times and expect to be in "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); // then rollback by going backward it('should be on Employee 10004 row at previous "Development" cell, then type "ArrowLeft" key once and expect to be in "Lunch Break"', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).as('active_cell').click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + // The proxy scrollbar is a sibling of the canvas, so native + // scrollIntoView() cannot reveal a horizontally virtualized cell. + cy.get('.slick-horizontal-scroller').scrollTo('right', { ensureScrollable: false }); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).as('active_cell').scrollIntoView().click(); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); cy.get('@active_cell').type('{leftarrow}'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key once and expect to be in "Conference" between 4:00-5:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).type('{leftarrow}'); - cy.get(`[data-row=3] > .slick-cell.l16.r17.active`).should('contain', 'Conference'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).type('{leftarrow}'); + cy.get(`[data-row=3] .slick-cell.l16.r17.active`).should('contain', 'Conference'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 3x times and expect to be back to "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).type('{leftarrow}{leftarrow}{leftarrow}'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).type('{leftarrow}{leftarrow}{leftarrow}'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 4x times and expect to be back to "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 5x times and expect to be back to "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{leftarrow}{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); - cy.get(`[data-row=3] > .slick-cell.l6.r9.active`).should('contain', 'Testing'); + cy.get(`[data-row=3] .slick-cell.l6.r9.active`).should('contain', 'Testing'); }); // going down it('should start at 10am "Team Meeting, then type "ArrowDown" key once and expect to be in "Support" between 9:30-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}'); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key twice and expect to be in "Check Email" between 9:00-10:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 3x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}'); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}'); - cy.get(`[data-row=5] > .slick-cell.l4.r6.active`).should('contain', 'Support'); + cy.get(`[data-row=5] .slick-cell.l4.r6.active`).should('contain', 'Support'); }); // going up from inverse it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" once and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}'); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 2x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 3x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}{uparrow}'); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 4x times and expect to be back to same "Team Meeting"', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}{uparrow}{uparrow}'); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); }); }); describe('Grid Navigate Functions', () => { it('should start at Employee 10004, then type "Navigate Right" twice and expect to be in "Check Mail" between 9:00-10:30am', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); cy.get('[data-test="goto-next"]').click().click(); - cy.get('[data-row=2] > .slick-cell.l2.r4.active').should('contain', 'Check Mail'); + cy.get('[data-row=2] .slick-cell.l2.r4.active').should('contain', 'Check Mail'); }); it('should start at Employee 10004, then type "Navigate Right" 4x times and expect to be in "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('[data-test="goto-next"]').click().click().click().click(); - cy.get('[data-row=3] > .slick-cell.l6.r9.active').should('contain', 'Testing'); + cy.get('[data-row=3] .slick-cell.l6.r9.active').should('contain', 'Testing'); }); it('should start at Employee 10004, then type "Navigate Right" 5x times and expect to be in "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('[data-test="goto-next"]').click().click().click().click().click(); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, then type "Navigate Right" 6x times and expect to be in "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('[data-test="goto-next"]').click().click().click().click().click().click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); // then rollback by going backward it('should be on Employee 10004 row at previous "Development" cell, then type "Navigate Left" once and expect to be in "Lunch Break"', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).as('active_cell').click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get('.slick-horizontal-scroller').scrollTo('right', { ensureScrollable: false }); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).as('active_cell').scrollIntoView().click(); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); cy.get('[data-test="goto-prev"]').click(); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" once and expect to be in "Conference" between 4:00-5:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click(); - cy.get(`[data-row=3] > .slick-cell.l16.r17.active`).should('contain', 'Conference'); + cy.get(`[data-row=3] .slick-cell.l16.r17.active`).should('contain', 'Conference'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 3x times and expect to be back to "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click().click().click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 4x times and expect to be back to "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click().click().click().click(); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 5x times and expect to be back to "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click().click().click().click().click(); - cy.get(`[data-row=3] > .slick-cell.l6.r9.active`).should('contain', 'Testing'); + cy.get(`[data-row=3] .slick-cell.l6.r9.active`).should('contain', 'Testing'); }); // going down it('should start at 10am "Team Meeting, then type "ArrowDown" key once and expect to be in "Support" between 9:30-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click(); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key twice and expect to be in "Check Email" between 9:00-10:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click(); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 3x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click(); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); - cy.get(`[data-row=5] > .slick-cell.l4.r6.active`).should('contain', 'Support'); + cy.get(`[data-row=5] .slick-cell.l4.r6.active`).should('contain', 'Support'); }); // going up from inverse it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" once and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click(); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 2x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click().click(); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 3x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click().click().click(); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 4x times and expect to be back to same "Team Meeting"', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click().click().click().click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); }); }); describe('Grid Editing', () => { it('should toggle editing', () => { cy.get('#isEditable').contains('false'); - cy.get('[data-row=0] > .slick-cell.l4.r4').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active .editor-text').should('not.exist'); + cy.get('[data-row=0] .slick-cell.l4.r4').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active .editor-text').should('not.exist'); cy.get('[data-test=toggle-editing]').click(); cy.get('#isEditable').contains('true'); - cy.get('[data-row=0] > .slick-cell.l4.r4').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active.editable .editor-text').should('exist'); - cy.get('[data-row=0] > .slick-cell.l4.r4.active.editable .editor-text').type('Team Meeting.xyz{enter}'); + cy.get('[data-row=0] .slick-cell.l4.r4').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active.editable .editor-text').should('exist'); + cy.get('[data-row=0] .slick-cell.l4.r4.active.editable .editor-text').type('Team Meeting.xyz{enter}'); }); // going down it('should have changed active cell to "Support" between 9:30-11:00am', () => { - cy.get('[data-row=1] > .slick-cell.l3.r5.active.editable .editor-text') + cy.get('[data-row=1] .slick-cell.l3.r5.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Support')); - cy.get('[data-row=1] > .slick-cell.l3.r5.active.editable .editor-text').type('Support.xyz{enter}'); + cy.get('[data-row=1] .slick-cell.l3.r5.active.editable .editor-text').type('Support.xyz{enter}'); }); it('should have changed active cell to "Check Email" between 9:00-10:30am', () => { - cy.get('[data-row=2] > .slick-cell.l2.r4.active.editable .editor-text') + cy.get('[data-row=2] .slick-cell.l2.r4.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Check Mail')); - cy.get('[data-row=2] > .slick-cell.l2.r4.active.editable .editor-text').type('Check Mail.xyz{enter}'); + cy.get('[data-row=2] .slick-cell.l2.r4.active.editable .editor-text').type('Check Mail.xyz{enter}'); }); it('should have changed active cell to "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=4] > .slick-cell.l2.r5.active.editable .editor-text') + cy.get('[data-row=4] .slick-cell.l2.r5.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Task Assign')); - cy.get('[data-row=4] > .slick-cell.l2.r5.active.editable .editor-text').type('Task Assign.xyz{enter}'); + cy.get('[data-row=4] .slick-cell.l2.r5.active.editable .editor-text').type('Task Assign.xyz{enter}'); }); it('should have changed active cell to "Support" between 10:00-11:30am', () => { - cy.get('[data-row=5] > .slick-cell.l4.r6.active.editable .editor-text') + cy.get('[data-row=5] .slick-cell.l4.r6.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Support')); - cy.get('[data-row=5] > .slick-cell.l4.r6.active.editable .editor-text').type('Support.xyz{enter}'); + cy.get('[data-row=5] .slick-cell.l4.r6.active.editable .editor-text').type('Support.xyz{enter}'); }); it('should have changed active cell to "Testing" and cancel editing when typing "Escape" key', () => { - cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text') + cy.get('[data-row=6] .slick-cell.l4.r4.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Testing')); - cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text').type('{esc}'); - cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text').should('not.exist'); + cy.get('[data-row=6] .slick-cell.l4.r4.active.editable .editor-text').type('{esc}'); + cy.get('[data-row=6] .slick-cell.l4.r4.active.editable .editor-text').should('not.exist'); }); }); @@ -418,32 +432,32 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 0 }, }); it('should expect EmployeeID to follow columns at index 0 column index', () => { - cy.get(`[data-row=0] > .slick-cell.l0.r0.rowspan`).should('contain', 'Davolio'); - cy.get(`[data-row=0] > .slick-cell.l0.r0.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l0.r0.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] .slick-cell.l0.r0.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=2] > .slick-cell.l1.r3.rowspan`).should('contain', 'Check Mail'); - cy.get(`[data-row=2] > .slick-cell.l1.r3.rowspan`).should(($el) => + cy.get(`[data-row=2] .slick-cell.l1.r3.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l1.r3.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=8] > .slick-cell.l6.r8.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=8] > .slick-cell.l6.r8.rowspan`).should(($el) => + cy.get(`[data-row=8] .slick-cell.l6.r8.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] .slick-cell.l6.r8.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); }); it('should expect "Lunch Break" to be moved to the left by 1 index less', () => { - cy.get(`[data-row=0] > .slick-cell.l9.r11.rowspan`).should('contain', 'Lunch Break'); - cy.get(`[data-row=0] > .slick-cell.l9.r11.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l9.r11.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l9.r11.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10) ); }); it('should expect "Development" to be moved to the left by 1 index less', () => { - cy.get(`[data-row=1] > .slick-cell.l12.r13.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=1] > .slick-cell.l12.r13.rowspan`).should(($el) => + cy.get(`[data-row=1] .slick-cell.l12.r13.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l12.r13.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) ); }); @@ -455,32 +469,32 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 0 }, }); it('should expect EmployeeID to follow columns at index 1 column index', () => { - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should(($el) => + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should(($el) => + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); }); it('should expect "Lunch Break" to be moved to the right by 1 index less', () => { - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10) ); }); it('should expect "Development" to be moved to the right by 1 index less and a large "Development" section that spans over multiple columns & rows in the afternoon', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should(($el) => + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) ); }); diff --git a/demos/aurelia/test/cypress/e2e/example44.cy.ts b/demos/aurelia/test/cypress/e2e/example44.cy.ts index 5d5bf6f477..451c07efd7 100644 --- a/demos/aurelia/test/cypress/e2e/example44.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example44.cy.ts @@ -30,8 +30,14 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { cy.get('h2').should('contain', 'Example 44: colspan/rowspan with large dataset'); }); + it('should hide sub-title', () => { + cy.get('[data-test=toggle-subtitle]').click(); + }); + it('should calculate a height that fits the wrapped Revenue Growth header', () => { - cy.get('.slick-header-auto-height').should('have.length', 2); + // The pinning POC uses one live header instead of separate left/right + // header panes, so auto-height is applied to a single header element. + cy.get('.slick-header-auto-height').should('have.length', 1); cy.get('.slick-header-auto-height') .first() .should(($header) => { @@ -58,7 +64,7 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { let draggedColumn: HTMLElement; cy.clock(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).its('0.scrollLeft').should('equal', 0); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0).its('0.scrollLeft').should('equal', 0); cy.get('.slick-header-columns-left').then(($header) => { const sortableProperty = Object.keys($header[0]).find((property) => property.startsWith('Sortable')); @@ -75,12 +81,12 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); cy.tick(300); - cy.get('.slick-viewport-top.slick-viewport-left').its('0.scrollLeft').should('be.greaterThan', 0); + cy.get('.slick-horizontal-scroller').its('0.scrollLeft').should('be.greaterThan', 0); cy.then(() => sortInstance.options.onEnd({ item: draggedColumn, stopPropagation: () => {} })); cy.get('.slick-header-column:nth(0)').should('contain', 'Title'); cy.get('.slick-header-column:nth(1)').should('contain', 'Revenue Growth'); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0); + cy.get('.slick-vertical-scroller').scrollTo(0, 0); }); it('should drag Title column to swap with 2nd column "Revenue Growth" in the grid and expect rowspan to stay at same position with Task 0 to spread instead', () => { @@ -208,7 +214,7 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); it('should scroll to the right and still expect spans without any extra texts', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(400, 0).wait(10); + cy.get('.slick-horizontal-scroller').scrollTo(400, 0).wait(10); cy.get(`[data-row=3] > .slick-cell:nth(1)`).contains(/\d+$/); cy.get(`[data-row=3] > .slick-cell.l1.r2.rowspan`).should('exist'); @@ -229,7 +235,7 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); it('should scroll back to left and expect Task 8 to have 2 different spans (Revenue Grow: rowspan=80, Policy Index: rowspan=2000,colspan=2)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).wait(10); + cy.get('.slick-vertical-scroller').scrollTo(0, 0).wait(10); cy.get(`[data-row=8] > .slick-cell.l0.r0`).should('contain', 'Task 8'); cy.get(`[data-row=8] > .slick-cell.l1.r1.rowspan`).should(($el) => { @@ -350,6 +356,10 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); it('should start at RevenueGrowth column on first dashed cell, then type "Ctrl+End" then "Ctrl+Home" keys and expect active cell to go to bottom/top of grid on same column', () => { + // The preceding span/scroll cases share state. Reset both axes through + // the POC's actual scroll owners before interacting with the first row. + cy.get('.slick-horizontal-scroller').scrollTo(0, 0); + cy.get('.slick-vertical-scroller').scrollTo(0, 0); cy.get('[data-row=0] > .slick-cell.l2.r2').as('active_cell').click(); cy.get('[data-row=0] > .slick-cell.l2.r2.active').should('have.length', 1); cy.get('@active_cell').type('{ctrl}{end}', { release: false }); diff --git a/demos/aurelia/test/cypress/e2e/example45.cy.ts b/demos/aurelia/test/cypress/e2e/example45.cy.ts index 6f56e1a753..b4485b498b 100644 --- a/demos/aurelia/test/cypress/e2e/example45.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example45.cy.ts @@ -224,9 +224,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { }); it('should scroll down when the row detail is just barely visible and then scroll back up and still expect same filters/sorting', () => { - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 350); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 350); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should('contain', '10281'); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); @@ -236,9 +236,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { it('should scroll down by 2 pages down and then scroll back up and no longer the same filters/sorting', () => { cy.get('#grid45 [data-row="0"] .slick-cell.r2.l2').first().click().type('{pagedown}'); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 2000); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 2000); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should( 'not.contain', @@ -419,7 +419,7 @@ describe('Example 45 - Row Detail with inner Grid', () => { cy.get('#grid45').type('{pageDown}{pageDown}', { release: false }); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 350); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 350); // expect same grid details for both grids // 2nd row detail @@ -520,9 +520,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { }); it('should scroll far down (out of viewport) and back up and expect inner grid sort/filter state is PRESERVED (keepComponentAlive)', () => { - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 800); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 800); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); // state should be PRESERVED because keepComponentAlive is enabled cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should('contain', '10281'); @@ -532,9 +532,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { }); it('should scroll out of viewport a second time and back up and still expect inner grid sort/filter state is PRESERVED', () => { - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 800); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 800); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should('contain', '10281'); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); diff --git a/demos/aurelia/test/cypress/e2e/example47.cy.ts b/demos/aurelia/test/cypress/e2e/example47.cy.ts index 936cb3002e..3e5480a883 100644 --- a/demos/aurelia/test/cypress/e2e/example47.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example47.cy.ts @@ -101,7 +101,7 @@ describe('Example 47 - Row Detail View + Grouping', () => { cy.get('.detail-label label').should('contain', 'Assignee:'); cy.get('.detail-label input').should('exist'); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('.dynamic-cell-detail').find('[data-test=delete-btn]').click(); cy.get('.toast.text-bg-danger').contains(/Deleted row with Task [0-9]*/); cy.get('.dynamic-cell-detail').should('have.length', 0); @@ -112,7 +112,7 @@ describe('Example 47 - Row Detail View + Grouping', () => { cy.on('window:alert', stub); let assigneeName = ''; - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="1"] > .slick-cell.l1').contains(/Task [0-9]*/); cy.get('[data-row="1"] > .slick-cell.l0').click().wait(40); diff --git a/demos/aurelia/test/cypress/e2e/example48.cy.ts b/demos/aurelia/test/cypress/e2e/example48.cy.ts index 5e7f317681..329aac187e 100644 --- a/demos/aurelia/test/cypress/e2e/example48.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example48.cy.ts @@ -153,7 +153,7 @@ describe('Example 48 - Hybrid Selection Model', () => { }); it('should auto scroll take effect to display the selecting element when dragging', { scrollBehavior: false }, () => { - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo('top'); testScroll('#grid48-1', '#grid48-1', 0, 1).then((scrollDistance: { cell: any; row: any }) => { expect(scrollDistance.cell.scrollBefore).to.be.lte(scrollDistance.cell.scrollAfter); @@ -161,11 +161,11 @@ describe('Example 48 - Hybrid Selection Model', () => { }); cy.get('#selectionRange1').contains(/"fromRow":0,"fromCell":1,"toRow":1[45],"toCell":3/); - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo(0, 13 * 35); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo(0, 13 * 35); }); it('should toggle multiple cell selection ranges with the checkbox', () => { - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo('top'); cy.get('[data-test="enable-multi-selection"]').check(); cy.get('#grid48-1 .slick-row[data-row="1"] .slick-cell.l1.r1').click(); @@ -201,7 +201,7 @@ describe('Example 48 - Hybrid Selection Model', () => { }); it('should preserve row and column offsets when copying multiple cell ranges', () => { - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo('top'); cy.get('[data-test="enable-multi-selection"]').should('be.checked'); cy.window().then((win) => { cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWriteText'); @@ -264,7 +264,7 @@ describe('Example 48 - Hybrid Selection Model', () => { }); cy.get('#selectionRange2').contains(/"fromRow":0,"fromCell":0,"toRow":1[0-9],"toCell":7/); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo(0, 12 * 35); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo(0, 12 * 35); }); it('should click on a cell outside of the selected range and expect previous selection to remain', () => { @@ -272,7 +272,7 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('@task1x') .contains(/Task 1[0-9]/) .click(); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-cell.selected').should('have.length.gte', 60); cy.get('#selectionRange2').contains(/"fromRow":0,"fromCell":0,"toRow":1[0-9],"toCell":7/); }); @@ -284,20 +284,20 @@ describe('Example 48 - Hybrid Selection Model', () => { it('should click on row 4 and 5 row checkbox and expect 5 full rows to be selected', () => { cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l1.r1').should('contain', '4'); cy.get('#grid48-2 .slick-row[data-row="4"] input[type=checkbox]').click({ force: true }); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 1); // select another row cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l1.r1').should('contain', '5'); cy.get('#grid48-2 .slick-row[data-row="5"] input[type=checkbox]').click({ force: true }); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 2); }); it('should toggle multiple row selection ranges with the checkbox', () => { - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="4"] input[type=checkbox]').uncheck({ force: true }); cy.get('#grid48-2 .slick-row[data-row="5"] input[type=checkbox]').uncheck({ force: true }); cy.get('[data-test="enable-multi-selection"]').should('be.checked'); @@ -321,7 +321,7 @@ describe('Example 48 - Hybrid Selection Model', () => { const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; const combinedRanges = `${firstRange}${secondRange}`; - cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-vertical-scroller`).scrollTo('top'); cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); diff --git a/demos/aurelia/test/cypress/e2e/example55.cy.ts b/demos/aurelia/test/cypress/e2e/example55.cy.ts index 37ecd02a59..3a8477ecf3 100644 --- a/demos/aurelia/test/cypress/e2e/example55.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example55.cy.ts @@ -38,7 +38,7 @@ describe('Example 55 - Variable Row Height (provider)', { retries: 1 }, () => { it('should keep row 90 aligned at top after clicking scroll button', () => { cy.get('[data-test="scroll-row-90-example55"]').click(); - cy.get('.slick-viewport-top.slick-viewport-left') + cy.get('.slick-vertical-scroller') .invoke('scrollTop') .then((scrollTop) => { expect(Number(scrollTop)).to.be.closeTo(topOf(90), 2); diff --git a/demos/aurelia/test/cypress/e2e/example56.cy.ts b/demos/aurelia/test/cypress/e2e/example56.cy.ts index c02518e284..e30d1b7f00 100644 --- a/demos/aurelia/test/cypress/e2e/example56.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example56.cy.ts @@ -1,6 +1,6 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () => { const BASE_ROW_HEIGHT = 40; - const FROZEN_ROW_COUNT = 2; + const PINNED_ROW_COUNT = 2; const hDefault = (r: number) => { const cycle = [33, 44, 44, 80]; @@ -18,36 +18,41 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () return t; }; - const frozenTopHeight = (hOf: (row: number) => number) => topOf(FROZEN_ROW_COUNT, hOf); + const pinnedTopHeight = (hOf: (row: number) => number) => topOf(PINNED_ROW_COUNT, hOf); const relativeTopInCanvas = (r: number, hOf: (row: number) => number) => { - if (r < FROZEN_ROW_COUNT) { - return topOf(r, hOf); - } - return topOf(r, hOf) - frozenTopHeight(hOf); + // Pinned rows are moved into the overlay, but center rows retain their + // natural document coordinates behind that overlay. + return topOf(r, hOf); }; - const canvasSelector = (r: number) => (r < FROZEN_ROW_COUNT ? '.grid-canvas-top' : '.grid-canvas-bottom'); + const rowHostSelector = (r: number) => (r < PINNED_ROW_COUNT ? '.slick-docking-overlay' : '.grid-canvas-top'); const assertRowStyle = (row: number, hOf: (row: number) => number) => { const expectedHeight = hOf(row); const expectedTop = relativeTopInCanvas(row, hOf); - cy.get(`${canvasSelector(row)} .slick-row[data-row=${row}]`) + cy.get(`${rowHostSelector(row)} .slick-row[data-row=${row}]`) .should('have.attr', 'style') .and('contain', `transform: translateY(${expectedTop}px)`) .then((style) => { if (expectedHeight !== BASE_ROW_HEIGHT) { expect(style).to.contain(`height: ${expectedHeight}px`); } else { - expect(style).not.to.contain('height:'); + // Docked rows carry their resolved height inline so editor/content + // styles cannot collapse the pinned row. The base-height case is + // therefore valid with either the stylesheet fallback or an + // explicit `height: 40px` declaration. + expect(style).to.match(/(?:height: 40px;|^(?!.*height:))/); } }); - cy.get(`[data-row="${row}"] > .slick-cell:nth(3)`).should('contain', `${expectedHeight}px`); + // Rows are split into left/center/right docking regions, so cells are + // nested under their region wrapper rather than being direct row children. + cy.get(`[data-row="${row}"] .slick-cell:nth(3)`).should('contain', `${expectedHeight}px`); }; const ensureDefaultDensity = () => { - cy.get('.grid-canvas-top .slick-row[data-row=1]') + cy.get('.slick-docking-overlay .slick-row[data-row=1]') .invoke('attr', 'style') .then((style) => { if ((style ?? '').includes('height: 50px')) { @@ -55,7 +60,7 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () } }); - cy.get('.grid-canvas-top .slick-row[data-row=1]').should('have.attr', 'style').and('contain', 'height: 44px'); + cy.get('.slick-docking-overlay .slick-row[data-row=1]').should('have.attr', 'style').and('contain', 'height: 44px'); }; beforeEach(() => { @@ -67,7 +72,7 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () cy.get('h2').should('contain', 'Example 56: Variable Row Height (item metadata)'); }); - it('should render frozen and scrollable rows with expected transform and row heights from metadata fallback', () => { + it('should render pinned and scrollable rows with expected transform and row heights from metadata fallback', () => { for (const r of [0, 1, 2, 3, 4, 5, 6]) { assertRowStyle(r, hDefault); } @@ -85,12 +90,12 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () } }); - it('should scroll row 90 to top of scrollable pane with frozen top rows', () => { - const expectedScrollTop = topOf(90, hDefault) - frozenTopHeight(hDefault); + it('should scroll row 90 to top of scrollable pane with pinned top rows', () => { + const expectedScrollTop = topOf(90, hDefault) - pinnedTopHeight(hDefault); cy.get('[data-test="scroll-row-90-example56"]').click(); - cy.get('.slick-viewport-bottom.slick-viewport-left').should(($viewport) => { + cy.get('.slick-vertical-scroller').should(($viewport) => { expect($viewport.scrollTop()).to.be.closeTo(expectedScrollTop, 2); }); diff --git a/demos/aurelia/test/cypress/e2e/example57.cy.ts b/demos/aurelia/test/cypress/e2e/example57.cy.ts index df73fe98f8..7055d540b1 100644 --- a/demos/aurelia/test/cypress/e2e/example57.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example57.cy.ts @@ -54,14 +54,14 @@ describe('Example 57 - RTL (Right-to-Left)', () => { describe('Scrolling Behavior', () => { it('should have horizontal scroll enabled', () => { - cy.get('#grid57 .slick-viewport').then(($viewport) => { + cy.get('#grid57 .slick-horizontal-scroller').then(($viewport) => { const viewport = $viewport[0] as HTMLElement; expect(viewport.scrollWidth).to.be.greaterThan(viewport.clientWidth); }); }); it('should update visible header columns when scrolling', () => { - cy.get('#grid57 .slick-viewport').then(($viewport) => { + cy.get('#grid57 .slick-horizontal-scroller').then(($viewport) => { const viewport = $viewport[0] as HTMLElement; const maxScroll = viewport.scrollWidth - viewport.clientWidth; viewport.scrollLeft = maxScroll; @@ -72,7 +72,7 @@ describe('Example 57 - RTL (Right-to-Left)', () => { cy.wait(150); - cy.get('#grid57 .slick-viewport').then(($viewport) => { + cy.get('#grid57 .slick-horizontal-scroller').then(($viewport) => { const viewport = $viewport[0] as HTMLElement; expect(Math.abs(viewport.scrollLeft)).to.be.greaterThan(0); }); diff --git a/demos/aurelia/test/cypress/support/drag.ts b/demos/aurelia/test/cypress/support/drag.ts index 8228176a5a..b26012a988 100644 --- a/demos/aurelia/test/cypress/support/drag.ts +++ b/demos/aurelia/test/cypress/support/drag.ts @@ -82,15 +82,22 @@ export function getScrollDistanceWhenDragOutsideGrid( return (cy as any).convertPosition(viewport).then((_viewportPosition: { x: number; y: number }) => { const viewportSelector = `${selector} .slick-viewport-${_viewportPosition.x}.slick-viewport-${_viewportPosition.y}`; (cy as any).getNthCell(fromRow, fromCol, viewport, { parentSelector: selector }).dragStart(); - return cy.get(viewportSelector).then(($viewport) => { - const scrollTopBefore = $viewport.scrollTop(); - const scrollLeftBefore = $viewport.scrollLeft(); + return cy.get(selector).then(($grid) => { + const viewport = ($grid.find(viewportSelector)[0] || $grid.find('.slick-vertical-scroller')[0]) as HTMLElement; + const horizontalScroller = $grid.find('.slick-horizontal-scroller')[0] as HTMLElement | undefined; + const horizontalOwner = horizontalScroller || viewport; + const scrollTopBefore = viewport.scrollTop; + const scrollLeftBefore = horizontalOwner.scrollLeft; cy.dragOutside(dragDirection, 300, px, { parentSelector: selector }); - return cy.get(viewportSelector).then(($viewportAfter) => { + return cy.get(selector).then(($gridAfter) => { + const viewportAfter = ($gridAfter.find(viewportSelector)[0] || $gridAfter.find('.slick-vertical-scroller')[0]) as HTMLElement; + const horizontalScrollerAfter = $gridAfter.find('.slick-horizontal-scroller')[0] as HTMLElement | undefined; + const horizontalOwnerAfter = horizontalScrollerAfter || viewportAfter; cy.dragEnd(selector); - const scrollTopAfter = $viewportAfter.scrollTop(); - const scrollLeftAfter = $viewportAfter.scrollLeft(); - cy.get(viewportSelector).scrollTo(0, 0, { ensureScrollable: false }); + const scrollTopAfter = viewportAfter.scrollTop; + const scrollLeftAfter = horizontalOwnerAfter.scrollLeft; + horizontalOwnerAfter.scrollLeft = 0; + viewportAfter.scrollTop = 0; return cy.wrap({ scrollTopBefore, scrollLeftBefore, diff --git a/demos/react-fluent/src/examples/slickgrid/Example05.tsx b/demos/react-fluent/src/examples/slickgrid/Example05.tsx index e5c9206e1d..127472e9c3 100644 --- a/demos/react-fluent/src/examples/slickgrid/Example05.tsx +++ b/demos/react-fluent/src/examples/slickgrid/Example05.tsx @@ -22,9 +22,9 @@ const Example05: React.FC = () => { const [dataset] = useState(getData()); const [gridOptions, setGridOptions] = useState(undefined); const [darkModeGrid, setDarkModeGrid] = useState(false); - const [frozenColumnCount, setFrozenColumnCount] = useState(2); - const [frozenRowCount, setFrozenRowCount] = useState(3); - const [isFrozenBottom, setIsFrozenBottom] = useState(false); + const [pinnedColumnCount, setPinnedColumnCount] = useState(2); + const [pinnedRowCount, setPinnedRowCount] = useState(3); + const [isPinnedBottom, setIsPinnedBottom] = useState(false); const reactGridRef = useRef(null); const slickEventHandler = new SlickEventHandler(); @@ -41,8 +41,7 @@ const Example05: React.FC = () => { function reactGridReady(reactGrid: SlickgridReactInstance) { reactGridRef.current = reactGrid; - // with frozen (pinned) grid, in order to see the entire row being highlighted when hovering - // we need to do some extra tricks (that is because frozen grids use 2 separate div containers) + // With a pinned grid, the entire row is highlighted through its shared row regions. // the trick is to use row selection to highlight when hovering current row and remove selection once we're not slickEventHandler.subscribe(reactGridRef.current?.slickGrid.onMouseEnter, (event) => highlightRow(event, true)); slickEventHandler.subscribe(reactGridRef.current?.slickGrid.onMouseLeave, (event) => highlightRow(event, false)); @@ -253,13 +252,11 @@ const Example05: React.FC = () => { editable: true, autoEdit: true, enableExcelCopyBuffer: true, - frozenColumn: 2, - frozenRow: 3, - // frozenBottom: true, // if you want to freeze the bottom instead of the top, you can enable this property + pinning: { columns: { left: 2 }, rows: { top: [0, 1, 2] } }, - // show both Frozen Columns in HeaderMenu & GridMenu, these are opt-in commands so they're disabled by default - gridMenu: { hideClearFrozenColumnsCommand: false }, - headerMenu: { hideFreezeColumnsCommand: false }, + // show both single-column and bulk pinning commands in HeaderMenu & GridMenu; these are opt-in commands + gridMenu: { hideClearPinningCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, ...baseFluentGridOption, }; @@ -290,26 +287,26 @@ const Example05: React.FC = () => { } /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - function changeFrozenColumnCount(e: React.FormEvent) { - const frozenColumn = +((e.target as HTMLInputElement)?.value ?? 0); - setFrozenColumnCount(frozenColumn); + function changePinnedColumnCount(e: React.FormEvent) { + const pinnedColumn = +((e.target as HTMLInputElement)?.value ?? 0); + setPinnedColumnCount(pinnedColumn); } - function updateFrozenColumnCount() { + function updatePinnedColumnCount() { reactGridRef.current?.slickGrid?.setOptions({ - frozenColumn: frozenColumnCount, + pinning: { columns: { left: pinnedColumnCount } }, }); } /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - function changeFrozenRowCount(e: React.FormEvent) { - const frozenRow = +((e.target as HTMLInputElement)?.value ?? 0); - setFrozenRowCount(frozenRow); + function changePinnedRowCount(e: React.FormEvent) { + const pinnedRow = +((e.target as HTMLInputElement)?.value ?? 0); + setPinnedRowCount(pinnedRow); } - function updateFrozenRowCount() { + function updatePinnedRowCount() { reactGridRef.current?.slickGrid?.setOptions({ - frozenRow: frozenRowCount, + pinning: { rows: { top: Array.from({ length: Math.max(0, pinnedRowCount) }, (_value, index) => index) } }, }); } @@ -330,8 +327,8 @@ const Example05: React.FC = () => { showToast(args.validationResults.msg, 'danger'); } - function setFrozenColumns(frozenCols: number) { - reactGridRef.current?.slickGrid.setOptions({ frozenColumn: frozenCols }); + function setPinnedColumns(pinnedCols: number) { + reactGridRef.current?.slickGrid.setOptions({ pinning: { columns: { left: pinnedCols } } }); const updatedGridOptions = reactGridRef.current?.slickGrid.getOptions(); setGridOptions(updatedGridOptions); } @@ -348,20 +345,20 @@ const Example05: React.FC = () => { } /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - function toggleFrozenBottomRows() { + function togglePinnedBottomRows() { reactGridRef.current?.slickGrid.setOptions({ - frozenBottom: !isFrozenBottom, + pinning: { rows: { top: isPinnedBottom ? [0, 1, 2] : [], bottom: isPinnedBottom ? [] : [0, 1, 2] } }, }); - const newIsFrozenBottom = !isFrozenBottom; - setIsFrozenBottom(newIsFrozenBottom); + const newIsPinnedBottom = !isPinnedBottom; + setIsPinnedBottom(newIsPinnedBottom); } return !gridOptions ? ( '' ) : (
-

Example 5: Pinned (frozen) Columns/Rows

+

Example 5: Pinned Columns/Rows

@@ -370,10 +367,10 @@ const Example05: React.FC = () => { changeFrozenRowCount($event)} + defaultValue={pinnedRowCount} + onInput={($event) => changePinnedRowCount($event)} /> - @@ -382,10 +379,10 @@ const Example05: React.FC = () => { changeFrozenColumnCount($event)} + defaultValue={pinnedColumnCount} + onInput={($event) => changePinnedColumnCount($event)} /> - @@ -398,17 +395,17 @@ const Example05: React.FC = () => { Toggle Dark Mode - - - - : {isFrozenBottom ? 'Bottom' : 'Top'} + : {isPinnedBottom ? 'Bottom' : 'Top'}
diff --git a/demos/react-fluent/src/examples/slickgrid/example05.scss b/demos/react-fluent/src/examples/slickgrid/example05.scss index 6e31054384..e41265a0e7 100644 --- a/demos/react-fluent/src/examples/slickgrid/example05.scss +++ b/demos/react-fluent/src/examples/slickgrid/example05.scss @@ -1,8 +1,8 @@ -/** You can change the pinned/frozen border styling through this css override */ +/** You can change the pinned/pinned border styling through this css override */ -.slick-row .slick-cell.frozen:last-child, -.slick-headerrow-column.frozen:last-child, -.slick-footerrow-column.frozen:last-child { +.slick-row .slick-cell.pinned:last-child, +.slick-headerrow-column.pinned:last-child, +.slick-footerrow-column.pinned:last-child { border-right: 1px solid #969696 !important; } diff --git a/demos/react/src/assets/locales/en/translation.json b/demos/react/src/assets/locales/en/translation.json index b412eea6d6..600e68cdc1 100644 --- a/demos/react/src/assets/locales/en/translation.json +++ b/demos/react/src/assets/locales/en/translation.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Clear all Filters", "CLEAR_ALL_GROUPING": "Clear all Grouping", "CLEAR_ALL_SORTING": "Clear all Sorting", - "CLEAR_PINNING": "Unfreeze Columns/Rows", + "CLEAR_PINNING": "Unpin Columns/Rows", "CLONE": "Clone", "COLLAPSE_ALL_GROUPS": "Collapse all Groups", "COLUMNS": "Columns", @@ -28,7 +28,10 @@ "FILTER_SHORTCUTS": "Filter Shortcuts", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items", "FORCE_FIT_COLUMNS": "Force fit columns", - "FREEZE_COLUMNS": "Freeze Columns", + "PIN_COLUMN": "Column Pinning", + "PIN_COLUMNS": "Pin Through Here", + "PIN_LEFT": "Pin Left", + "PIN_RIGHT": "Pin Right", "INVALID_FLOAT": "The number must be valid and have a maximum of {{maxDecimal}} decimals.", "GREATER_THAN": "Greater than", "GREATER_THAN_OR_EQUAL_TO": "Greater than or equal to", @@ -63,7 +66,8 @@ "SYNCHRONOUS_RESIZE": "Synchronous resize", "TOGGLE_FILTER_ROW": "Toggle Filter Row", "TOGGLE_PRE_HEADER_ROW": "Toggle Pre-Header Row", - "UNFREEZE_COLUMNS": "Unfreeze Columns", + "UNPIN_COLUMN": "Unpin Column", + "UNPIN_COLUMNS": "Unpin All Columns", "X_OF_Y_SELECTED": "# of % selected", "X_OF_Y_MASS_SELECTED": "{{x}} of {{y}} selected", "BILLING": { diff --git a/demos/react/src/assets/locales/fr/translation.json b/demos/react/src/assets/locales/fr/translation.json index c957bb46b8..832f0207b7 100644 --- a/demos/react/src/assets/locales/fr/translation.json +++ b/demos/react/src/assets/locales/fr/translation.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Supprimer tous les filtres", "CLEAR_ALL_GROUPING": "Supprimer tous les groupes", "CLEAR_ALL_SORTING": "Supprimer tous les tris", - "CLEAR_PINNING": "Dégeler les colonnes/rangées", + "CLEAR_PINNING": "Désépingler les colonnes/rangées", "CLONE": "Cloner", "COLLAPSE_ALL_GROUPS": "Réduire tous les groupes", "COLUMNS": "Colonnes", @@ -28,7 +28,10 @@ "FILTER_SHORTCUTS": "Raccourcis de filtre", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments", "FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes", - "FREEZE_COLUMNS": "Geler les colonnes", + "PIN_COLUMN": "Épinglage de colonnes", + "PIN_COLUMNS": "Épingler jusqu'ici", + "PIN_LEFT": "Épingler à gauche", + "PIN_RIGHT": "Épingler à droite", "GREATER_THAN": "Plus grand que", "GREATER_THAN_OR_EQUAL_TO": "Plus grand ou égal à", "GROUP_BY": "Grouper par", @@ -63,7 +66,8 @@ "SYNCHRONOUS_RESIZE": "Redimension synchrone", "TOGGLE_FILTER_ROW": "Basculer la ligne des filtres", "TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête", - "UNFREEZE_COLUMNS": "Dégeler les colonnes", + "UNPIN_COLUMN": "Désépingler la colonne", + "UNPIN_COLUMNS": "Désépingler toutes les colonnes", "X_OF_Y_SELECTED": "# de % sélectionnés", "X_OF_Y_MASS_SELECTED": "{{x}} de {{y}} sélectionnés", "BILLING": { diff --git a/demos/react/src/examples/slickgrid/Example07.tsx b/demos/react/src/examples/slickgrid/Example07.tsx index 96db83d625..71a44a2f5d 100644 --- a/demos/react/src/examples/slickgrid/Example07.tsx +++ b/demos/react/src/examples/slickgrid/Example07.tsx @@ -58,8 +58,6 @@ const Example7: React.FC = () => { ...gridOptions1, enableHeaderMenu: true, enableFiltering: true, - // frozenColumn: 2, - // frozenRow: 2, headerButton: { onCommand: (_e, args) => handleOnCommand(_e, args, 2), }, diff --git a/demos/react/src/examples/slickgrid/Example14.tsx b/demos/react/src/examples/slickgrid/Example14.tsx index b0c88d91de..0b0ba6a73f 100644 --- a/demos/react/src/examples/slickgrid/Example14.tsx +++ b/demos/react/src/examples/slickgrid/Example14.tsx @@ -49,6 +49,7 @@ const Example14: React.FC = () => { createPreHeaderPanel: true, showPreHeaderPanel: true, preHeaderPanelHeight: 28, + rowHeight: 33, gridHeight: 275, gridWidth: 800, enableExcelExport: true, @@ -101,14 +102,15 @@ const Example14: React.FC = () => { explicitInitialization: true, gridHeight: 275, gridWidth: 800, - frozenColumn: 2, + rowHeight: 33, + pinning: { columns: { left: 2 } }, enableExcelExport: true, excelExportOptions: { exportWithFormatter: false, }, externalResources: [new ExcelExportService(), new PdfExportService()], - gridMenu: { hideClearFrozenColumnsCommand: false }, - headerMenu: { hideFreezeColumnsCommand: false }, + gridMenu: { hideClearPinningCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, }; setColumns2(columns2); @@ -133,8 +135,8 @@ const Example14: React.FC = () => { return mockDataset; } - function setFrozenColumns2(frozenCols: number) { - reactGridRef2.current?.slickGrid.setOptions({ frozenColumn: frozenCols }); + function setPinnedColumns2(pinnedCols: number) { + reactGridRef2.current?.slickGrid.setOptions({ pinning: { columns: { left: pinnedCols } } }); const updatedGridOptions = reactGridRef2.current?.slickGrid.getOptions(); setGridOptions2(updatedGridOptions); } @@ -230,23 +232,23 @@ const Example14: React.FC = () => {

- Grid 2 (with Header Grouping & Frozen/Pinned Columns) + Grid 2 (with Header Grouping & Pinned Columns)

diff --git a/demos/react/src/examples/slickgrid/Example15.tsx b/demos/react/src/examples/slickgrid/Example15.tsx index 82c2889f62..c9264c6a16 100644 --- a/demos/react/src/examples/slickgrid/Example15.tsx +++ b/demos/react/src/examples/slickgrid/Example15.tsx @@ -72,10 +72,11 @@ const Example15: React.FC = () => { }, gridMenu: { hideForceFitButton: true, - hideClearFrozenColumnsCommand: false, + hideClearPinningCommand: false, }, headerMenu: { - hideFreezeColumnsCommand: false, + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, }, enablePagination: true, pagination: { diff --git a/demos/react/src/examples/slickgrid/Example20.tsx b/demos/react/src/examples/slickgrid/Example20.tsx index b785c6bd6a..ef2b56fd72 100644 --- a/demos/react/src/examples/slickgrid/Example20.tsx +++ b/demos/react/src/examples/slickgrid/Example20.tsx @@ -1,10 +1,10 @@ +import { ExcelExportService } from '@slickgrid-universal/excel-export'; import React, { useEffect, useRef, useState } from 'react'; import { Editors, Filters, formatNumber, Formatters, - SlickEventHandler, SlickgridReact, type Column, type ColumnEditorDualInput, @@ -18,54 +18,30 @@ const Example20: React.FC = () => { const [columns, setColumns] = useState([]); const [dataset] = useState(getData()); const [gridOptions, setGridOptions] = useState(undefined); - const [frozenColumnCount, setFrozenColumnCount] = useState(2); - const [frozenRowCount, setFrozenRowCount] = useState(3); - const [isFrozenBottom, setIsFrozenBottom] = useState(false); + const [pinnedColumnCount, setPinnedColumnCount] = useState(2); + const [pinnedRowCount, setPinnedRowCount] = useState(3); + const [pinnedRightColumnCount, setPinnedRightColumnCount] = useState(1); + const [isPinnedBottom, setIsPinnedBottom] = useState(false); const [hideSubTitle, setHideSubTitle] = useState(false); + const [isSelectAllShownAsColumnTitle, setIsSelectAllShownAsColumnTitle] = useState(false); + const checkboxSelectorRef = useRef(null); + const pinnedColumnCountRef = useRef(2); + const pinnedRightColumnInputRef = useRef(null); + const pinnedRightColumnCountRef = useRef(1); const reactGridRef = useRef(null); - const slickEventHandler = new SlickEventHandler(); useEffect(() => { defineGrid(); - - // when unmounting - return () => { - slickEventHandler.unsubscribeAll(); - }; }, []); function reactGridReady(reactGrid: SlickgridReactInstance) { reactGridRef.current = reactGrid; - - // with frozen (pinned) grid, in order to see the entire row being highlighted when hovering - // we need to do some extra tricks (that is because frozen grids use 2 separate div containers) - // the trick is to use row selection to highlight when hovering current row and remove selection once we're not - slickEventHandler.subscribe(reactGridRef.current?.slickGrid.onMouseEnter, (event) => highlightRow(event, true)); - slickEventHandler.subscribe(reactGridRef.current?.slickGrid.onMouseLeave, (event) => highlightRow(event, false)); - } - - function highlightRow(event: any, isMouseEnter: boolean) { - const cell = reactGridRef.current?.slickGrid.getCellFromEvent(event); - const rows = isMouseEnter ? [cell?.row ?? 0] : []; - reactGridRef.current?.slickGrid.setSelectedRows(rows); // highlight current row - event.preventDefault(); } /* Define grid Options and Columns */ function defineGrid() { const columns: Column[] = [ - { - id: 'sel', - name: '#', - field: 'id', - minWidth: 40, - width: 40, - maxWidth: 40, - cannotTriggerInsert: true, - resizable: false, - unselectable: true, - }, { id: 'title', name: 'Title', @@ -82,7 +58,6 @@ const Example20: React.FC = () => { resizable: false, minWidth: 130, width: 140, - formatter: Formatters.percentCompleteBar, type: 'number', filterable: true, filter: { model: Filters.slider, operator: '>=' }, @@ -92,29 +67,44 @@ const Example20: React.FC = () => { id: 'start', name: 'Start', field: 'start', - minWidth: 100, - width: 120, + type: 'dateIso', filterable: true, sortable: true, formatter: Formatters.dateIso, + filter: { model: Filters.compoundDate }, }, { id: 'finish', name: 'Finish', field: 'finish', - minWidth: 100, - width: 120, + type: 'dateIso', filterable: true, sortable: true, formatter: Formatters.dateIso, + filter: { model: Filters.compoundDate }, + }, + { + id: 'completed', + name: 'Completed', + field: 'completed', + sortable: true, + filterable: true, + formatter: Formatters.checkmarkMaterial, + editor: { model: Editors.checkbox }, + filter: { + model: Filters.singleSelect, + collection: [ + { value: '', label: '' }, + { value: true, label: 'True' }, + { value: false, label: 'False' }, + ], + }, }, { id: 'cost', name: 'Cost | Duration', field: 'cost', formatter: costDurationFormatter, - minWidth: 150, - width: 170, sortable: true, // filterable: true, filter: { @@ -184,58 +174,37 @@ const Example20: React.FC = () => { }, }, { - id: 'effortDriven', - name: 'Effort Driven', - field: 'effortDriven', - minWidth: 100, - width: 120, - formatter: Formatters.checkmarkMaterial, - filterable: true, - filter: { - collection: [ - { value: '', label: '' }, - { value: true, label: 'True' }, - { value: false, label: 'False' }, - ], - model: Filters.singleSelect, - }, - sortable: true, - }, - { - id: 'title1', - name: 'Title 1', - field: 'title1', + id: 'cityOfOrigin', + name: 'City of Origin', + field: 'cityOfOrigin', minWidth: 100, - width: 120, filterable: true, sortable: true, }, { - id: 'title2', - name: 'Title 2', - field: 'title2', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, - }, - { - id: 'title3', - name: 'Title 3', - field: 'title3', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, - }, - { - id: 'title4', - name: 'Title 4', - field: 'title4', - minWidth: 100, - width: 120, - filterable: true, - sortable: true, + id: 'action', + name: 'Action', + field: 'action', + width: 100, + maxWidth: 100, + excludeFromExport: true, + formatter: () => '
Action
', + cellMenu: { + commandTitle: 'Commands', + commandItems: [ + { command: 'command1', title: 'Command 1' }, + { command: 'command2', title: 'Command 2', itemUsabilityOverride: (args: any) => !args.dataContext.completed }, + { command: 'delete-row', title: 'Delete Row', itemVisibilityOverride: (args: any) => !args.dataContext.completed }, + { divider: true, command: '' }, + { command: 'help', title: 'Help' }, + { command: 'something', title: 'Disabled Command', disabled: true }, + ], + optionTitle: 'Change Complete Flag', + optionItems: [ + { option: true, title: 'True' }, + { option: false, title: 'False' }, + ], + }, }, ]; @@ -244,18 +213,49 @@ const Example20: React.FC = () => { container: '#demo-container', rightPadding: 10, }, - gridWidth: 920, + // Keep the left-pinned columns compact so two right-pinned columns fit + // beside the framework demo's route sidebar. + autoFitColumnsOnFirstLoad: false, enableCellNavigation: true, editable: true, autoEdit: true, enableExcelCopyBuffer: true, - frozenColumn: 2, - frozenRow: 3, - // frozenBottom: true, // if you want to freeze the bottom instead of the top, you can enable this property - - // show both Frozen Columns in HeaderMenu & GridMenu, these are opt-in commands so they're disabled by default - gridMenu: { hideClearFrozenColumnsCommand: false }, - headerMenu: { hideFreezeColumnsCommand: false }, + enableExcelExport: true, + externalResources: [new ExcelExportService()], + enableFiltering: true, + enableSelection: true, + enableCheckboxSelector: true, + selectionOptions: { selectActiveRow: false }, + checkboxSelector: { + hideInColumnTitleRow: !isSelectAllShownAsColumnTitle, + hideInFilterHeaderRow: isSelectAllShownAsColumnTitle, + name: 'Sel', + onExtensionRegistered: (instance: any) => (checkboxSelectorRef.current = instance), + }, + pinning: { columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, rows: { top: [0, 1, 2] } }, + // pinnedBottom: true, // if you want to pin the bottom instead of the top, you can enable this property + + // show both single-column and bulk pinning commands in HeaderMenu & GridMenu; these are opt-in commands + gridMenu: { + hideClearPinningCommand: false, + // Grid Menu visibility updates rebuild the column layout. Reassert the + // demo's current ID-based pinning after that rebuild so React's right + // pinning state cannot briefly fall back to the scrolling band. + onColumnsChanged: () => reapplyPinnedColumns(), + }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, + enableCellMenu: true, + cellMenu: { + onCommand: (_e: unknown, args: any) => executeCommand(args), + onOptionSelected: (_e: unknown, args: any) => { + if (args?.dataContext) { + args.dataContext.completed = args.item.option; + reactGridRef.current?.gridService?.updateItem(args.dataContext); + } + }, + }, + enableContextMenu: true, + contextMenu: getContextMenuOptions(), }; setColumns(columns); @@ -274,40 +274,114 @@ const Example20: React.FC = () => { percentComplete: Math.round(Math.random() * 100), start: new Date(2009, 0, 1), finish: new Date(2009, 4, 5), - effortDriven: i % 5 === 0, - title1: `Some Text ${Math.round(Math.random() * 25)}`, - title2: `Some Text ${Math.round(Math.random() * 25)}`, - title3: `Some Text ${Math.round(Math.random() * 25)}`, - title4: `Some Text ${Math.round(Math.random() * 25)}`, + completed: i % 5 === 0, + cityOfOrigin: i % 2 ? 'Vancouver, BC, Canada' : 'Boston, MA, United States', }; } return mockDataset; } /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - function changeFrozenColumnCount(e: React.FormEvent) { - const frozenColumn = +((e.target as HTMLInputElement)?.value ?? 0); - setFrozenColumnCount(frozenColumn); + function changePinnedColumnCount(e: React.FormEvent) { + setPinnedColumnCount(+(e.target as HTMLInputElement).value || 0); } - function updateFrozenColumnCount() { - reactGridRef.current?.slickGrid?.setOptions({ - frozenColumn: frozenColumnCount, - }); + function updatePinnedColumnCount() { + setPinnedColumns(pinnedColumnCount, pinnedRightColumnCount); } /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - function changeFrozenRowCount(e: React.FormEvent) { - const frozenRow = +((e.target as HTMLInputElement)?.value ?? 0); - setFrozenRowCount(frozenRow); + function changePinnedRowCount(e: React.FormEvent) { + const pinnedRow = +((e.target as HTMLInputElement)?.value ?? 0); + setPinnedRowCount(pinnedRow); } - function updateFrozenRowCount() { + function updatePinnedRowCount() { + const rows = Array.from({ length: Math.max(0, pinnedRowCount) }, (_v, i) => i); reactGridRef.current?.slickGrid?.setOptions({ - frozenRow: frozenRowCount, + pinning: { rows: isPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } }, }); } + function getContextMenuOptions(): any { + const percentItems = [ + { option: 0, title: 'Not Started (0%)' }, + { option: 50, title: 'Half Completed (50%)' }, + { option: 100, title: 'Completed (100%)' }, + ]; + return { + optionShownOverColumnIds: ['percentComplete'], + hideCloseButton: true, + dropSide: 'right', + optionTitle: 'Change Percent Complete', + optionItems: [ + ...percentItems, + 'divider', + { option: null, title: 'Sub-Options (demo)', subMenuTitle: 'Set Percent Complete', optionItems: percentItems }, + ], + commandItems: [ + { command: '', divider: true, positionOrder: 98 }, + { + command: 'export', + title: 'Exports', + positionOrder: 99, + commandItems: [ + { command: 'exports-txt', title: 'Text (tab delimited)' }, + { + command: 'sub-menu', + title: 'Excel', + subMenuTitle: 'available formats', + commandItems: [ + { command: 'exports-csv', title: 'Excel (csv)' }, + { command: 'exports-xlsx', title: 'Excel (xlsx)' }, + ], + }, + ], + }, + { + command: 'feedback', + title: 'Feedback', + positionOrder: 100, + commandItems: [ + { command: 'request-update', title: 'Request update from supplier' }, + 'divider', + { + command: 'sub-menu', + title: 'Contact Us', + subMenuTitle: 'contact us...', + commandItems: [ + { command: 'contact-email', title: 'Email us' }, + { command: 'contact-chat', title: 'Chat with us' }, + { command: 'contact-meeting', title: 'Book an appointment' }, + ], + }, + ], + }, + ], + onOptionSelected: (_e: unknown, args: any) => { + if (args?.dataContext) { + args.dataContext.percentComplete = args.item.option; + reactGridRef.current?.slickGrid?.updateRow(args.row || 0); + } + }, + onCommand: (_e: unknown, args: any) => executeCommand(args), + }; + } + + function executeCommand(args: any) { + if (args.command === 'delete-row') { + if (confirm(`Do you really want to delete row (${args.row + 1}) with "${args.dataContext.title}"?`)) { + reactGridRef.current?.gridService?.deleteItemById(args.dataContext.id); + } + } else if (['command1', 'command2', 'help'].includes(args.command)) { + alert(args.item.title); + } else if (['exports-csv', 'exports-txt', 'exports-xlsx'].includes(args.command)) { + alert(`Exporting as ${args.item.title}`); + } else { + alert(`Command: ${args.command}`); + } + } + function costDurationFormatter(_row: number, _cell: number, _value: any, _columnDef: Column, dataContext: any) { const costText = isNullUndefinedOrEmpty(dataContext.cost) ? 'n/a' : formatNumber(dataContext.cost, 0, 2, false, '$', '', '.', ','); let durationText = 'n/a'; @@ -325,21 +399,76 @@ const Example20: React.FC = () => { showToast(args.validationResults.msg, 'danger'); } - function setFrozenColumns(frozenCols: number) { - reactGridRef.current?.slickGrid.setOptions({ frozenColumn: frozenCols }); - const updatedGridOptions = reactGridRef.current?.slickGrid.getOptions(); - setGridOptions(updatedGridOptions); - setFrozenColumnCount(frozenCols); + function removePinnedColumns() { + setPinnedColumns(-1, 0); + setPinnedColumnCount(0); + } + + function setPinnedColumns(left: number, right = pinnedRightColumnCount) { + const nextRight = Math.max(0, Number(right) || 0); + const leftIds = left >= 0 ? ['_checkbox_selector', 'title', 'percentComplete'].slice(0, left + 1) : []; + const rightIds = ['cityOfOrigin', 'action'].slice(Math.max(0, 2 - nextRight)); + pinnedColumnCountRef.current = left; + pinnedRightColumnCountRef.current = nextRight; + reactGridRef.current?.slickGrid.setOptions({ pinning: { columns: { left: leftIds, right: rightIds } } }); + setPinnedColumnCount(left); + setPinnedRightColumnCount(nextRight); + } + + function reapplyPinnedColumns() { + const left = pinnedColumnCountRef.current; + const right = pinnedRightColumnCountRef.current; + const leftIds = left >= 0 ? ['_checkbox_selector', 'title', 'percentComplete'].slice(0, left + 1) : []; + const rightIds = ['cityOfOrigin', 'action'].slice(Math.max(0, 2 - right)); + reactGridRef.current?.slickGrid.setOptions({ pinning: { columns: { left: leftIds, right: rightIds } } }); + } + + function changePinnedRightColumnCount(e: React.FormEvent) { + setPinnedRightColumnCount(+(e.target as HTMLInputElement).value || 0); + } + + function updatePinnedRightColumnCount() { + const nextRight = +(pinnedRightColumnInputRef.current?.value ?? pinnedRightColumnCount) || 0; + setPinnedColumns(pinnedColumnCount, nextRight); + } + + function toggleRightPinning() { + setPinnedColumns(pinnedColumnCount, pinnedRightColumnCount > 0 ? 0 : 1); + } + + function toggleSelectAllRow() { + const next = !isSelectAllShownAsColumnTitle; + setIsSelectAllShownAsColumnTitle(next); + checkboxSelectorRef.current?.setOptions({ hideInColumnTitleRow: !next, hideInFilterHeaderRow: next }); + } + + function setLargePinnedColumns() { + reactGridRef.current?.gridStateService?.applyColumnLayout?.( + [ + { columnId: '_checkbox_selector', cssClass: 'slick-cell-checkboxsel', headerCssClass: '', width: 40 }, + { columnId: 'title', cssClass: '', headerCssClass: '', width: 220 }, + { columnId: 'percentComplete', cssClass: '', headerCssClass: '', width: 280 }, + { columnId: 'start', cssClass: '', headerCssClass: '', width: 150 }, + { columnId: 'finish', cssClass: '', headerCssClass: '', width: 280 }, + { columnId: 'completed', cssClass: '', headerCssClass: '', width: 180 }, + { columnId: 'cost', cssClass: '', headerCssClass: '', width: 220 }, + { columnId: 'cityOfOrigin', cssClass: '', headerCssClass: '', width: 180 }, + { columnId: 'action', cssClass: '', headerCssClass: '', width: 110 }, + ], + false, + false + ); + setPinnedColumns(pinnedColumnCount, pinnedRightColumnCount); } /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - function toggleFrozenBottomRows() { + function togglePinnedBottomRows() { + const newIsPinnedBottom = !isPinnedBottom; + const rows = Array.from({ length: Math.max(0, pinnedRowCount) }, (_value, index) => index); reactGridRef.current?.slickGrid.setOptions({ - frozenBottom: !isFrozenBottom, + pinning: { rows: newIsPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } }, }); - - const newIsFrozenBottom = !isFrozenBottom; - setIsFrozenBottom(newIsFrozenBottom); + setIsPinnedBottom(newIsPinnedBottom); } function toggleSubTitle() { @@ -355,7 +484,7 @@ const Example20: React.FC = () => { ) : (

- Example 20: Pinned (frozen) Columns/Rows + Example 20: Pinned Columns/Rows see  {

- This example demonstrates the use of Pinned (aka frozen) Columns and/or Rows ( - + This example demonstrates the use of Pinned (aka pinned) Columns and/or Rows ( + Docs ) @@ -395,15 +524,33 @@ const Example20: React.FC = () => {
- changeFrozenRowCount($event)} /> - - changeFrozenColumnCount($event)} /> - + + + + changePinnedRightColumnCount($event)} + /> + @@ -414,20 +561,49 @@ const Example20: React.FC = () => {
- + - - : {isFrozenBottom ? 'Bottom' : 'Top'} + : {isPinnedBottom ? 'Bottom' : 'Top'} + +
diff --git a/demos/react/src/examples/slickgrid/Example43.tsx b/demos/react/src/examples/slickgrid/Example43.tsx index 2521e1e0ef..fbc350e3e1 100644 --- a/demos/react/src/examples/slickgrid/Example43.tsx +++ b/demos/react/src/examples/slickgrid/Example43.tsx @@ -130,7 +130,6 @@ export default function Example43() { autoResize: { container: '#demo-container', bottomPadding: 30, - rightPadding: 50, }, enableCellNavigation: true, enableColumnReorder: true, @@ -142,7 +141,7 @@ export default function Example43() { autoEdit: true, editable: false, datasetIdPropertyName: 'employeeID', - frozenColumn: 0, + pinning: { columns: { left: 0 } }, gridHeight: 348, rowHeight: 30, dataView: { @@ -443,7 +442,7 @@ export default function Example43() { newMetadata[row].columns[Number(col) + colDirIdx] = (this.metadata as any)[row].columns[col]; } } - reactGrid?.slickGrid?.setOptions({ frozenColumn: newShowEmployeeId ? 0 : 1 }); + reactGrid?.slickGrid?.setOptions({ pinning: { columns: { left: newShowEmployeeId ? 0 : 1 } } }); reactGrid?.slickGrid?.updateColumnById('employeeID', { hidden: !newShowEmployeeId }); reactGrid?.slickGrid?.updateColumns(); */ diff --git a/demos/react/src/examples/slickgrid/Example55.tsx b/demos/react/src/examples/slickgrid/Example55.tsx index 099865443b..553bc4eaca 100644 --- a/demos/react/src/examples/slickgrid/Example55.tsx +++ b/demos/react/src/examples/slickgrid/Example55.tsx @@ -84,7 +84,7 @@ const Example55: React.FC = () => { const owners = ['Alex', 'Priya', 'Mia', 'Sam', 'Chris']; const fragments = [ 'Refactor keyboard shortcut handling for better readability.', - 'Adjust frozen rows when view-model updates after grouping.', + 'Adjust pinned rows when view-model updates after grouping.', 'Improve screen-reader labels on grid menu actions.', 'Align batch editor validation with backend constraints.', 'Capture edge-case around hidden columns and row-span.', diff --git a/demos/react/src/examples/slickgrid/Example56.tsx b/demos/react/src/examples/slickgrid/Example56.tsx index 5af54c812a..b15f7c5cd9 100644 --- a/demos/react/src/examples/slickgrid/Example56.tsx +++ b/demos/react/src/examples/slickgrid/Example56.tsx @@ -79,7 +79,7 @@ const Example56: React.FC = () => { includeColumnWidth: true, }, rowHeight: 40, - frozenRow: 2, + pinning: { rows: { top: [0, 1] } }, gridHeight: 560, gridWidth: 1080, dataView: { @@ -113,7 +113,7 @@ const Example56: React.FC = () => { const statuses: Array = ['Todo', 'In Progress', 'Done']; const notesPool = [ 'Short note.', - 'Need to validate keyboard navigation and ensure screen reader output remains stable across frozen panes.', + 'Need to validate keyboard navigation and ensure screen reader output remains stable across pinned panes.', 'Review row height invalidation path when data changes quickly due to live updates from backend polling.', 'Longer QA note: validate scrolling behavior at top and bottom boundaries, compare rendered range against expected rows, and confirm no visual clipping for wrapped cells.', ]; diff --git a/demos/react/src/examples/slickgrid/example14.scss b/demos/react/src/examples/slickgrid/example14.scss index 6e31054384..e41265a0e7 100644 --- a/demos/react/src/examples/slickgrid/example14.scss +++ b/demos/react/src/examples/slickgrid/example14.scss @@ -1,8 +1,8 @@ -/** You can change the pinned/frozen border styling through this css override */ +/** You can change the pinned/pinned border styling through this css override */ -.slick-row .slick-cell.frozen:last-child, -.slick-headerrow-column.frozen:last-child, -.slick-footerrow-column.frozen:last-child { +.slick-row .slick-cell.pinned:last-child, +.slick-headerrow-column.pinned:last-child, +.slick-footerrow-column.pinned:last-child { border-right: 1px solid #969696 !important; } diff --git a/demos/react/src/examples/slickgrid/example20.scss b/demos/react/src/examples/slickgrid/example20.scss index 6e31054384..e41265a0e7 100644 --- a/demos/react/src/examples/slickgrid/example20.scss +++ b/demos/react/src/examples/slickgrid/example20.scss @@ -1,8 +1,8 @@ -/** You can change the pinned/frozen border styling through this css override */ +/** You can change the pinned/pinned border styling through this css override */ -.slick-row .slick-cell.frozen:last-child, -.slick-headerrow-column.frozen:last-child, -.slick-footerrow-column.frozen:last-child { +.slick-row .slick-cell.pinned:last-child, +.slick-headerrow-column.pinned:last-child, +.slick-footerrow-column.pinned:last-child { border-right: 1px solid #969696 !important; } diff --git a/demos/react/test/cypress/e2e/example03.cy.ts b/demos/react/test/cypress/e2e/example03.cy.ts index f488efd517..a08337a57c 100644 --- a/demos/react/test/cypress/e2e/example03.cy.ts +++ b/demos/react/test/cypress/e2e/example03.cy.ts @@ -61,8 +61,8 @@ describe('Example 3 - Grid with Editors', () => { .click(); // change Title & Custom Title - cy.get('.editor-title > textarea').type('Task 2222'); - cy.get('.editor-title .btn-save').click(); + cy.get('.editor-title:visible > textarea').type('Task 2222'); + cy.get('.editor-title:visible .btn-save').click(); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 2222'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(3)`).should('contain', 'Task 2222'); @@ -98,7 +98,7 @@ describe('Example 3 - Grid with Editors', () => { `[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(11) > input.editor-checkbox.editor-effort-driven` ).check(); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); }); it('should dynamically add 2x new "Title" columns', () => { @@ -147,8 +147,8 @@ describe('Example 3 - Grid with Editors', () => { .click(); // change Title & Custom Title - cy.get('.editor-title > textarea').type('Task 0000'); - cy.get('.editor-title .btn-save').click(); + cy.get('.editor-title:visible > textarea').type('Task 0000'); + cy.get('.editor-title:visible .btn-save').click(); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 0000'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(3)`).should('contain', 'Task 0000'); @@ -180,7 +180,7 @@ describe('Example 3 - Grid with Editors', () => { .blur(); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(10)`).click(); // the blur seems to not always work, so just click on another cell cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(11)`).find('.mdi-check.checkmark-icon'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); }); it('should be able to filter and search "Task 2222" in the new column and expect only 1 row showing in the grid', () => { @@ -258,7 +258,7 @@ describe('Example 3 - Grid with Editors', () => { }); it('should open the "Prerequisites" Filter and expect to have Task 500 & 101 in the Filter', () => { - cy.get('div.ms-filter.filter-prerequisites').trigger('click', { force: true }); + cy.get('div.ms-filter.filter-prerequisites').trigger('click'); cy.get('.ms-drop').find('span:nth(1)').contains('Task 101'); @@ -270,7 +270,7 @@ describe('Example 3 - Grid with Editors', () => { it('should open the "Prerequisites" Editor and expect to have Task 100 & 101 in the Editor', () => { cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(12)`) .should('contain', '') - .click({ force: true }); + .click(); cy.get('.ms-drop').find('span:nth(1)').contains('Task 101'); @@ -284,11 +284,11 @@ describe('Example 3 - Grid with Editors', () => { }); it('should delete the last item "Task 101" and expect it to be removed from the Filter', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('right').wait(50); + cy.get('.slick-horizontal-scroller').scrollTo('right').wait(50); cy.get('[data-test="delete-item-btn"]').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).wait(50); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0).wait(50); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 100'); cy.get('div.ms-filter.filter-prerequisites').trigger('click'); diff --git a/demos/react/test/cypress/e2e/example07.cy.ts b/demos/react/test/cypress/e2e/example07.cy.ts index 048c260d9b..446c389b24 100644 --- a/demos/react/test/cypress/e2e/example07.cy.ts +++ b/demos/react/test/cypress/e2e/example07.cy.ts @@ -75,7 +75,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should go over the last "Column J" and expect to find the button to have the disabled class and clicking it should not turn the negative numbers to red neither expect console log after clicking the disabled button', () => { - cy.get('#grid7-1 .slick-viewport-top.slick-viewport-left').scrollTo('right').wait(50); + cy.get('#grid7-1 .slick-horizontal-scroller').scrollTo('right').wait(50); cy.get('#grid7-1 .slick-header-columns') .children('.slick-header-column:nth(9)') @@ -109,7 +109,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should resize 1st column and make it wider', () => { - cy.get('#grid7-1 .slick-viewport-top.slick-viewport-left').scrollTo('left').wait(50); + cy.get('#grid7-1 .slick-horizontal-scroller').scrollTo('left').wait(50); cy.get('#grid7-1 .slick-header-columns').children('.slick-header-column:nth(0)').should('contain', 'Resize me!'); @@ -220,7 +220,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should go over the last "Column J" and expect to find the button to have the disabled class and clicking it should not turn the negative numbers to red neither expect console log after clicking the disabled button', () => { - cy.get('#grid7-2 .slick-viewport-top.slick-viewport-left').scrollTo('right').wait(50); + cy.get('#grid7-2 .slick-horizontal-scroller').scrollTo('right').wait(50); cy.get('#grid7-2 .slick-header-columns') .children('.slick-header-column:nth(9)') @@ -254,7 +254,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should resize 1st column and make it wider', () => { - cy.get('#grid7-2 .slick-viewport-top.slick-viewport-left').scrollTo('left').wait(50); + cy.get('#grid7-2 .slick-horizontal-scroller').scrollTo('left').wait(50); cy.get('#grid7-2 .slick-header-columns').children('.slick-header-column:nth(0)').should('contain', 'Resize me!'); @@ -364,7 +364,7 @@ describe('Example 7 - Header Button Plugin', () => { }); it('should expect first few items of "Column C" to be negative numbers and be red', () => { - cy.get('#grid7-2 .slick-viewport-top.slick-viewport-left').scrollTo('top').wait(50); + cy.get('#grid7-2 .slick-vertical-scroller').scrollTo('top').wait(50); cy.get('#grid7-2 .slick-row').each(($row, index) => { if (index > 10) { diff --git a/demos/react/test/cypress/e2e/example10.cy.ts b/demos/react/test/cypress/e2e/example10.cy.ts index f65cc30eeb..fb67e74664 100644 --- a/demos/react/test/cypress/e2e/example10.cy.ts +++ b/demos/react/test/cypress/e2e/example10.cy.ts @@ -389,14 +389,14 @@ describe('Example 10 - Multiple Grids with Row Selection', () => { it('should scroll to the bottom of 2nd Grid and still have 5 rows (Task 1,Task 3,Task 12,Task 13,Task 522) selected and find 2 row selected because we now have 2 rows that got rendered (first and last)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); cy.get('[data-test=grid2-selections]').should('contain', 'Task 1,Task 3,Task 12,Task 13,Task 522'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(10); + cy.get('@grid2').find('.slick-vertical-scroller').scrollTo('bottom').wait(10); cy.get('@grid2').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 2); }); it('should have 2 rows (Task 3,Task 13) selected in 2nd grid after typing in a search filter (3)', () => { cy.get('#slickGridContainer-grid2').as('grid2'); cy.get('@grid2').find('.filter-title').type('3'); - cy.get('@grid2').find('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(10); + cy.get('@grid2').find('.slick-vertical-scroller').scrollTo('top').wait(10); cy.get('@grid2').find('.slick-row').should('not.have.length', 0); cy.wait(50); diff --git a/demos/react/test/cypress/e2e/example12.cy.ts b/demos/react/test/cypress/e2e/example12.cy.ts index ec11ba1405..7ed1cab4e7 100644 --- a/demos/react/test/cypress/e2e/example12.cy.ts +++ b/demos/react/test/cypress/e2e/example12.cy.ts @@ -229,7 +229,7 @@ describe('Example 12: Localization (i18n)', () => { it('should scroll to bottom of the grid then select "Task 4"', () => { cy.get('#slickGridContainer-grid12').as('grid12'); - cy.get('@grid12').find('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(10); + cy.get('@grid12').find('.slick-vertical-scroller').scrollTo('bottom').wait(10); cy.get('#grid12').contains('Task 4').parent().children('.slick-cell-checkboxsel').find('input[type=checkbox]').click({ force: true }); @@ -259,7 +259,7 @@ describe('Example 12: Localization (i18n)', () => { cy.get('.grid-canvas').find('.slick-row').should('be.visible'); - cy.get('@grid12').find('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(10); + cy.get('@grid12').find('.slick-vertical-scroller').scrollTo('top').wait(10); cy.get('@grid12').find('.slick-row').children().filter('.slick-cell-checkboxsel.selected').should('have.length', 1); diff --git a/demos/react/test/cypress/e2e/example14.cy.ts b/demos/react/test/cypress/e2e/example14.cy.ts index 91f7db34b6..32b529fd3b 100644 --- a/demos/react/test/cypress/e2e/example14.cy.ts +++ b/demos/react/test/cypress/e2e/example14.cy.ts @@ -1,5 +1,4 @@ describe('Example 14 - Column Span & Header Grouping', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows const fullPreTitles = ['', 'Common Factor', 'Period', 'Analysis']; const fullTitles = ['#', 'Title', 'Duration', 'Start', 'Finish', '% Complete', 'Effort Driven']; @@ -20,17 +19,18 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should have a frozen grid on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('#grid2').find('[data-row=0]').should('have.length', 2); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); + it('should have a pinned grid on page load with 3 pinned columns and 4 scrolling columns', () => { + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell`).should('have.length', 3); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell`).should('have.length', 4); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]> .slick-cell:nth(2)').should('contain', '5 days'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]> .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(0)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid again', () => { @@ -45,17 +45,18 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.get('[data-test="remove-frozen-column-button"]').click(); + it('should click on the "Remove Pinned Columns" button to switch to a regular grid without pinned columns', () => { + cy.get('[data-test="remove-pinned-column-button"]').click(); - cy.get('#grid2').find('[data-row=0]').should('have.length', 1); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-cell`).should('have.length', 7); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`${firstRow} .slick-cell:nth(3)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-cell:nth(4)`).should('contain', '01/05/2009'); }); it('should have exact Column Pre-Header & Column Header Titles in the grid once again', () => { @@ -70,19 +71,20 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { - cy.contains('Set 3 Frozen Columns').click({ force: true }); + it('should click on the "Set 3 Pinned Columns" button to pin 3 columns and leave 4 scrolling columns', () => { + cy.contains('Set 3 Pinned Columns').click({ force: true }); - cy.get('#grid2').find('[data-row=0]').should('have.length', 2); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 3); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0]').children().should('have.length', 4); + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell`).should('have.length', 3); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell`).should('have.length', 4); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-pinned-left-cells .slick-cell:nth(2)`).should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(0)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-right > [data-row=0] > .slick-cell:nth(1)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(0)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-scrolling-cells .slick-cell:nth(1)`).should('contain', '01/05/2009'); }); it('should have still exact Column Pre-Header & Column Header Titles in the grid', () => { @@ -97,56 +99,49 @@ describe('Example 14 - Column Span & Header Grouping', () => { .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { + it('should click on the Grid Menu command "Unpin Columns/Rows" to switch to a regular grid without pinned columns', () => { cy.get('#grid2').find('button.slick-grid-menu-button').click({ force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + cy.contains('Unpin Columns/Rows').click({ force: true }); - cy.get('#grid2').find('[data-row=0]').should('have.length', 1); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0]').children().should('have.length', 7); + const firstRow = '#grid2 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-cell`).should('have.length', 7); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(2)').should('contain', '5 days'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(3)').should('contain', '01/01/2009'); - cy.get('#grid2').find('.grid-canvas-left > [data-row=0] > .slick-cell:nth(4)').should('contain', '01/05/2009'); + cy.get(`${firstRow} .slick-cell:nth(0)`).should('contain', '0'); + cy.get(`${firstRow} .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${firstRow} .slick-cell:nth(2)`).should('contain', '5 days'); + cy.get(`${firstRow} .slick-cell:nth(3)`).should('contain', '01/01/2009'); + cy.get(`${firstRow} .slick-cell:nth(4)`).should('contain', '01/05/2009'); }); - it('should reapply 3 frozen columns on 2nd grid', () => { - cy.contains('Set 3 Frozen Columns').click({ force: true }); + it('should reapply 3 pinned columns on 2nd grid', () => { + cy.contains('Set 3 Pinned Columns').click({ force: true }); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 3); - - cy.get('#grid2') - .find('.slick-pane-right .slick-header.slick-header-right .slick-header-columns .slick-header-column') - .should('have.length', 4); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-column-pinned-left').should('have.length', 3); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column:not(.slick-column-pinned-left)').should( + 'have.length', + 4 + ); }); - it('should be able to "Unfreeze Columns" from header menu', () => { + it('should be able to "Unpin All Columns" from header menu', () => { cy.get('#grid2') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') + .find('.slick-header.slick-header-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') .click(); - cy.get('.slick-header-menu .slick-menu-command-list') - .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Unfreeze Columns') - .click(); + cy.get('.slick-header-menu .slick-menu-command-list').should('be.visible').find('[data-command="pin-column"]').click(); + cy.get('.slick-submenu [data-command="unpin-columns"]').should('contain', 'Unpin All Columns').click(); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 7); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should be able to "Freeze Columns" back from header menu', () => { + it('should be able to "Pin Through Here" back from header menu', () => { cy.get('#grid2') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') + .find('.slick-header.slick-header-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(2)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') @@ -154,57 +149,72 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') .click(); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 3); + cy.get('.slick-submenu [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); - cy.get('#grid2') - .find('.slick-pane-right .slick-header.slick-header-right .slick-header-columns .slick-header-column') - .should('have.length', 4); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-column-pinned-left').should('have.length', 3); + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column:not(.slick-column-pinned-left)').should( + 'have.length', + 4 + ); }); describe('Basic Key Navigations', () => { it('should remove any freezing', () => { - cy.get('[data-test="remove-frozen-column-button"]').click(); + cy.get('[data-test="remove-pinned-column-button"]').click(); + + cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); + }); + + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and land on a full colspan', () => { + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}'); + cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + }); + + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and land on a colspan of 3', () => { + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pagedown}{pagedown}'); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + }); - cy.get('#grid2') - .find('.slick-pane-left .slick-header.slick-header-left .slick-header-columns .slick-header-column') - .should('have.length', 7); + it('should navigate PageUp twice from a colspan of 3 back to the starting colspan of 3', () => { + cy.get('#grid1 [data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('@active_cell').type('{pageup}{pageup}'); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); - cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); - cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}'); - cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { - cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); }); }); @@ -219,7 +229,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { .contains(/(true|false)+$/); cy.get('#grid1') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') + .find('.slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') @@ -232,6 +242,11 @@ describe('Example 14 - Column Span & Header Grouping', () => { .should('contain', 'Hide Column') .click(); + // The colspan still spans the logical Duration/Start/Finish range, but + // the hidden Finish track is zero-width. The host cell must remain + // rendered and visibly cover the two remaining columns. + cy.get('#grid1 [data-row=1] .slick-cell.l1.r3').should('contain', '5 days').and('be.visible'); + // goto right cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0').click(); cy.get('#grid1').find('[data-row=1] .slick-cell.l0.r0.active').should('contain', 'Task 1').type('{rightArrow}'); @@ -272,7 +287,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { .contains(/(true|false)+$/); cy.get('#grid1') - .find('.slick-pane-left .slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') + .find('.slick-header-columns .slick-header-column[role="columnheader"]:nth(3)') .trigger('mouseover') .children('.slick-header-menu-button') .invoke('show') diff --git a/demos/react/test/cypress/e2e/example15.cy.ts b/demos/react/test/cypress/e2e/example15.cy.ts index 2792dbeac6..5a7e09da9d 100644 --- a/demos/react/test/cypress/e2e/example15.cy.ts +++ b/demos/react/test/cypress/e2e/example15.cy.ts @@ -1,7 +1,6 @@ import { format } from '@formkit/tempo'; describe('Example 15: Grid State & Presets using Local Storage', () => { - const GRID_ROW_HEIGHT = 35; const fullEnglishTitles = ['', 'Title', 'Description', 'Duration', '% Complete', 'Start', 'Completed']; beforeEach(() => { @@ -12,16 +11,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.saveLocalStorage(); }); - it('should display Example title', () => { - cy.visit(`${Cypress.config('baseUrl')}/example15`); + it('should display Example title from a clean local-storage state', () => { + cy.visit(`${Cypress.config('baseUrl')}/example15`, { + onBeforeLoad: (window) => window.localStorage.clear(), + }); cy.get('h2').should('contain', 'Example 15: Grid State & Presets using Local Storage'); - - cy.clearLocalStorage(); - cy.get('[data-test=reset-button]').click(); - }); - - it('should reload the page', () => { - cy.reload().wait(50); }); it('should have exact Column Titles in the grid', () => { @@ -348,7 +342,7 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { .each(($child, index) => expect($child.find('.slick-column-name').text()).to.eq(expectedTitles[index])); }); - it('should be able to freeze "Description" column', () => { + it('should be able to pin "Description" column', () => { cy.get('.slick-header-columns') .children('.slick-header-column:nth(1)') .trigger('mouseover') @@ -359,10 +353,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Geler les colonnes') - .click(); + .find('[data-command="pin-column"]') + .should('contain', 'Épinglage de colonnes') + .trigger('mouseover'); + + cy.get('.slick-submenu:visible [data-command="pin-columns"]').should('contain', "Épingler jusqu'ici").click(); }); it('should reload the page', () => { @@ -423,10 +418,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { }); }); - it('should have a persisted frozen column after "Description" and a grid with 4 containers on page load with 2 columns on the left and 3 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]').children().should('have.length', 3); + it('should have a persisted pinned column after "Description" with 2 pinned and 3 scrolling columns', () => { + const firstRow = '#grid15 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells > .slick-cell`).should('have.length', 2); + cy.get(`${firstRow} .slick-scrolling-cells > .slick-cell`).should('have.length', 3); }); it('should click on the reset button and have exact Column Titles position as in beginning', () => { @@ -453,7 +449,7 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); }); - it('should be able to freeze "Description" 3rd column', () => { + it('should be able to pin "Description" 3rd column', () => { cy.get('.slick-header-columns') .children('.slick-header-column:nth(2)') .trigger('mouseover') @@ -464,10 +460,11 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') - .click(); + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') + .trigger('mouseover'); + + cy.get('.slick-submenu:visible [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); }); it('should swap "Duration" and "% Complete" columns', () => { @@ -481,7 +478,7 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { .each(($child, index) => expect($child.text()).to.eq(expectedTitles[index])); }); - it('should be able to freeze "% Complete" and expect 4th column to be freezed', () => { + it('should be able to pin "% Complete" and expect 4th column to be pinned', () => { cy.get('.slick-header-columns') .children('.slick-header-column:nth(3)') .trigger('mouseover') @@ -492,16 +489,18 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(1)') - .children('.slick-menu-content') - .should('contain', 'Freeze Columns') - .click(); + .find('[data-command="pin-column"]') + .should('contain', 'Column Pinning') + .trigger('mouseover'); + + cy.get('.slick-submenu:visible [data-command="pin-columns"]').should('contain', 'Pin Through Here').click(); }); - it('should have a persisted frozen column after "Description" and a grid with 4 containers on page load with 2 columns on the left and 3 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 4); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]').children().should('have.length', 3); + it('should have a persisted pinned column after "Description" with 4 pinned and 3 scrolling columns', () => { + const firstRow = '#grid15 .slick-row[data-row="0"]'; + cy.get(firstRow).should('have.length', 1); + cy.get(`${firstRow} .slick-pinned-left-cells > .slick-cell`).should('have.length', 4); + cy.get(`${firstRow} .slick-scrolling-cells > .slick-cell`).should('have.length', 3); }); describe('Filter Shortcuts', () => { @@ -599,9 +598,9 @@ describe('Example 15: Grid State & Presets using Local Storage', () => { expect(Number($span.text())).to.gt(80); }); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).contains('desc'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).contains('desc'); - cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 2}px);"] > .slick-cell:nth(2)`).contains('desc'); + cy.get('#grid15 .slick-row[data-row="0"] .slick-cell.l2').contains('desc'); + cy.get('#grid15 .slick-row[data-row="1"] .slick-cell.l2').contains('desc'); + cy.get('#grid15 .slick-row[data-row="2"] .slick-cell.l2').contains('desc'); }); }); }); diff --git a/demos/react/test/cypress/e2e/example16.cy.ts b/demos/react/test/cypress/e2e/example16.cy.ts index d9eb35e061..87d69f920c 100644 --- a/demos/react/test/cypress/e2e/example16.cy.ts +++ b/demos/react/test/cypress/e2e/example16.cy.ts @@ -39,7 +39,7 @@ describe('Example 16 - Row Move & Checkbox Selector Selector Plugins', () => { }); it('should expect the row to have moved to another row index', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 0'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 1'); @@ -76,7 +76,7 @@ describe('Example 16 - Row Move & Checkbox Selector Selector Plugins', () => { cy.get('@moveIconTask5').trigger('mousemove', 'bottomRight').trigger('mouseup', 'bottomRight', { which: 1, force: true }); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 0}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 0'); cy.get(`[style="transform: translateY(${GRID_ROW_HEIGHT * 1}px);"] > .slick-cell:nth(2)`).should('contain', 'Task 1'); diff --git a/demos/react/test/cypress/e2e/example19.cy.ts b/demos/react/test/cypress/e2e/example19.cy.ts index babe74c51a..d17f2c9055 100644 --- a/demos/react/test/cypress/e2e/example19.cy.ts +++ b/demos/react/test/cypress/e2e/example19.cy.ts @@ -80,7 +80,7 @@ describe('Example 19 - Row Detail View', () => { cy.get('@detailContainer').find('[data-test=delete-btn]').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('#grid19') .find('.slick-row') @@ -111,7 +111,7 @@ describe('Example 19 - Row Detail View', () => { cy.get('[data-test=collapse-all-btn]').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('#grid19').find('.dynamic-cell-detail .innerDetailView_0 .container_0').should('not.exist'); @@ -175,7 +175,7 @@ describe('Example 19 - Row Detail View', () => { cy.get('#grid19').find('.slick-header-column:nth(1)').find('.slick-sort-indicator-asc').should('have.length', 1); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('#grid19').find('.dynamic-cell-detail .innerDetailView_0 .container_0').should('not.exist'); diff --git a/demos/react/test/cypress/e2e/example20.cy.ts b/demos/react/test/cypress/e2e/example20.cy.ts index 11481d0d77..91c8f4b076 100644 --- a/demos/react/test/cypress/e2e/example20.cy.ts +++ b/demos/react/test/cypress/e2e/example20.cy.ts @@ -1,102 +1,172 @@ -describe('Example 20 - Frozen Grid', () => { - // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows - - const fullTitles = [ - '#', - 'Title', - '% Complete', - 'Start', - 'Finish', - 'Cost | Duration', - 'Effort Driven', - 'Title 1', - 'Title 2', - 'Title 3', - 'Title 4', - ]; +describe('Example 20 - Pinned Grid', () => { + before(() => { + // The framework demos include a 250px route sidebar. Use enough width for + // the two-column pinning scenario to remain valid with that sidebar. + cy.viewport(1440, 900); + }); + + const withTitleRowTitles = ['Sel', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const withoutTitleRowTitles = ['', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const getCell = (rowIndex: number, columnIndex: number) => + cy.get(`#grid20 .slick-row[data-row="${rowIndex}"] .slick-cell.l${columnIndex}`); + const setRightPinning = (count: number) => { + cy.get('.pinned-right-column-count').clear().type(`${count}`); + cy.get('[data-test="set-pinned-right-column"]').click(); + }; it('should display Example title', () => { cy.visit(`${Cypress.config('baseUrl')}/example20`); - cy.get('h2').should('contain', 'Example 20: Pinned (frozen) Columns/Rows'); + cy.get('h2').should('contain', 'Example 20: Pinned Columns/Rows'); }); it('should have exact column titles on 1st grid', () => { cy.get('#grid20') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + }); + + it('should hide sub-title to provide more space for the grid', () => { + cy.get('[data-test="toggle-subtitle"]').click(); }); it('should have exact Column Header Titles in the grid', () => { cy.get('#grid20') - .find('.slick-header-columns:nth(0)') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns:nth(0) .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should have a frozen grid with 4 containers on page load with 3 columns on the left and 4 columns on the right', () => { - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + it('should have three top-pinned rows and left/center/right cells on page load', () => { + const row0 = '#grid20 .slick-row[data-row="0"]'; + + // Pinning uses one row node split into regions and a single docking overlay; + // it no longer duplicates rows into legacy left/right pinned canvases. + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + cy.get(`${row0} .slick-pinned-left-cells > .slick-cell`).should('have.length', 3); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell`).should('have.length', 5); + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 1); + + cy.get(`${row0} .slick-pinned-left-cells > .slick-cell:nth(0)`).should('contain', ''); + cy.get(`${row0} .slick-pinned-left-cells > .slick-cell:nth(1)`).should('contain', 'Task 0'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(0)`).should('contain', '2009-01-01'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(1)`).should('contain', '2009-05-05'); + + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell:nth(0) .cell-menu-dropdown`).should('contain', 'Action'); + }); + + it('should pin multiple columns on the right and render matching header and filter regions', () => { + setRightPinning(2); + + const row0 = '#grid20 .slick-row[data-row="0"]'; + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 2); + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell.l7`).should('contain', 'Boston'); + cy.get(`${row0} .slick-pinned-right-cells > .slick-cell.l8 .cell-menu-dropdown`).should('contain', 'Action'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid20 .slick-header-columns-right .slick-header-column').should('have.length', 2); + cy.get('#grid20 .slick-header-columns-right [data-id="cityOfOrigin"]').should('contain', 'City of Origin'); + cy.get('#grid20 .slick-header-columns-right [data-id="action"]').should('contain', 'Action'); + cy.get('#grid20 .slick-headerrow-columns-right .slick-headerrow-column').should('have.length', 2); + cy.get('#grid20 .slick-headerrow-columns-right .slick-headerrow-column.l7 input').should('exist'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + setRightPinning(1); + }); + + it('should keep multiple right-pinned columns fixed while scrolling', () => { + setRightPinning(2); + // The default fixture fits in the viewport, so use the demo's wide layout + // to exercise an actual horizontal scroll rather than a no-op scroll. + cy.get('[data-test="set-large-pinned-columns"]').click(); + const actionCell = '#grid20 .slick-row[data-row="10"] .slick-pinned-right-cells .slick-cell.l8'; + + cy.get(actionCell).then(($cell) => { + const rightEdge = $cell[0].getBoundingClientRect().right; + cy.get('#grid20 .slick-horizontal-scroller').scrollTo('right'); + cy.get(actionCell).should(($scrolledCell) => { + expect(Math.abs($scrolledCell[0].getBoundingClientRect().right - rightEdge)).to.be.lessThan(2); + }); + }); + + cy.get('#grid20 .slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + // Restore the initial fixture after exercising the wide layout so the + // following serial tests do not inherit resized columns. + cy.visit(`${Cypress.config('baseUrl')}/example20`); + }); + + it('should resize columns while left and right pinning are active', () => { + cy.visit(`${Cypress.config('baseUrl')}/example20`); + + const resizeColumn = (columnSelector: string, pinClass: string) => { + cy.get(columnSelector).should('have.class', pinClass); + cy.get(`${columnSelector} .slick-resizable-handle`) + .should('exist') + .then(($handle) => { + const header = $handle.closest('.slick-header-column')[0] as HTMLElement; + const initialWidth = header.getBoundingClientRect().width; + + cy.wrap($handle).trigger('mousedown', { which: 1, pageX: 100, clientX: 100, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 125, clientX: 125, force: true }); + cy.get('body').trigger('mousemove', { which: 1, pageX: 150, clientX: 150, force: true }); + cy.get('body').trigger('mouseup', { which: 1, pageX: 150, clientX: 150, force: true }); + + cy.get(columnSelector).should(($updatedHeader) => { + expect($updatedHeader[0].getBoundingClientRect().width).to.be.greaterThan(initialWidth); + }); + }); + }; + + resizeColumn('#grid20 .slick-header-columns-left [data-id="title"]', 'slick-column-pinned-left'); + + // Use City of Origin because Action has a maxWidth of 100px and would refuse a wider resize. + setRightPinning(2); + resizeColumn('#grid20 .slick-header-columns-right [data-id="cityOfOrigin"]', 'slick-column-pinned-right'); + + // Keep the following serial tests on the demo's default configuration. + cy.visit(`${Cypress.config('baseUrl')}/example20`); }); - it('should hide "Title" column from Grid Menu and expect last frozen column to be "% Complete"', () => { - const newColumnList = [ - '#', - '% Complete', - 'Start', - 'Finish', - 'Cost | Duration', - 'Effort Driven', - 'Title 1', - 'Title 2', - 'Title 3', - 'Title 4', - ]; + it('should disable and re-enable right pinning through the numeric grid control', () => { + setRightPinning(0); + cy.get('#grid20 .slick-row[data-row="0"] .slick-pinned-right-cells').should('exist'); + cy.get('#grid20 .slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell').should('not.exist'); + cy.get('#grid20 .slick-header-columns-right .slick-header-column').should('not.exist'); + + setRightPinning(1); + cy.get('#grid20 .slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell.l8').should('contain', 'Action'); + cy.get('#grid20 .slick-header-columns-right [data-id="action"]').should('contain', 'Action'); + }); + + it('should hide "Title" column from Grid Menu and expect last pinned column to be "% Complete"', () => { + const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const row0 = '#grid20 .slick-row[data-row="0"]'; cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); cy.get('#grid20') .get('.slick-grid-menu:visible') .find('.slick-column-picker-list') - .children('li:visible:nth(1)') + .children('li:visible:nth(0)') .children('label') .should('contain', 'Title') .click({ force: true }); cy.get('#grid20') - .find('.slick-header-columns') - .children() + .find('.slick-header-columns .slick-header-column') .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 2 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 2); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 5); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').children().should('have.length', 1); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(0)`).should('contain', '2009-01-01'); + cy.get(`${row0} .slick-scrolling-cells > .slick-cell:nth(1)`).should('contain', '2009-05-05'); }); - it('should show again "Title" column from Grid Menu and expect last frozen column to still be "% Complete"', () => { + it('should show again "Title" column from Grid Menu and expect last pinned column to still be "% Complete"', () => { cy.get('#grid20') .get('.slick-grid-menu:visible') .find('.slick-column-picker-list') - .children('li:visible:nth(1)') + .children('li:visible:nth(0)') .children('label') .should('contain', 'Title') .click({ force: true }); @@ -104,77 +174,60 @@ describe('Example 20 - Frozen Grid', () => { cy.get('#grid20').get('.slick-grid-menu:visible').find('.close').click({ force: true }); cy.get('#grid20') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 5); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').children().should('have.length', 1); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + cy.get('.slick-scrolling-cells .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-scrolling-cells .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should hide "Title" column from Header Menu and expect last frozen column to be "% Complete"', () => { - const newColumnList = [ - '#', - '% Complete', - 'Start', - 'Finish', - 'Cost | Duration', - 'Effort Driven', - 'Title 1', - 'Title 2', - 'Title 3', - 'Title 4', - ]; + it('should hide "Title" column from Header Menu and expect last pinned column to be "% Complete"', () => { + const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; - cy.get('#grid20') - .find('.slick-header-column:nth(1)') - .trigger('mouseover') - .children('.slick-header-menu-button') - .should('be.hidden') - .invoke('show') - .click(); + cy.get('#grid20').find('.slick-header-column:nth(1)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(8)') + .children('.slick-menu-item:nth-of-type(9)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); cy.get('#grid20') - .find('.slick-header-columns') - .children() + .find('.slick-header-columns .slick-header-column') .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 2 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 2); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 5); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').children().should('have.length', 1); + + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + it('should toggle right pinned column and expect only 2 left/center containers to be visible', () => { + cy.get('[data-test="toggle-pinned-right"]').click(); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 2); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells').should('exist').and('not.have.class', 'slick-pinned-right-cells-active'); + cy.get('.slick-row[data-row="0"] .slick-pinned-right-cells > .slick-cell').should('not.exist'); }); - it('should show again "Title" column from Column Picker and expect last frozen column to still be "% Complete"', () => { - cy.get('#grid20').find('.slick-header-column:nth(5)').trigger('mouseover').trigger('contextmenu').invoke('show'); + it('should show again "Title" column from Column Picker and expect last pinned column to still be "% Complete"', () => { + cy.get('#grid20').find('.slick-header-column:nth(4)').trigger('mouseover').trigger('contextmenu').invoke('show'); cy.get('.slick-column-picker') .find('.slick-column-picker-list') - .children('li:nth-child(2)') + .children('li:nth-of-type(2)') .children('label') .should('contain', 'Title') .click(); @@ -182,83 +235,769 @@ describe('Example 20 - Frozen Grid', () => { cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); cy.get('#grid20') - .find('.slick-header-columns') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', ''); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(1)').should('contain', 'Task 0'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); }); - it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.get('[data-test=remove-frozen-column-button]').click({ force: true }); + it('should click on the "Remove Pinned Columns" button to switch to a regular grid view without pinned columns and expect 7 columns on the left container', () => { + cy.get('[data-test=remove-pinned-column-button]').click({ force: true }); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 1 * 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 11 * 2); + cy.get('#grid20 .slick-row[data-row="0"]').should('have.length.at.least', 1); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').should('exist').and('not.have.class', 'slick-pinned-left-cells-active'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell').should('have.length', 9); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + getCell(0, 0).should('contain', ''); + getCell(0, 1).should('contain', 'Task 0'); + getCell(0, 3).should('contain', '2009-01-01'); + getCell(0, 4).should('contain', '2009-05-05'); + }); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(3)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '2009-05-05'); + it('should expect to have exact Column Header Titles in the grid', () => { + cy.get('#grid20') + .find('.slick-header-columns:nth(0) .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should have exact Column Header Titles in the grid', () => { + it('should click on the "Set 3 Pinned Columns" button to switch pinned columns grid and expect 3 pinned columns on the left and 4 columns on the right', () => { + cy.get('[data-test=set-3pinned-columns]').click({ force: true }); + + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').children().should('have.length', 3); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells').children().should('have.length', 6); + + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(0)').should('contain', ''); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells > .slick-cell:nth(1)').should('contain', 'Task 0'); + + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); + + it('should recheck again and still have exact Column Header Titles in the grid', () => { cy.get('#grid20') - .find('.slick-header-columns:nth(0)') - .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .find('.slick-header-columns:nth(0) .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); - it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { - cy.get('[data-test=set-3frozen-columns]').click({ force: true }); + it('should click on the Grid Menu command "Unpin Columns/Rows" to switch to a regular grid without pinned columns/rows', () => { + cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 2 * 2); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 3 * 2); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"]') - .children() - .should('have.length', 8 * 2); + cy.contains('Unpin Columns/Rows').click({ force: true }); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('#grid20 .slick-row[data-row="0"]').should('have.length.at.least', 1); + cy.get('.slick-row[data-row="0"] .slick-pinned-left-cells').should('exist').and('not.have.class', 'slick-pinned-left-cells-active'); + cy.get('.slick-row[data-row="0"] .slick-scrolling-cells > .slick-cell').should('have.length', 9); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-right > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + getCell(0, 0).should('contain', ''); + getCell(0, 1).should('contain', 'Task 0'); + getCell(0, 3).should('contain', '2009-01-01'); + getCell(0, 4).should('contain', '2009-05-05'); }); - it('should have exact Column Header Titles in the grid', () => { + it('should open the Cell Menu on 2nd and 3rd row and change the Effort-Driven to "True" and expect the cell to be updated and have checkmark icon', () => { + getCell(1, 1).should('contain', 'Task 1'); + getCell(1, 8).find('.checkmark-icon').should('have.length', 0); + getCell(2, 1).should('contain', 'Task 2'); + getCell(2, 8).find('.checkmark-icon').should('have.length', 0); + + getCell(1, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('True').click(); + getCell(2, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('True').click(); + + getCell(1, 5).find('.checkmark-icon').should('have.length', 1); + getCell(2, 5).find('.checkmark-icon').should('have.length', 1); + }); + + it('should open the Cell Menu on 2nd and 3rd row and change the Effort-Driven to "False" and expect the cell to be updated and no longer have checkmark', () => { + getCell(1, 5).find('.checkmark-icon').should('have.length', 1); + getCell(2, 5).find('.checkmark-icon').should('have.length', 1); + + getCell(1, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('False').click(); + getCell(2, 8).contains('Action').click({ force: true }); + cy.get('.slick-cell-menu .slick-menu-option-list .slick-menu-item').contains('False').click(); + + getCell(1, 5).find('.checkmark-icon').should('have.length', 0); + getCell(2, 5).find('.checkmark-icon').should('have.length', 0); + }); + + it('should open the Cell Menu and delete Row 3 and 4 from the Cell Menu', () => { + cy.window().then((win) => { + const stub = cy.stub(win, 'confirm').returns(true); + cy.wrap(stub).as('confirmStub'); + }); + + getCell(3, 1).should('contain', 'Task 3'); + getCell(4, 1).should('contain', 'Task 4'); + + getCell(3, 8).contains('Action').click({ force: true }); + + cy.get('.slick-cell-menu .slick-menu-command-list .slick-menu-item').contains('Delete Row').click(); + cy.get('@confirmStub').should('have.been.calledWith', 'Do you really want to delete row (4) with "Task 3"?'); + getCell(3, 1).should('contain', 'Task 4'); + }); + + it.skip('should filter autocomplete by typing Vancouver in the "City of Origin" and expect only filtered rows to show up', () => { + cy.get('.search-filter.filter-cityOfOrigin').type('Vancouver'); + + cy.get('.slick-autocomplete').should('be.visible'); + cy.get('.slick-autocomplete div').should('have.length', 2); + cy.get('.slick-autocomplete').find('div:nth(0)').click(); + + getCell(0, 1).should('contain', 'Task 1'); + getCell(1, 1).should('contain', 'Task 5'); + getCell(2, 1).should('contain', 'Task 7'); + getCell(3, 1).should('contain', 'Task 9'); + getCell(4, 1).should('contain', 'Task 11'); + }); + + it('should Clear all Filters', () => { + cy.get('#grid20').find('button.slick-grid-menu-button').trigger('click').click({ force: true }); + + cy.get(`.slick-grid-menu:visible`).find('.slick-menu-item').first().find('span').contains('Clear all Filters').click(); + }); + + it.skip('should edit first row (Task 1) and change its city by choosing it inside the autocomplete editor list', () => { + getCell(0, 7).click(); + cy.get('input.autocomplete.editor-cityOfOrigin').type('Sydney'); + + cy.get('.slick-autocomplete').should('be.visible'); + cy.get('.slick-autocomplete div').should('have.length', 3); + cy.get('.slick-autocomplete').find('div:nth(1)').click(); + + getCell(0, 1).should('contain', 'Task 0'); + getCell(0, 7).should('contain', 'Sydney, NS, Australia'); + }); + + it('should open Context Menu hover "% Complete" column then select "Not Started (0%)" option and expect Task to be at 0', () => { + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu .slick-menu-option-list').should('exist').contains('Not Started (0%)').click(); + + getCell(0, 2).should('contain', '0'); + }); + + it('should reopen Context Menu hover "% Complete" column then open options sub-menu & select "Half Completed (50%)" option and expect Task to be at 50', () => { + const subOptions = ['Not Started (0%)', 'Half Completed (50%)', 'Completed (100%)']; + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-option-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Sub-Options (demo)') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-option-list').as('subMenuList'); + cy.get('@subMenuList').find('.slick-menu-title').contains('Set Percent Complete'); + cy.get('@subMenuList') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.eq(subOptions[index])); + + cy.get('@subMenuList').find('.slick-menu-item .slick-menu-content').contains('Half Completed (50%)').click(); + + getCell(0, 2).should('contain', '50'); + }); + + it('should be able to open Context Menu and click on Export->Text and expect alert triggered with Text Export', () => { + const subCommands1 = ['Text', 'Excel']; + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list').find('.slick-menu-item').contains('Text').click(); + cy.get('@alertStub').should('have.been.calledWith', 'Exporting as Text (tab delimited)'); + }); + + it('should be able to open Context Menu and click on Export->Excel-> sub-commands to see 1 context menu + 1 sub-menu then clicking on Text should call alert action', () => { + const subCommands1 = ['Text', 'Excel']; + const subCommands2 = ['Excel (csv)', 'Excel (xlsx)']; + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Excel') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list').as('subMenuList2'); + + cy.get('@subMenuList2').find('.slick-menu-title').contains('available formats'); + + cy.get('@subMenuList2') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands2[index])); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Excel (xlsx)') + .click(); + cy.get('@alertStub').should('have.been.calledWith', 'Exporting as Excel (xlsx)'); + }); + + it('should open Export->Excel sub-menu & open again Sub-Options on top and expect sub-menu to be recreated with that Sub-Options list instead of the Export->Excel list', () => { + const subCommands1 = ['Text', 'Excel']; + const subCommands2 = ['Excel (csv)', 'Excel (xlsx)']; + const subOptions = ['Not Started (0%)', 'Half Completed (50%)', 'Completed (100%)']; + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick(); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Excel') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands2[index])); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-option-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Sub-Options') + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-option-list').as('optionSubList2'); + + cy.get('@optionSubList2').find('.slick-menu-title').contains('Set Percent Complete'); + + cy.get('@optionSubList2') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($option, index) => expect($option.text()).to.contain(subOptions[index])); + }); + + it('should open Export->Excel context sub-menu then open Feedback->ContactUs sub-menus and expect previous Export menu to no longer exists', () => { + const subCommands1 = ['Text', 'Excel']; + const subCommands2 = ['Request update from supplier', '', 'Contact Us']; + const subCommands2_1 = ['Email us', 'Chat with us', 'Book an appointment']; + + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + + getCell(0, 2).should('contain', '0'); + getCell(0, 2).rightclick({ force: true }); + + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains(/^Exports$/) + .click(); + + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands1[index])); + + // click different sub-menu + cy.get('.slick-context-menu.slick-menu-level-0 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Feedback') + .should('exist') + .click(); + + cy.get('.slick-submenu').should('have.length', 1); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-command-list') + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.contain(subCommands2[index])); + + // click on Feedback->ContactUs + cy.get('.slick-context-menu.slick-menu-level-1.dropright') // right align + .find('.slick-menu-item .slick-menu-content') + .contains('Contact Us') + .should('exist') + .trigger('mouseover'); // mouseover or click should work + + cy.get('.slick-submenu').should('have.length', 2); + cy.get('.slick-context-menu.slick-menu-level-2.dropright') // right align + .should('exist') + .find('.slick-menu-item .slick-menu-content') + .each(($command, index) => expect($command.text()).to.eq(subCommands2_1[index])); + + cy.get('.slick-context-menu.slick-menu-level-2'); + + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-command-list') + .find('.slick-menu-item .slick-menu-content') + .contains('Chat with us') + .click(); + cy.get('@alertStub').should('have.been.calledWith', 'Command: contact-chat'); + + cy.get('.slick-submenu').should('have.length', 0); + }); + + it('should toggle Select All checkbox and expect back "Sel" column title to show when Select All checkbox is shown in the header row', () => { + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('contain', 'Sel'); + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('not.contain', 'Sel'); + + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); + }); + + it('should toggle back Select All checkbox and expect back "Sel" column title to show when Select All checkbox is shown in the header row', () => { + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('contain', 'Sel'); + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)').find('.slick-column-name').should('not.contain', 'Sel'); + + cy.get('.slick-header-columns .slick-header-column').each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); + }); + + it('should open Column Picker and try unchecked all the columns on the right of the column pinning and expect an error and abort of the execution', () => { + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + cy.get('[data-test=set-3pinned-columns]').click({ force: true }); + + const leftColumns = ['', 'Title', '% Complete']; + const rightColumns = ['Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + cy.get('#grid20').find('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') + .children() + .each(($child, index) => { + if (index >= leftColumns.length) { + if ($child.text() === rightColumns[index - leftColumns.length]) { + expect($child.text()).to.eq(rightColumns[index - leftColumns.length]); + if (index <= rightColumns.length + 1) { + cy.wrap($child).children('label').click(); + } else { + cy.wrap($child) + .children('label') + .click() + .then(() => { + cy.get('@alertStub').should( + 'have.been.calledWith', + '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + + 'You could alternatively unpin columns before trying again.' + ); + }); + } + } + } + }); + + cy.get('button[data-dismiss="slick-column-picker"]').click(); + }); + + it('should also not be able to "Hide Column" via the Header Menu', () => { + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + const newColumnList = ['', 'Title', '% Complete', 'Action']; + + cy.get('#grid20').find('.slick-header-column:nth(3)').trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + + cy.get('.slick-header-menu .slick-menu-command-list') + .should('be.visible') + .children('.slick-menu-item') + .contains('Hide Column') + .click() + .then(() => { + cy.get('@alertStub').should( + 'have.been.calledWith', + '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + + 'You could alternatively unpin columns before trying again.' + ); + }); + cy.get('#grid20') - .find('.slick-header-columns:nth(0)') + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); + }); + + it('should be able to uncheck "Title" column without any alert', () => { + cy.get('#grid20').find('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + const updatedColumns = ['', '% Complete', 'Action']; + cy.get('.slick-column-picker-list li:not(.hidden) .checkbox-picker-label').first().click(); + cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); + cy.get('.slick-header-columns:nth(0) .slick-header-column').each(($child, index) => expect($child.text()).to.eq(updatedColumns[index])); + }); + + it('should be able to add back hidden "Title" column without any alert', () => { + const updatedColumns = ['', 'Title', '% Complete', 'Action']; + cy.get('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + cy.get('.slick-column-picker-list li:not(.hidden) .checkbox-picker-label').first().click(); + cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); + cy.get('.slick-header-columns:nth(0) .slick-header-column').each(($child, index) => expect($child.text()).to.eq(updatedColumns[index])); + }); + + it('should reset hidden column from the Column Picker and expect all columns to be back', () => { + const leftColumns = ['', 'Title', '% Complete']; + const rightColumns = ['Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + + cy.get('.slick-header-column').first().trigger('mouseover').trigger('contextmenu').invoke('show'); + cy.get('.slick-column-picker') + .find('.slick-column-picker-list') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => { + if (index >= leftColumns.length) { + if ($child.text() === rightColumns[index - leftColumns.length]) { + expect($child.text()).to.eq(rightColumns[index - leftColumns.length]); + if (index <= rightColumns.length + 1) { + cy.wrap($child).children('label').click(); + } + } + } + }); + + cy.get('.slick-column-picker:visible').find('.close').trigger('click').click(); + + cy.get('#grid20') + .find('.slick-header-columns .slick-header-column') + .each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); }); - it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { - cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); + describe('Test UI rendering after Scrolling with large columns', () => { + it('should unpin all columns/rows', () => { + cy.get('#grid20').find('button.slick-grid-menu-button').click({ force: true }); + + cy.contains('Unpin Columns/Rows').click({ force: true }); + }); + + it('should resize all columns and make them wider', () => { + // resize CityOfOrigin column + cy.get('.slick-header-columns .slick-header-column:nth(7)').should('contain', 'City of Origin'); + + cy.get('.slick-resizable-handle:nth(7)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(8)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); - cy.contains('Unfreeze Columns/Rows').click({ force: true }); + // resize Cost|Duration column + cy.get('.slick-header-columns .slick-header-column:nth(6)').should('contain', 'Cost | Duration'); - cy.get('[style="transform: translateY(0px);"]').should('have.length', 1); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"]').children().should('have.length', 11); + cy.get('.slick-resizable-handle:nth(6)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(0)').should('contain', '0'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + cy.get('.slick-header-column:nth(8)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Completed column + cy.get('.slick-header-columns .slick-header-column:nth(5)').should('contain', 'Completed'); + + cy.get('.slick-resizable-handle:nth(5)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(7)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Finish column + cy.get('.slick-header-columns .slick-header-column:nth(4)').should('contain', 'Finish'); + + cy.get('.slick-resizable-handle:nth(4)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(6)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Start column + cy.get('.slick-header-columns .slick-header-column:nth(3)').should('contain', 'Start'); + + cy.get('.slick-resizable-handle:nth(3)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(6)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize %Complete column + cy.get('.slick-header-columns .slick-header-column:nth(2)').should('contain', '% Complete'); + + cy.get('.slick-resizable-handle:nth(2)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(3)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + + // resize Title column + cy.get('.slick-header-columns .slick-header-column:nth(1)').should('contain', 'Title'); + + cy.get('.slick-resizable-handle:nth(1)').trigger('mousedown', { which: 1, force: true }).trigger('mousemove', 'bottomRight'); + + cy.get('.slick-header-column:nth(3)') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { which: 1, force: true }); + }); + + it('should scroll horizontally completely to the right and expect all cell to be rendered', () => { + getCell(2, 1).contains(/Task [0-9]*/); + getCell(2, 2).contains(/[0-9]*/); + + getCell(15, 1).contains(/Task [0-9]*/); + getCell(15, 2).contains(/[0-9]*/); + + // horizontal scroll to right + // Pinning has one real horizontal scroll owner. Scrolling the old body + // viewport only exercises the compatibility bridge; target the proxy + // here to verify the user-facing scrollbar and all chrome move together. + cy.get('#grid20 .slick-horizontal-scroller').scrollTo('100%', '0%', { duration: 1500 }); + getCell(2, 3).should('contain', '2009-01-01'); + getCell(2, 4).should('contain', '2009-05-05'); + getCell(2, 7).contains(/[United State|Canada]*/); + getCell(2, 8).should('contain', 'Action'); + + getCell(15, 3).should('contain', '2009-01-01'); + getCell(15, 4).should('contain', '2009-05-05'); + getCell(15, 7).contains(/[United State|Canada]*/); + getCell(15, 8).should('contain', 'Action'); + }); + + it('should scroll vertically to the middle of the grid and expect all cell to be rendered', () => { + // vertical scroll to middle + cy.get('.slick-vertical-scroller').scrollTo('0%', '40%', { duration: 1500 }); + + getCell(200, 3).should('contain', '2009-01-01'); + getCell(200, 4).should('contain', '2009-05-05'); + getCell(200, 7).contains(/[United State|Canada]*/); + getCell(200, 8).should('contain', 'Action'); + + getCell(205, 3).should('contain', '2009-01-01'); + getCell(205, 4).should('contain', '2009-05-05'); + getCell(205, 7).contains(/[United State|Canada]*/); + getCell(205, 8).should('contain', 'Action'); + + // reset scroll + cy.get('.slick-vertical-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + }); + }); + + describe('accessibility sub-menus tests', () => { + beforeEach(() => { + // Open the context menu on a cell to start each test + cy.get('.slick-vertical-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('[data-row="0"] .slick-cell.l3.r3').rightclick({ force: true }); + cy.get('.slick-context-menu.slick-menu-level-0').should('be.visible'); + }); + + it('should open Exports sub-menu with ArrowRight, then Excel sub-menu with ArrowRight, and close with ArrowLeft', () => { + // Move down to "Exports" (4th item) + cy.focused(); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.get('.slick-context-menu.slick-menu-level-0 .slick-submenu-item[data-command="export"]').should('have.focus'); + + // Open "Exports" sub-menu with ArrowRight + cy.focused().type('{rightarrow}'); + cy.get('.slick-context-menu.slick-menu-level-1[data-sub-menu-parent="export"]').should('be.visible'); + + // Move down to "Excel" (2nd item in sub-menu) + cy.focused().type('{downarrow}'); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-submenu-item[data-command="sub-menu"]').should('have.focus'); + + // Open "Excel" sub-menu with ArrowRight + cy.focused().type('{rightarrow}'); + cy.get('.slick-context-menu.slick-menu-level-2[data-sub-menu-parent="sub-menu"]').should('be.visible'); + + // Move down to "Excel (xlsx)" (2nd item in Excel sub-menu) + cy.focused().type('{downarrow}'); + cy.get('.slick-context-menu.slick-menu-level-2 .slick-menu-item[data-command="exports-xlsx"]').should('have.focus'); + + // Close Excel sub-menu with ArrowLeft + cy.focused().type('{leftarrow}'); + cy.get('.slick-context-menu.slick-menu-level-2').should('not.exist'); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-submenu-item[data-command="sub-menu"]').should('have.focus'); + + // close all context menus + cy.get('[data-row="0"] .slick-cell.l0.r0').click(); + }); + + it('should open sub-menus using Enter as well as ArrowRight', () => { + // Move down to "Exports" + cy.focused(); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.get('.slick-context-menu.slick-menu-level-0 .slick-submenu-item[data-command="export"]').should('have.focus'); + + // Open "Exports" sub-menu with Enter + cy.focused().type('{enter}'); + cy.get('.slick-context-menu.slick-menu-level-1[data-sub-menu-parent="export"]').should('be.visible'); + + // Move down to "Excel" + cy.focused().type('{downarrow}'); + cy.get('.slick-context-menu.slick-menu-level-1 .slick-submenu-item[data-command="sub-menu"]').should('have.focus'); + + // Open "Excel" sub-menu with Enter + cy.focused().type('{enter}'); + cy.get('.slick-context-menu.slick-menu-level-2[data-sub-menu-parent="sub-menu"]').should('be.visible'); + + // close all context menus + cy.get('[data-row="0"] .slick-cell.l0.r0').click(); + }); + + it('should activate a sub-menu leaf item with Enter', () => { + // Move down to "Exports" + cy.window().then((win) => { + cy.stub(win, 'alert').as('alertStub'); + }); + cy.focused(); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.DOWN); + cy.press(Cypress.Keyboard.Keys.ENTER); + cy.get('.slick-context-menu.slick-menu-level-1[data-sub-menu-parent="export"]').should('be.visible'); + + // "Text (tab delimited)" is first item, should have focus + cy.get('.slick-context-menu.slick-menu-level-1 .slick-menu-item[data-command="exports-txt"]').should('have.focus'); + // Activate with Enter (add your assertion for the result) + cy.focused().type('{enter}'); + }); + + it('should reapply 3 Pinned Columns and expect to be able to focus on first filter and go left/right between both viewports without problems', () => { + cy.get('[data-test="set-3pinned-columns"]').click(); + cy.get('.slick-headerrow-column.l1 input').focus(); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l2 input').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l3 select').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l3 input').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l4 select').should('have.focus'); + cy.press(Cypress.Keyboard.Keys.TAB); + cy.get('.slick-headerrow-column.l4 input').should('have.focus'); + + // Shift+Tab dosn't work in Cypress, so we can't go further with tests + }); + }); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(3)').should('contain', '2009-01-01'); - cy.get('.grid-canvas-left > [style="transform: translateY(0px);"] > .slick-cell:nth(4)').should('contain', '2009-05-05'); + describe('drag & drop column reordering with auto-scroll', () => { + it('should auto-scroll right viewport and reorder columns when "Start" is dragged well past the right edge (ending up after "Finish")', () => { + // Close the context menu opened by beforeEach + cy.get('body').type('{esc}'); + + // Control app timers to make drag auto-scroll deterministic in CI. + cy.clock(); + + // Normalize right viewport scroll so this test is isolated from previous test state. + cy.get('.slick-horizontal-scroller').then(($viewport) => { + $viewport[0].scrollLeft = 0; + $viewport[0].dispatchEvent(new Event('scroll', { bubbles: true })); + }); + cy.get('.slick-horizontal-scroller').its('0.scrollLeft').should('equal', 0); + cy.get('[data-test="set-large-pinned-columns"]').click(); + + // Step 1: call SortableJS onStart for the "Start" column (1st center-section column). + // This binds the document 'drag' auto-scroll listener for the shared proxy scrollbar. + cy.get('.slick-header-columns-center').then(($rightHeader) => { + let sortInstance: any; + Object.keys($rightHeader[0]).forEach((prop) => { + if (prop.startsWith('Sortable')) { + sortInstance = ($rightHeader[0] as any)[prop]; + } + }); + expect(sortInstance).to.exist; + const startColumnEl = $rightHeader[0].querySelectorAll('.slick-header-column')[0] as HTMLElement; + sortInstance.options.onStart({ item: startColumnEl }); + }); + + // Step 2: fire a document drag event well past the right edge (viewport-relative) + // to avoid CI flakiness caused by environment-dependent viewport widths. + cy.window().then((win) => { + const dragX = win.innerWidth + 1200; + cy.document().trigger('drag', { pageX: dragX, clientX: dragX, clientY: 50 }); + }); + + // Step 3: advance mocked time so the 30ms scroll interval ticks several times. + cy.tick(350); + + // Auto-scroll should have moved the right viewport to the right + cy.get('.slick-horizontal-scroller').its('0.scrollLeft').should('be.greaterThan', 0); + + // Step 4: simulate the drag result — "Start" was moved to the right, past "Finish". + // SortableJS reads the DOM order via toArray() inside onEnd, so physically reorder the children first. + cy.get('.slick-header-columns-center').then(($rightHeader) => { + let sortInstance: any; + Object.keys($rightHeader[0]).forEach((prop) => { + if (prop.startsWith('Sortable')) { + sortInstance = ($rightHeader[0] as any)[prop]; + } + }); + expect(sortInstance).to.exist; + const startColumnEl = $rightHeader[0].querySelector('[data-id="start"]') as HTMLElement; + const finishColumnEl = $rightHeader[0].querySelector('[data-id="finish"]') as HTMLElement; + expect(startColumnEl).to.exist; + expect(finishColumnEl).to.exist; + + // Move "Finish" before "Start" → mirrors dragging Start past Finish + $rightHeader[0].insertBefore(finishColumnEl, startColumnEl); + + // onEnd reads the new DOM order via toArray() and calls setColumns() if the order changed + sortInstance.options.onEnd({ item: startColumnEl, stopPropagation: () => {} }); + }); + + // The center region should now place Finish before Start. The exact + // region membership can vary when a large requested pin band does not + // fit the current viewport, so assert semantic order by column id. + cy.get('.slick-header-column').then(($headers) => { + const ids = [...$headers].map((header) => header.dataset.id); + expect(ids.indexOf('finish')).to.be.lessThan(ids.indexOf('start')); + }); + + // When a left band is active, its order must remain unchanged. + cy.get('.slick-header-columns-left').then(($leftRegion) => { + const left = $leftRegion.find('.slick-header-column'); + if (left.length) { + expect([...left].map((header) => header.dataset.id)).to.deep.equal(['_checkbox_selector', 'title', 'percentComplete']); + } + }); + }); }); }); diff --git a/demos/react/test/cypress/e2e/example24.cy.ts b/demos/react/test/cypress/e2e/example24.cy.ts index c2ea74764b..b73ab056b1 100644 --- a/demos/react/test/cypress/e2e/example24.cy.ts +++ b/demos/react/test/cypress/e2e/example24.cy.ts @@ -232,7 +232,7 @@ describe('Example 24 - Cell Menu & Context Menu Plugins', () => { }); it('should check Context Menu "menuUsabilityOverride" condition and expect to not be able to open Context Menu from rows than are >= to Task 21', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('bottom').wait(25); + cy.get('.slick-vertical-scroller').scrollTo('bottom').wait(25); cy.get('#grid24').find('.slick-row:nth(3) .slick-cell:nth(1)').rightclick({ force: true }); @@ -240,7 +240,7 @@ describe('Example 24 - Cell Menu & Context Menu Plugins', () => { }); it('should scroll back to top row and be able to open Context Menu', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top').wait(25); + cy.get('.slick-vertical-scroller').scrollTo('top').wait(25); cy.get('#grid24').find('.slick-row:nth(1) .slick-cell:nth(1)').rightclick({ force: true }); diff --git a/demos/react/test/cypress/e2e/example27.cy.ts b/demos/react/test/cypress/e2e/example27.cy.ts index 74b4750874..a6a3c1b699 100644 --- a/demos/react/test/cypress/e2e/example27.cy.ts +++ b/demos/react/test/cypress/e2e/example27.cy.ts @@ -157,7 +157,7 @@ describe('Example 27 - Tree Data (from a flat dataset with parentId references)' it('should be able to update the 1st row item (Task 0)', () => { cy.get('[data-test=update-item-btn]').contains('Update 1st Row Item').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); const now = new Date(); const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -198,7 +198,7 @@ describe('Example 27 - Tree Data (from a flat dataset with parentId references)' cy.get(`.slick-grid-menu:visible`).find('.slick-menu-item').first().find('span').contains('Clear all Filters').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); }); it('should be able to open "Task 1" and "Task 3" parents', () => { diff --git a/demos/react/test/cypress/e2e/example28.cy.ts b/demos/react/test/cypress/e2e/example28.cy.ts index 8f8f5e2228..123830953d 100644 --- a/demos/react/test/cypress/e2e/example28.cy.ts +++ b/demos/react/test/cypress/e2e/example28.cy.ts @@ -84,7 +84,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { it('should expand "pdf" folder and expect all folders to be expanded', () => { cy.get('[data-row="4"] > .slick-cell:nth(0) .slick-group-toggle.collapsed').click(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('top', { force: true } as any); }); it('should have default Files list', () => { @@ -97,7 +97,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with aggregations of Sum(53.3MB) / Avg(26.65MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('center', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('center', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 151.3 MB / avg: 50.43 MB'); @@ -118,7 +118,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with updated aggregations including new pop songs of Sum(218.3MB) / Avg(54.58MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 316.3 MB / avg: 63.26 MB'); @@ -225,7 +225,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 400.3 MB / avg: 66.72 MB'); @@ -308,7 +308,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have again the pop songs folder with updated aggregations including 4 pop songs of Sum(400.3MB) / Avg(66.72MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('center', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('center', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 400.3 MB / avg: 66.72 MB'); @@ -336,7 +336,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have pop songs folder with aggregation reflecting what is displayed, Sum(316.3MB) / Avg(63.26MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('center', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('center', { force: true } as any); cy.get('[data-row="16"] > .slick-cell:nth(0)').should('contain', 'music'); cy.get('[data-row="16"] > .slick-cell:nth(3)').should('contain', 'sum: 316.3 MB / avg: 63.26 MB'); @@ -347,7 +347,7 @@ describe('Example 28 - Tree Data (from a Hierarchical Dataset)', () => { }); it('should have documents with same Sum as the beginning since auto-recalc is disabled, aggregation should be Sum(14.46MB) / Avg(1.45MB)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top', { force: true } as any); + cy.get('.slick-vertical-scroller').scrollTo('top', { force: true } as any); cy.get('[data-row="1"] > .slick-cell:nth(0)').should('contain', 'documents'); cy.get('[data-row="1"] > .slick-cell:nth(3)').should('contain', 'sum: 14.46 MB / avg: 1.45 MB (total)'); diff --git a/demos/react/test/cypress/e2e/example38.cy.ts b/demos/react/test/cypress/e2e/example38.cy.ts index a179cdc5cb..02b700180d 100644 --- a/demos/react/test/cypress/e2e/example38.cy.ts +++ b/demos/react/test/cypress/e2e/example38.cy.ts @@ -20,7 +20,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -32,7 +32,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a new total of 90 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '60'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '90'); @@ -48,7 +48,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { cy.get('[data-test="data-loaded-tag"]').should('not.have.class', 'fully-loaded'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '100'); @@ -78,7 +78,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom again and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -104,7 +104,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { it('should scroll to bottom again and expect next batch to be only 20 females appended to current dataset for a total of 50 items found in DB', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '50'); @@ -119,7 +119,7 @@ describe('Example 38 - Infinite Scroll with OData', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-test=odata-query-result]').should(($span) => { expect($span.text()).to.eq(`$count=true&$top=30`); @@ -130,11 +130,11 @@ describe('Example 38 - Infinite Scroll with OData', () => { }); it('should scroll to the bottom "Group by Gender" and expect 30 more items for a total of 60 items grouped', () => { - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-test=odata-query-result]').should(($span) => { expect($span.text()).to.eq(`$count=true&$top=30&$skip=30`); diff --git a/demos/react/test/cypress/e2e/example39.cy.ts b/demos/react/test/cypress/e2e/example39.cy.ts index d7091726f6..a87b8ef667 100644 --- a/demos/react/test/cypress/e2e/example39.cy.ts +++ b/demos/react/test/cypress/e2e/example39.cy.ts @@ -29,7 +29,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -44,7 +44,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom of the grid and expect next batch of 30 items appended to current dataset for a new total of 90 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '60'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '90'); @@ -63,7 +63,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { cy.get('[data-test="data-loaded-tag"]').should('not.have.class', 'fully-loaded'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '100'); @@ -100,7 +100,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom again and expect next batch of 30 items appended to current dataset for a total of 60 items', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '60'); @@ -135,7 +135,7 @@ describe('Example 39 - Infinite Scroll with GraphQL', () => { it('should scroll to bottom again and expect next batch to be only 20 females appended to current dataset for a total of 50 items found in DB', () => { cy.get('[data-test="itemCount"]').should('have.text', '30'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom'); + cy.get('.slick-vertical-scroller').scrollTo('bottom'); cy.get('[data-test="itemCount"]').should('have.text', '50'); diff --git a/demos/react/test/cypress/e2e/example40.cy.ts b/demos/react/test/cypress/e2e/example40.cy.ts index 4f8eeb96e2..4ef56ccdec 100644 --- a/demos/react/test/cypress/e2e/example40.cy.ts +++ b/demos/react/test/cypress/e2e/example40.cy.ts @@ -25,14 +25,14 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { it('should scroll to bottom of the grid and expect next batch of 50 items appended to current dataset for a total of 100 items', () => { cy.get('[data-test="totalItemCount"]').should('have.text', '50'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '100'); }); it('should scroll to bottom of the grid again and expect 50 more items for a total of now 150 items', () => { cy.get('[data-test="totalItemCount"]').should('have.text', '100'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '150'); }); @@ -42,7 +42,7 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { cy.get('[data-id="title"]').click(); cy.get('[data-test="totalItemCount"]').should('have.text', '150'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0)').should('contain', 'Task 0'); cy.get('[data-row="1"] > .slick-cell:nth(0)').should('contain', 'Task 1'); @@ -55,7 +55,7 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { cy.get('[data-id="title"]').click(); cy.get('[data-test="totalItemCount"]').should('have.text', '50'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0)').should('contain', 'Task 9'); cy.get('[data-row="1"] > .slick-cell:nth(0)').should('contain', 'Task 8'); @@ -68,17 +68,17 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { cy.get('[data-test="totalItemCount"]').should('have.text', '50'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-toggle.expanded').should('have.length', 1); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-title').contains(/Duration: [0-9]/); }); it('should scroll to the bottom "Group by Duration" and expect 50 more items for a total of 100 items grouped', () => { - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '100'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-toggle.expanded').should('have.length', 1); cy.get('[data-row="0"] > .slick-cell:nth(0) .slick-group-title').contains(/Duration: [0-9]/); }); @@ -103,12 +103,12 @@ describe('Example 40 - Infinite Scroll from JSON data', () => { it('should load 200 items and filter "Start" column with <=2020-08-25', () => { cy.get('[data-test="set-dynamic-filter"]').click(); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '150'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('bottom', { timeout: 100 }); + cy.get('.slick-vertical-scroller').scrollTo('bottom', { timeout: 100 }); cy.get('[data-test="totalItemCount"]').should('have.text', '200'); - cy.get('.slick-viewport.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get(`[data-row=0] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/); cy.get(`[data-row=1] > .slick-cell:nth(3)`).contains(/^Aug [0-9]{2}, 2020$/); diff --git a/demos/react/test/cypress/e2e/example43.cy.ts b/demos/react/test/cypress/e2e/example43.cy.ts index c184db9802..ca0c3b17cb 100644 --- a/demos/react/test/cypress/e2e/example43.cy.ts +++ b/demos/react/test/cypress/e2e/example43.cy.ts @@ -1,4 +1,4 @@ -describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 1 }, () => { +describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 0 }, () => { const GRID_ROW_HEIGHT = 30; const fullTitles = [ 'Employee ID', @@ -27,15 +27,23 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 1 }, cy.get('h2').should('contain', 'Example 43: colspan/rowspan - Employees Timesheets'); }); + it('should hide sub-title', () => { + cy.get('[data-test=toggle-subtitle]').click(); + }); + it('should have exact column titles', () => { cy.get('.slick-header-columns') .children() .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); }); - it('should expect 1st column to be frozen (frozen)', () => { - cy.get('.grid-canvas-left .slick-cell.frozen').should('have.length', 10); - cy.get('.grid-canvas-right .slick-cell:not(.frozen)').should('have.length.above', 50); + it('should expect 1st column to be pinned', () => { + // Pinning uses one live canvas and splits each row into left/center/right + // regions; the old grid-canvas-left/right pinned panes no longer exist. + cy.get('.slick-pinned-left-cells .slick-cell.slick-cell-pinned-left').should('have.length', 10); + // The shared Vue Cypress viewport is 1200px wide, but its route sidebar + // leaves less room for center-column virtualization than the Vanilla app. + cy.get('.slick-scrolling-cells .slick-cell').should('have.length.above', 50); }); it('should not display any Column Picker in the Grid Menu', () => { @@ -47,32 +55,32 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 1 }, describe('Spanning', () => { it('should expect "Davolio", "Check Mail", and "Development" to all have rowspan of 2 in morning hours', () => { - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should(($el) => + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should(($el) => + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); }); it('should expect "Lunch Break" to span over 3 columns and over all rows', () => { - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10) ); }); it('should expect a large "Development" section that spans over multiple columns & rows in the afternoon', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should(($el) => + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) ); }); @@ -80,334 +88,340 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 1 }, describe('Basic Key Navigations', () => { it('should start at Employee 10001, then type "End" key and expect to be in "Team Meeting" between 4:30-5:00pm', () => { - cy.get('[data-row=0] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l0.r0.active').should('contain', '10001'); + cy.get('[data-row=0] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l0.r0.active').should('contain', '10001'); cy.get('@active_cell').type('{end}'); - cy.get('[data-row=0] > .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); }); it('should start at Employee 10002, then type "End" key and also expect to be in "Team Meeting" between 4:30-5:00pm', () => { - cy.get('[data-row=1] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=1] > .slick-cell.l0.r0.active').should('contain', '10002'); + cy.get('[data-row=1] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=1] .slick-cell.l0.r0.active').should('contain', '10002'); cy.get('@active_cell').type('{end}'); - cy.get('[data-row=0] > .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l17.r18.active').should('contain', 'Team Meeting'); }); it('should start at Employee 10004, then type "ArrowRight" key twice and expect to be in "Check Mail" between 9:00-10:30am', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}'); - cy.get('[data-row=2] > .slick-cell.l2.r4.active').should('contain', 'Check Mail'); + cy.get('[data-row=2] .slick-cell.l2.r4.active').should('contain', 'Check Mail'); }); it('should start at Employee 10004, then type "ArrowRight" key 4x times and expect to be in "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); - cy.get('[data-row=3] > .slick-cell.l6.r9.active').should('contain', 'Testing'); + cy.get('[data-row=3] .slick-cell.l6.r9.active').should('contain', 'Testing'); }); it('should start at Employee 10004, then type "ArrowRight" key 5x times and expect to be in "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, then type "ArrowRight" key 6x times and expect to be in "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}{rightarrow}'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); // then rollback by going backward it('should be on Employee 10004 row at previous "Development" cell, then type "ArrowLeft" key once and expect to be in "Lunch Break"', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).as('active_cell').click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + // The proxy scrollbar is a sibling of the canvas, so native + // scrollIntoView() cannot reveal a horizontally virtualized cell. + cy.get('.slick-horizontal-scroller').scrollTo('right', { ensureScrollable: false }); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).as('active_cell').scrollIntoView().click(); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); cy.get('@active_cell').type('{leftarrow}'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key once and expect to be in "Conference" between 4:00-5:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).type('{leftarrow}'); - cy.get(`[data-row=3] > .slick-cell.l16.r17.active`).should('contain', 'Conference'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).type('{leftarrow}'); + cy.get(`[data-row=3] .slick-cell.l16.r17.active`).should('contain', 'Conference'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 3x times and expect to be back to "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).type('{leftarrow}{leftarrow}{leftarrow}'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).type('{leftarrow}{leftarrow}{leftarrow}'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 4x times and expect to be back to "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "ArrowLeft" key 5x times and expect to be back to "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{leftarrow}{leftarrow}{leftarrow}{leftarrow}{leftarrow}'); - cy.get(`[data-row=3] > .slick-cell.l6.r9.active`).should('contain', 'Testing'); + cy.get(`[data-row=3] .slick-cell.l6.r9.active`).should('contain', 'Testing'); }); // going down it('should start at 10am "Team Meeting, then type "ArrowDown" key once and expect to be in "Support" between 9:30-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}'); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key twice and expect to be in "Check Email" between 9:00-10:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 3x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}'); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}'); - cy.get(`[data-row=5] > .slick-cell.l4.r6.active`).should('contain', 'Support'); + cy.get(`[data-row=5] .slick-cell.l4.r6.active`).should('contain', 'Support'); }); // going up from inverse it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" once and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}'); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 2x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 3x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}{uparrow}'); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 4x times and expect to be back to same "Team Meeting"', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('@active_cell').type('{downarrow}{downarrow}{downarrow}{downarrow}{uparrow}{uparrow}{uparrow}{uparrow}'); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); }); }); describe('Grid Navigate Functions', () => { it('should start at Employee 10004, then type "Navigate Right" twice and expect to be in "Check Mail" between 9:00-10:30am', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); cy.get('[data-test="goto-next"]').click().click(); - cy.get('[data-row=2] > .slick-cell.l2.r4.active').should('contain', 'Check Mail'); + cy.get('[data-row=2] .slick-cell.l2.r4.active').should('contain', 'Check Mail'); }); it('should start at Employee 10004, then type "Navigate Right" 4x times and expect to be in "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('[data-test="goto-next"]').click().click().click().click(); - cy.get('[data-row=3] > .slick-cell.l6.r9.active').should('contain', 'Testing'); + cy.get('[data-row=3] .slick-cell.l6.r9.active').should('contain', 'Testing'); }); it('should start at Employee 10004, then type "Navigate Right" 5x times and expect to be in "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('[data-test="goto-next"]').click().click().click().click().click(); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, then type "Navigate Right" 6x times and expect to be in "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('[data-test="goto-next"]').click().click().click().click().click().click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); // then rollback by going backward it('should be on Employee 10004 row at previous "Development" cell, then type "Navigate Left" once and expect to be in "Lunch Break"', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).as('active_cell').click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get('.slick-horizontal-scroller').scrollTo('right', { ensureScrollable: false }); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).as('active_cell').scrollIntoView().click(); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); cy.get('[data-test="goto-prev"]').click(); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" once and expect to be in "Conference" between 4:00-5:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click(); - cy.get(`[data-row=3] > .slick-cell.l16.r17.active`).should('contain', 'Conference'); + cy.get(`[data-row=3] .slick-cell.l16.r17.active`).should('contain', 'Conference'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 3x times and expect to be back to "Development" between 2:30-3:30pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click().click().click(); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan.active`).should('contain', 'Development'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 4x times and expect to be back to "Lunch Break"', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click().click().click().click(); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan.active`).should('contain', 'Lunch Break'); }); it('should start at Employee 10004, type "End" and be at "Team Meeting" at 5pm, then type "Navigate Left" 5x times and expect to be back to "Testing" between 11:00-1:00pm', () => { - cy.get('[data-row=3] > .slick-cell.l0.r0').as('active_cell').click(); - cy.get('[data-row=3] > .slick-cell.l0.r0.active').should('contain', '10004'); + cy.get('[data-row=3] .slick-cell.l0.r0').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=3] .slick-cell.l0.r0.active').should('contain', '10004'); cy.get('@active_cell').type('{end}'); - cy.get(`[data-row=3] > .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); + cy.get(`[data-row=3] .slick-cell.l18.r18.active`).as('active_cell').should('contain', 'Team Meeting'); cy.get('[data-test="goto-prev"]').click().click().click().click().click(); - cy.get(`[data-row=3] > .slick-cell.l6.r9.active`).should('contain', 'Testing'); + cy.get(`[data-row=3] .slick-cell.l6.r9.active`).should('contain', 'Testing'); }); // going down it('should start at 10am "Team Meeting, then type "ArrowDown" key once and expect to be in "Support" between 9:30-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0, { ensureScrollable: false }); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click(); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key twice and expect to be in "Check Email" between 9:00-10:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click(); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 3x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click(); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); - cy.get(`[data-row=5] > .slick-cell.l4.r6.active`).should('contain', 'Support'); + cy.get(`[data-row=5] .slick-cell.l4.r6.active`).should('contain', 'Support'); }); // going up from inverse it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" once and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click(); - cy.get(`[data-row=4] > .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); + cy.get(`[data-row=4] .slick-cell.l2.r5.active`).should('contain', 'Task Assign'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 2x times and expect to be in "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click().click(); - cy.get(`[data-row=2] > .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.active`).should('contain', 'Check Mail'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 3x times and expect to be in "Support" between 10:00-11:30am', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click().click().click(); - cy.get(`[data-row=1] > .slick-cell.l3.r5.active`).should('contain', 'Support'); + cy.get(`[data-row=1] .slick-cell.l3.r5.active`).should('contain', 'Support'); }); it('should start at 10am "Team Meeting, then type "ArrowDown" key 4x times, then "ArrowUp" 4x times and expect to be back to same "Team Meeting"', () => { - cy.get('[data-row=0] > .slick-cell.l4.r4').as('active_cell').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4').as('active_cell').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); cy.get('[data-test="goto-down"]').click().click().click().click(); cy.get('[data-test="goto-up"]').click().click().click().click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); + cy.get('[data-row=0] .slick-cell.l4.r4.active').should('contain', 'Team Meeting'); }); }); describe('Grid Editing', () => { it('should toggle editing', () => { cy.get('#isEditable').contains('false'); - cy.get('[data-row=0] > .slick-cell.l4.r4').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active .editor-text').should('not.exist'); + cy.get('[data-row=0] .slick-cell.l4.r4').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active .editor-text').should('not.exist'); cy.get('[data-test=toggle-editing]').click(); cy.get('#isEditable').contains('true'); - cy.get('[data-row=0] > .slick-cell.l4.r4').click(); - cy.get('[data-row=0] > .slick-cell.l4.r4.active.editable .editor-text').should('exist'); - cy.get('[data-row=0] > .slick-cell.l4.r4.active.editable .editor-text').type('Team Meeting.xyz{enter}'); + cy.get('[data-row=0] .slick-cell.l4.r4').scrollIntoView().click(); + cy.get('[data-row=0] .slick-cell.l4.r4.active.editable .editor-text').should('exist'); + cy.get('[data-row=0] .slick-cell.l4.r4.active.editable .editor-text').type('Team Meeting.xyz{enter}'); }); // going down it('should have changed active cell to "Support" between 9:30-11:00am', () => { - cy.get('[data-row=1] > .slick-cell.l3.r5.active.editable .editor-text') + cy.get('[data-row=1] .slick-cell.l3.r5.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Support')); - cy.get('[data-row=1] > .slick-cell.l3.r5.active.editable .editor-text').type('Support.xyz{enter}'); + cy.get('[data-row=1] .slick-cell.l3.r5.active.editable .editor-text').type('Support.xyz{enter}'); }); it('should have changed active cell to "Check Email" between 9:00-10:30am', () => { - cy.get('[data-row=2] > .slick-cell.l2.r4.active.editable .editor-text') + cy.get('[data-row=2] .slick-cell.l2.r4.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Check Mail')); - cy.get('[data-row=2] > .slick-cell.l2.r4.active.editable .editor-text').type('Check Mail.xyz{enter}'); + cy.get('[data-row=2] .slick-cell.l2.r4.active.editable .editor-text').type('Check Mail.xyz{enter}'); }); it('should have changed active cell to "Task Assign" between 9:00-11:00am', () => { - cy.get('[data-row=4] > .slick-cell.l2.r5.active.editable .editor-text') + cy.get('[data-row=4] .slick-cell.l2.r5.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Task Assign')); - cy.get('[data-row=4] > .slick-cell.l2.r5.active.editable .editor-text').type('Task Assign.xyz{enter}'); + cy.get('[data-row=4] .slick-cell.l2.r5.active.editable .editor-text').type('Task Assign.xyz{enter}'); }); it('should have changed active cell to "Support" between 10:00-11:30am', () => { - cy.get('[data-row=5] > .slick-cell.l4.r6.active.editable .editor-text') + cy.get('[data-row=5] .slick-cell.l4.r6.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Support')); - cy.get('[data-row=5] > .slick-cell.l4.r6.active.editable .editor-text').type('Support.xyz{enter}'); + cy.get('[data-row=5] .slick-cell.l4.r6.active.editable .editor-text').type('Support.xyz{enter}'); }); it('should have changed active cell to "Testing" and cancel editing when typing "Escape" key', () => { - cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text') + cy.get('[data-row=6] .slick-cell.l4.r4.active.editable .editor-text') .invoke('val') .then((text) => expect(text).to.eq('Testing')); - cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text').type('{esc}'); - cy.get('[data-row=6] > .slick-cell.l4.r4.active.editable .editor-text').should('not.exist'); + cy.get('[data-row=6] .slick-cell.l4.r4.active.editable .editor-text').type('{esc}'); + cy.get('[data-row=6] .slick-cell.l4.r4.active.editable .editor-text').should('not.exist'); }); }); @@ -418,32 +432,32 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 1 }, }); it('should expect EmployeeID to follow columns at index 0 column index', () => { - cy.get(`[data-row=0] > .slick-cell.l0.r0.rowspan`).should('contain', 'Davolio'); - cy.get(`[data-row=0] > .slick-cell.l0.r0.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l0.r0.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] .slick-cell.l0.r0.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=2] > .slick-cell.l1.r3.rowspan`).should('contain', 'Check Mail'); - cy.get(`[data-row=2] > .slick-cell.l1.r3.rowspan`).should(($el) => + cy.get(`[data-row=2] .slick-cell.l1.r3.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l1.r3.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=8] > .slick-cell.l6.r8.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=8] > .slick-cell.l6.r8.rowspan`).should(($el) => + cy.get(`[data-row=8] .slick-cell.l6.r8.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] .slick-cell.l6.r8.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); }); it('should expect "Lunch Break" to be moved to the left by 1 index less', () => { - cy.get(`[data-row=0] > .slick-cell.l9.r11.rowspan`).should('contain', 'Lunch Break'); - cy.get(`[data-row=0] > .slick-cell.l9.r11.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l9.r11.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l9.r11.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10) ); }); it('should expect "Development" to be moved to the left by 1 index less', () => { - cy.get(`[data-row=1] > .slick-cell.l12.r13.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=1] > .slick-cell.l12.r13.rowspan`).should(($el) => + cy.get(`[data-row=1] .slick-cell.l12.r13.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l12.r13.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) ); }); @@ -455,32 +469,32 @@ describe('Example 43 - colspan/rowspan - Employees Timesheets', { retries: 1 }, }); it('should expect EmployeeID to follow columns at index 1 column index', () => { - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); - cy.get(`[data-row=0] > .slick-cell.l1.r1.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should('contain', 'Davolio'); + cy.get(`[data-row=0] .slick-cell.l1.r1.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); - cy.get(`[data-row=2] > .slick-cell.l2.r4.rowspan`).should(($el) => + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should('contain', 'Check Mail'); + cy.get(`[data-row=2] .slick-cell.l2.r4.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=8] > .slick-cell.l7.r9.rowspan`).should(($el) => + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=8] .slick-cell.l7.r9.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 2) ); }); it('should expect "Lunch Break" to be moved to the right by 1 index less', () => { - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); - cy.get(`[data-row=0] > .slick-cell.l10.r12.rowspan`).should(($el) => + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should('contain', 'Lunch Break'); + cy.get(`[data-row=0] .slick-cell.l10.r12.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 10) ); }); it('should expect "Development" to be moved to the right by 1 index less and a large "Development" section that spans over multiple columns & rows in the afternoon', () => { - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); - cy.get(`[data-row=1] > .slick-cell.l13.r14.rowspan`).should(($el) => + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should('contain', 'Development'); + cy.get(`[data-row=1] .slick-cell.l13.r14.rowspan`).should(($el) => expect(parseInt(`${$el.outerHeight()}`, 10)).to.eq(GRID_ROW_HEIGHT * 5) ); }); diff --git a/demos/react/test/cypress/e2e/example44.cy.ts b/demos/react/test/cypress/e2e/example44.cy.ts index 5d5bf6f477..451c07efd7 100644 --- a/demos/react/test/cypress/e2e/example44.cy.ts +++ b/demos/react/test/cypress/e2e/example44.cy.ts @@ -30,8 +30,14 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { cy.get('h2').should('contain', 'Example 44: colspan/rowspan with large dataset'); }); + it('should hide sub-title', () => { + cy.get('[data-test=toggle-subtitle]').click(); + }); + it('should calculate a height that fits the wrapped Revenue Growth header', () => { - cy.get('.slick-header-auto-height').should('have.length', 2); + // The pinning POC uses one live header instead of separate left/right + // header panes, so auto-height is applied to a single header element. + cy.get('.slick-header-auto-height').should('have.length', 1); cy.get('.slick-header-auto-height') .first() .should(($header) => { @@ -58,7 +64,7 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { let draggedColumn: HTMLElement; cy.clock(); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).its('0.scrollLeft').should('equal', 0); + cy.get('.slick-horizontal-scroller').scrollTo(0, 0).its('0.scrollLeft').should('equal', 0); cy.get('.slick-header-columns-left').then(($header) => { const sortableProperty = Object.keys($header[0]).find((property) => property.startsWith('Sortable')); @@ -75,12 +81,12 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); cy.tick(300); - cy.get('.slick-viewport-top.slick-viewport-left').its('0.scrollLeft').should('be.greaterThan', 0); + cy.get('.slick-horizontal-scroller').its('0.scrollLeft').should('be.greaterThan', 0); cy.then(() => sortInstance.options.onEnd({ item: draggedColumn, stopPropagation: () => {} })); cy.get('.slick-header-column:nth(0)').should('contain', 'Title'); cy.get('.slick-header-column:nth(1)').should('contain', 'Revenue Growth'); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0); + cy.get('.slick-vertical-scroller').scrollTo(0, 0); }); it('should drag Title column to swap with 2nd column "Revenue Growth" in the grid and expect rowspan to stay at same position with Task 0 to spread instead', () => { @@ -208,7 +214,7 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); it('should scroll to the right and still expect spans without any extra texts', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(400, 0).wait(10); + cy.get('.slick-horizontal-scroller').scrollTo(400, 0).wait(10); cy.get(`[data-row=3] > .slick-cell:nth(1)`).contains(/\d+$/); cy.get(`[data-row=3] > .slick-cell.l1.r2.rowspan`).should('exist'); @@ -229,7 +235,7 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); it('should scroll back to left and expect Task 8 to have 2 different spans (Revenue Grow: rowspan=80, Policy Index: rowspan=2000,colspan=2)', () => { - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo(0, 0).wait(10); + cy.get('.slick-vertical-scroller').scrollTo(0, 0).wait(10); cy.get(`[data-row=8] > .slick-cell.l0.r0`).should('contain', 'Task 8'); cy.get(`[data-row=8] > .slick-cell.l1.r1.rowspan`).should(($el) => { @@ -350,6 +356,10 @@ describe('Example 44 - Column & Row Span', { retries: 0 }, () => { }); it('should start at RevenueGrowth column on first dashed cell, then type "Ctrl+End" then "Ctrl+Home" keys and expect active cell to go to bottom/top of grid on same column', () => { + // The preceding span/scroll cases share state. Reset both axes through + // the POC's actual scroll owners before interacting with the first row. + cy.get('.slick-horizontal-scroller').scrollTo(0, 0); + cy.get('.slick-vertical-scroller').scrollTo(0, 0); cy.get('[data-row=0] > .slick-cell.l2.r2').as('active_cell').click(); cy.get('[data-row=0] > .slick-cell.l2.r2.active').should('have.length', 1); cy.get('@active_cell').type('{ctrl}{end}', { release: false }); diff --git a/demos/react/test/cypress/e2e/example45.cy.ts b/demos/react/test/cypress/e2e/example45.cy.ts index 6f56e1a753..b4485b498b 100644 --- a/demos/react/test/cypress/e2e/example45.cy.ts +++ b/demos/react/test/cypress/e2e/example45.cy.ts @@ -224,9 +224,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { }); it('should scroll down when the row detail is just barely visible and then scroll back up and still expect same filters/sorting', () => { - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 350); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 350); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should('contain', '10281'); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); @@ -236,9 +236,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { it('should scroll down by 2 pages down and then scroll back up and no longer the same filters/sorting', () => { cy.get('#grid45 [data-row="0"] .slick-cell.r2.l2').first().click().type('{pagedown}'); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 2000); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 2000); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should( 'not.contain', @@ -419,7 +419,7 @@ describe('Example 45 - Row Detail with inner Grid', () => { cy.get('#grid45').type('{pageDown}{pageDown}', { release: false }); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 350); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 350); // expect same grid details for both grids // 2nd row detail @@ -520,9 +520,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { }); it('should scroll far down (out of viewport) and back up and expect inner grid sort/filter state is PRESERVED (keepComponentAlive)', () => { - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 800); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 800); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); // state should be PRESERVED because keepComponentAlive is enabled cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should('contain', '10281'); @@ -532,9 +532,9 @@ describe('Example 45 - Row Detail with inner Grid', () => { }); it('should scroll out of viewport a second time and back up and still expect inner grid sort/filter state is PRESERVED', () => { - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 800); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 800); cy.wait(50); - cy.get('#grid45 .slick-viewport-top.slick-viewport-left').first().scrollTo(0, 0); + cy.get('#grid45 .slick-vertical-scroller').first().scrollTo(0, 0); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(0)`).should('contain', '10281'); cy.get(`#innergrid-1 .slick-row[style*="translateY(${GRID_ROW_HEIGHT * 0}px)"] > .slick-cell:nth(1)`).should('contain', 'Madrid'); diff --git a/demos/react/test/cypress/e2e/example47.cy.ts b/demos/react/test/cypress/e2e/example47.cy.ts index 936cb3002e..3e5480a883 100644 --- a/demos/react/test/cypress/e2e/example47.cy.ts +++ b/demos/react/test/cypress/e2e/example47.cy.ts @@ -101,7 +101,7 @@ describe('Example 47 - Row Detail View + Grouping', () => { cy.get('.detail-label label').should('contain', 'Assignee:'); cy.get('.detail-label input').should('exist'); - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('.dynamic-cell-detail').find('[data-test=delete-btn]').click(); cy.get('.toast.text-bg-danger').contains(/Deleted row with Task [0-9]*/); cy.get('.dynamic-cell-detail').should('have.length', 0); @@ -112,7 +112,7 @@ describe('Example 47 - Row Detail View + Grouping', () => { cy.on('window:alert', stub); let assigneeName = ''; - cy.get('.slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('.slick-vertical-scroller').scrollTo('top'); cy.get('[data-row="1"] > .slick-cell.l1').contains(/Task [0-9]*/); cy.get('[data-row="1"] > .slick-cell.l0').click().wait(40); diff --git a/demos/react/test/cypress/e2e/example48.cy.ts b/demos/react/test/cypress/e2e/example48.cy.ts index 5e7f317681..329aac187e 100644 --- a/demos/react/test/cypress/e2e/example48.cy.ts +++ b/demos/react/test/cypress/e2e/example48.cy.ts @@ -153,7 +153,7 @@ describe('Example 48 - Hybrid Selection Model', () => { }); it('should auto scroll take effect to display the selecting element when dragging', { scrollBehavior: false }, () => { - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo('top'); testScroll('#grid48-1', '#grid48-1', 0, 1).then((scrollDistance: { cell: any; row: any }) => { expect(scrollDistance.cell.scrollBefore).to.be.lte(scrollDistance.cell.scrollAfter); @@ -161,11 +161,11 @@ describe('Example 48 - Hybrid Selection Model', () => { }); cy.get('#selectionRange1').contains(/"fromRow":0,"fromCell":1,"toRow":1[45],"toCell":3/); - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo(0, 13 * 35); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo(0, 13 * 35); }); it('should toggle multiple cell selection ranges with the checkbox', () => { - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo('top'); cy.get('[data-test="enable-multi-selection"]').check(); cy.get('#grid48-1 .slick-row[data-row="1"] .slick-cell.l1.r1').click(); @@ -201,7 +201,7 @@ describe('Example 48 - Hybrid Selection Model', () => { }); it('should preserve row and column offsets when copying multiple cell ranges', () => { - cy.get('#grid48-1 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-1 .slick-vertical-scroller').scrollTo('top'); cy.get('[data-test="enable-multi-selection"]').should('be.checked'); cy.window().then((win) => { cy.stub(win.navigator.clipboard, 'writeText').as('clipboardWriteText'); @@ -264,7 +264,7 @@ describe('Example 48 - Hybrid Selection Model', () => { }); cy.get('#selectionRange2').contains(/"fromRow":0,"fromCell":0,"toRow":1[0-9],"toCell":7/); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo(0, 12 * 35); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo(0, 12 * 35); }); it('should click on a cell outside of the selected range and expect previous selection to remain', () => { @@ -272,7 +272,7 @@ describe('Example 48 - Hybrid Selection Model', () => { cy.get('@task1x') .contains(/Task 1[0-9]/) .click(); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-cell.selected').should('have.length.gte', 60); cy.get('#selectionRange2').contains(/"fromRow":0,"fromCell":0,"toRow":1[0-9],"toCell":7/); }); @@ -284,20 +284,20 @@ describe('Example 48 - Hybrid Selection Model', () => { it('should click on row 4 and 5 row checkbox and expect 5 full rows to be selected', () => { cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l1.r1').should('contain', '4'); cy.get('#grid48-2 .slick-row[data-row="4"] input[type=checkbox]').click({ force: true }); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="4"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 1); // select another row cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l1.r1').should('contain', '5'); cy.get('#grid48-2 .slick-row[data-row="5"] input[type=checkbox]').click({ force: true }); - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="5"] .slick-cell.l0.r0').should('have.class', 'selected'); cy.get('#grid48-2 .slick-cell.selected').should('have.length', 8 * 2); }); it('should toggle multiple row selection ranges with the checkbox', () => { - cy.get('#grid48-2 .slick-viewport-top.slick-viewport-left').scrollTo('top'); + cy.get('#grid48-2 .slick-vertical-scroller').scrollTo('top'); cy.get('#grid48-2 .slick-row[data-row="4"] input[type=checkbox]').uncheck({ force: true }); cy.get('#grid48-2 .slick-row[data-row="5"] input[type=checkbox]').uncheck({ force: true }); cy.get('[data-test="enable-multi-selection"]').should('be.checked'); @@ -321,7 +321,7 @@ describe('Example 48 - Hybrid Selection Model', () => { const secondRange = '{"fromRow":3,"fromCell":0,"toRow":4,"toCell":7}'; const combinedRanges = `${firstRange}${secondRange}`; - cy.get(`${gridSelector} .slick-viewport-top.slick-viewport-left`).scrollTo('top'); + cy.get(`${gridSelector} .slick-vertical-scroller`).scrollTo('top'); cy.get(`${gridSelector} .slick-row[data-row="1"] input[type=checkbox]`).uncheck({ force: true }); cy.get(`${gridSelector} .slick-row[data-row="2"] input[type=checkbox]`).uncheck({ force: true }); cy.get(`${gridSelector} .slick-row[data-row="4"] input[type=checkbox]`).uncheck({ force: true }); diff --git a/demos/react/test/cypress/e2e/example55.cy.ts b/demos/react/test/cypress/e2e/example55.cy.ts index 37ecd02a59..3a8477ecf3 100644 --- a/demos/react/test/cypress/e2e/example55.cy.ts +++ b/demos/react/test/cypress/e2e/example55.cy.ts @@ -38,7 +38,7 @@ describe('Example 55 - Variable Row Height (provider)', { retries: 1 }, () => { it('should keep row 90 aligned at top after clicking scroll button', () => { cy.get('[data-test="scroll-row-90-example55"]').click(); - cy.get('.slick-viewport-top.slick-viewport-left') + cy.get('.slick-vertical-scroller') .invoke('scrollTop') .then((scrollTop) => { expect(Number(scrollTop)).to.be.closeTo(topOf(90), 2); diff --git a/demos/react/test/cypress/e2e/example56.cy.ts b/demos/react/test/cypress/e2e/example56.cy.ts index c02518e284..e30d1b7f00 100644 --- a/demos/react/test/cypress/e2e/example56.cy.ts +++ b/demos/react/test/cypress/e2e/example56.cy.ts @@ -1,6 +1,6 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () => { const BASE_ROW_HEIGHT = 40; - const FROZEN_ROW_COUNT = 2; + const PINNED_ROW_COUNT = 2; const hDefault = (r: number) => { const cycle = [33, 44, 44, 80]; @@ -18,36 +18,41 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () return t; }; - const frozenTopHeight = (hOf: (row: number) => number) => topOf(FROZEN_ROW_COUNT, hOf); + const pinnedTopHeight = (hOf: (row: number) => number) => topOf(PINNED_ROW_COUNT, hOf); const relativeTopInCanvas = (r: number, hOf: (row: number) => number) => { - if (r < FROZEN_ROW_COUNT) { - return topOf(r, hOf); - } - return topOf(r, hOf) - frozenTopHeight(hOf); + // Pinned rows are moved into the overlay, but center rows retain their + // natural document coordinates behind that overlay. + return topOf(r, hOf); }; - const canvasSelector = (r: number) => (r < FROZEN_ROW_COUNT ? '.grid-canvas-top' : '.grid-canvas-bottom'); + const rowHostSelector = (r: number) => (r < PINNED_ROW_COUNT ? '.slick-docking-overlay' : '.grid-canvas-top'); const assertRowStyle = (row: number, hOf: (row: number) => number) => { const expectedHeight = hOf(row); const expectedTop = relativeTopInCanvas(row, hOf); - cy.get(`${canvasSelector(row)} .slick-row[data-row=${row}]`) + cy.get(`${rowHostSelector(row)} .slick-row[data-row=${row}]`) .should('have.attr', 'style') .and('contain', `transform: translateY(${expectedTop}px)`) .then((style) => { if (expectedHeight !== BASE_ROW_HEIGHT) { expect(style).to.contain(`height: ${expectedHeight}px`); } else { - expect(style).not.to.contain('height:'); + // Docked rows carry their resolved height inline so editor/content + // styles cannot collapse the pinned row. The base-height case is + // therefore valid with either the stylesheet fallback or an + // explicit `height: 40px` declaration. + expect(style).to.match(/(?:height: 40px;|^(?!.*height:))/); } }); - cy.get(`[data-row="${row}"] > .slick-cell:nth(3)`).should('contain', `${expectedHeight}px`); + // Rows are split into left/center/right docking regions, so cells are + // nested under their region wrapper rather than being direct row children. + cy.get(`[data-row="${row}"] .slick-cell:nth(3)`).should('contain', `${expectedHeight}px`); }; const ensureDefaultDensity = () => { - cy.get('.grid-canvas-top .slick-row[data-row=1]') + cy.get('.slick-docking-overlay .slick-row[data-row=1]') .invoke('attr', 'style') .then((style) => { if ((style ?? '').includes('height: 50px')) { @@ -55,7 +60,7 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () } }); - cy.get('.grid-canvas-top .slick-row[data-row=1]').should('have.attr', 'style').and('contain', 'height: 44px'); + cy.get('.slick-docking-overlay .slick-row[data-row=1]').should('have.attr', 'style').and('contain', 'height: 44px'); }; beforeEach(() => { @@ -67,7 +72,7 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () cy.get('h2').should('contain', 'Example 56: Variable Row Height (item metadata)'); }); - it('should render frozen and scrollable rows with expected transform and row heights from metadata fallback', () => { + it('should render pinned and scrollable rows with expected transform and row heights from metadata fallback', () => { for (const r of [0, 1, 2, 3, 4, 5, 6]) { assertRowStyle(r, hDefault); } @@ -85,12 +90,12 @@ describe('Example 56 - Variable Row Height (item metadata)', { retries: 1 }, () } }); - it('should scroll row 90 to top of scrollable pane with frozen top rows', () => { - const expectedScrollTop = topOf(90, hDefault) - frozenTopHeight(hDefault); + it('should scroll row 90 to top of scrollable pane with pinned top rows', () => { + const expectedScrollTop = topOf(90, hDefault) - pinnedTopHeight(hDefault); cy.get('[data-test="scroll-row-90-example56"]').click(); - cy.get('.slick-viewport-bottom.slick-viewport-left').should(($viewport) => { + cy.get('.slick-vertical-scroller').should(($viewport) => { expect($viewport.scrollTop()).to.be.closeTo(expectedScrollTop, 2); }); diff --git a/demos/react/test/cypress/e2e/example57.cy.ts b/demos/react/test/cypress/e2e/example57.cy.ts index df73fe98f8..7055d540b1 100644 --- a/demos/react/test/cypress/e2e/example57.cy.ts +++ b/demos/react/test/cypress/e2e/example57.cy.ts @@ -54,14 +54,14 @@ describe('Example 57 - RTL (Right-to-Left)', () => { describe('Scrolling Behavior', () => { it('should have horizontal scroll enabled', () => { - cy.get('#grid57 .slick-viewport').then(($viewport) => { + cy.get('#grid57 .slick-horizontal-scroller').then(($viewport) => { const viewport = $viewport[0] as HTMLElement; expect(viewport.scrollWidth).to.be.greaterThan(viewport.clientWidth); }); }); it('should update visible header columns when scrolling', () => { - cy.get('#grid57 .slick-viewport').then(($viewport) => { + cy.get('#grid57 .slick-horizontal-scroller').then(($viewport) => { const viewport = $viewport[0] as HTMLElement; const maxScroll = viewport.scrollWidth - viewport.clientWidth; viewport.scrollLeft = maxScroll; @@ -72,7 +72,7 @@ describe('Example 57 - RTL (Right-to-Left)', () => { cy.wait(150); - cy.get('#grid57 .slick-viewport').then(($viewport) => { + cy.get('#grid57 .slick-horizontal-scroller').then(($viewport) => { const viewport = $viewport[0] as HTMLElement; expect(Math.abs(viewport.scrollLeft)).to.be.greaterThan(0); }); diff --git a/demos/react/test/cypress/support/drag.ts b/demos/react/test/cypress/support/drag.ts index 8228176a5a..b26012a988 100644 --- a/demos/react/test/cypress/support/drag.ts +++ b/demos/react/test/cypress/support/drag.ts @@ -82,15 +82,22 @@ export function getScrollDistanceWhenDragOutsideGrid( return (cy as any).convertPosition(viewport).then((_viewportPosition: { x: number; y: number }) => { const viewportSelector = `${selector} .slick-viewport-${_viewportPosition.x}.slick-viewport-${_viewportPosition.y}`; (cy as any).getNthCell(fromRow, fromCol, viewport, { parentSelector: selector }).dragStart(); - return cy.get(viewportSelector).then(($viewport) => { - const scrollTopBefore = $viewport.scrollTop(); - const scrollLeftBefore = $viewport.scrollLeft(); + return cy.get(selector).then(($grid) => { + const viewport = ($grid.find(viewportSelector)[0] || $grid.find('.slick-vertical-scroller')[0]) as HTMLElement; + const horizontalScroller = $grid.find('.slick-horizontal-scroller')[0] as HTMLElement | undefined; + const horizontalOwner = horizontalScroller || viewport; + const scrollTopBefore = viewport.scrollTop; + const scrollLeftBefore = horizontalOwner.scrollLeft; cy.dragOutside(dragDirection, 300, px, { parentSelector: selector }); - return cy.get(viewportSelector).then(($viewportAfter) => { + return cy.get(selector).then(($gridAfter) => { + const viewportAfter = ($gridAfter.find(viewportSelector)[0] || $gridAfter.find('.slick-vertical-scroller')[0]) as HTMLElement; + const horizontalScrollerAfter = $gridAfter.find('.slick-horizontal-scroller')[0] as HTMLElement | undefined; + const horizontalOwnerAfter = horizontalScrollerAfter || viewportAfter; cy.dragEnd(selector); - const scrollTopAfter = $viewportAfter.scrollTop(); - const scrollLeftAfter = $viewportAfter.scrollLeft(); - cy.get(viewportSelector).scrollTo(0, 0, { ensureScrollable: false }); + const scrollTopAfter = viewportAfter.scrollTop; + const scrollLeftAfter = horizontalOwnerAfter.scrollLeft; + horizontalOwnerAfter.scrollLeft = 0; + viewportAfter.scrollTop = 0; return cy.wrap({ scrollTopBefore, scrollLeftBefore, diff --git a/demos/vanilla/public/i18n/en.json b/demos/vanilla/public/i18n/en.json index 600083fa11..4f039dd3c3 100644 --- a/demos/vanilla/public/i18n/en.json +++ b/demos/vanilla/public/i18n/en.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Clear all Filters", "CLEAR_ALL_GROUPING": "Clear all Grouping", "CLEAR_ALL_SORTING": "Clear all Sorting", - "CLEAR_PINNING": "Unfreeze Columns/Rows", + "CLEAR_PINNING": "Unpin Columns/Rows", "CLONE": "Clone", "COLLAPSE_ALL_GROUPS": "Collapse all Groups", "COLUMNS": "Columns", @@ -29,7 +29,10 @@ "FILTER_SHORTCUTS": "Filter Shortcuts", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items", "FORCE_FIT_COLUMNS": "Force fit columns", - "FREEZE_COLUMNS": "Freeze Columns", + "PIN_COLUMN": "Column Pinning", + "PIN_COLUMNS": "Pin Through Here", + "PIN_LEFT": "Pin Left", + "PIN_RIGHT": "Pin Right", "INVALID_FLOAT": "The number must be valid and have a maximum of {{maxDecimal}} decimals.", "GREATER_THAN": "Greater than", "GREATER_THAN_OR_EQUAL_TO": "Greater than or equal to", @@ -68,7 +71,8 @@ "TOGGLE_DARK_MODE": "Toggle Dark Mode", "TOGGLE_FILTER_ROW": "Toggle Filter Row", "TOGGLE_PRE_HEADER_ROW": "Toggle Pre-Header Row", - "UNFREEZE_COLUMNS": "Unfreeze Columns", + "UNPIN_COLUMN": "Unpin Column", + "UNPIN_COLUMNS": "Unpin All Columns", "X_OF_Y_SELECTED": "# of % selected", "X_OF_Y_MASS_SELECTED": "{{x}} of {{y}} selected", "BILLING": { diff --git a/demos/vanilla/public/i18n/fr.json b/demos/vanilla/public/i18n/fr.json index 084d95bbb8..bc86353024 100644 --- a/demos/vanilla/public/i18n/fr.json +++ b/demos/vanilla/public/i18n/fr.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Supprimer tous les filtres", "CLEAR_ALL_GROUPING": "Supprimer tous les groupes", "CLEAR_ALL_SORTING": "Supprimer tous les tris", - "CLEAR_PINNING": "Dégeler les colonnes/rangées", + "CLEAR_PINNING": "Désépingler les colonnes/rangées", "CLONE": "Cloner", "COLLAPSE_ALL_GROUPS": "Réduire tous les groupes", "COLUMNS": "Colonnes", @@ -29,7 +29,10 @@ "FILTER_SHORTCUTS": "Raccourcis de filtre", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments", "FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes", - "FREEZE_COLUMNS": "Geler les colonnes", + "PIN_COLUMN": "Épinglage de colonnes", + "PIN_COLUMNS": "Épingler jusqu'ici", + "PIN_LEFT": "Épingler à gauche", + "PIN_RIGHT": "Épingler à droite", "GREATER_THAN": "Plus grand que", "GREATER_THAN_OR_EQUAL_TO": "Plus grand ou égal à", "GROUP_BY": "Grouper par", @@ -68,7 +71,8 @@ "TOGGLE_DARK_MODE": "Basculer le mode clair/sombre", "TOGGLE_FILTER_ROW": "Basculer la ligne des filtres", "TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête", - "UNFREEZE_COLUMNS": "Dégeler les colonnes", + "UNPIN_COLUMN": "Désépingler la colonne", + "UNPIN_COLUMNS": "Désépingler toutes les colonnes", "X_OF_Y_SELECTED": "# de % sélectionnés", "X_OF_Y_MASS_SELECTED": "{{x}} de {{y}} sélectionnés", "BILLING": { diff --git a/demos/vanilla/src/examples/example03.ts b/demos/vanilla/src/examples/example03.ts index 9006958aa4..1c1cacc553 100644 --- a/demos/vanilla/src/examples/example03.ts +++ b/demos/vanilla/src/examples/example03.ts @@ -349,6 +349,7 @@ export default class Example03 { }, enableFormattedDataCache: false, // enable it when you have a large dataset (e.g. we'll enable it when loading over 10K) headerMenu: { + hidePinColumnCommand: false, hidePinningColumnsCommand: false, }, gridMenu: { diff --git a/demos/vanilla/src/examples/example04.html b/demos/vanilla/src/examples/example04.html index 6815dbc0da..be81ca6bd4 100644 --- a/demos/vanilla/src/examples/example04.html +++ b/demos/vanilla/src/examples/example04.html @@ -22,12 +22,12 @@
- + - + @@ -36,11 +36,11 @@
- - @@ -48,7 +48,7 @@
Toggle Right Pinning - diff --git a/demos/vanilla/src/examples/example04.ts b/demos/vanilla/src/examples/example04.ts index f61e4ddf9e..57c8ef2416 100644 --- a/demos/vanilla/src/examples/example04.ts +++ b/demos/vanilla/src/examples/example04.ts @@ -453,7 +453,7 @@ export default class Example04 { }, }, gridMenu: { hideClearPinningCommand: false }, - headerMenu: { hidePinningColumnsCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, enableContextMenu: true, contextMenu: { optionShownOverColumnIds: ['percentComplete'], diff --git a/demos/vanilla/src/examples/example08.html b/demos/vanilla/src/examples/example08.html index 8398c2e9a9..a60a58f952 100644 --- a/demos/vanilla/src/examples/example08.html +++ b/demos/vanilla/src/examples/example08.html @@ -67,13 +67,13 @@

  
- -
diff --git a/demos/vanilla/src/examples/example08.ts b/demos/vanilla/src/examples/example08.ts index 508b3efaeb..b28d58810c 100644 --- a/demos/vanilla/src/examples/example08.ts +++ b/demos/vanilla/src/examples/example08.ts @@ -148,7 +148,7 @@ export default class Example08 { rowHeight: 33, showCustomFooter: true, gridMenu: { hideClearPinningCommand: false }, - headerMenu: { hidePinningColumnsCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, // enable the filtering but hide the user filter row since we use our own single filter enableFiltering: true, diff --git a/demos/vanilla/src/examples/example17.html b/demos/vanilla/src/examples/example17.html index 368a5cf048..363a6777da 100644 --- a/demos/vanilla/src/examples/example17.html +++ b/demos/vanilla/src/examples/example17.html @@ -36,11 +36,11 @@
- + @@ -48,7 +48,7 @@
- + diff --git a/demos/vanilla/src/examples/example17.scss b/demos/vanilla/src/examples/example17.scss index 1a05dbe9e6..f97f847712 100644 --- a/demos/vanilla/src/examples/example17.scss +++ b/demos/vanilla/src/examples/example17.scss @@ -31,15 +31,15 @@ .slick-row:not(.slick-group) > .cell-unselectable { background: #f4f4f4 !important; } - .slick-row .slick-cell.frozen:last-child, - .slick-header-column.frozen:last-child, - .slick-headerrow-column.frozen:last-child, - .slick-footerrow-column.frozen:last-child { + .slick-row .slick-cell.pinned:last-child, + .slick-header-column.pinned:last-child, + .slick-headerrow-column.pinned:last-child, + .slick-footerrow-column.pinned:last-child { border-right: 1px solid red; } - .slick-row.frozen:last-child .slick-cell, - .slick-row.frozen:last-child .slick-cell { + .slick-row.pinned:last-child .slick-cell, + .slick-row.pinned:last-child .slick-cell { border-bottom: 1px solid red; } } diff --git a/demos/vanilla/src/examples/example17.ts b/demos/vanilla/src/examples/example17.ts index 53a7ba7eb6..3050f309ec 100644 --- a/demos/vanilla/src/examples/example17.ts +++ b/demos/vanilla/src/examples/example17.ts @@ -156,6 +156,7 @@ export default class Example17 { rowHeight: 35, // enableExcelCopyBuffer: true, headerMenu: { + hidePinColumnCommand: false, hidePinningColumnsCommand: false, }, }; @@ -280,7 +281,7 @@ export default class Example17 { : this.groupByDuration2(); } - toggleFrozen() { + togglePinned() { const currentPinning = this.sgb1.slickGrid?.getOptions().pinning; const isPinned = !!( currentPinning?.columns?.left && diff --git a/demos/vanilla/src/examples/example26.ts b/demos/vanilla/src/examples/example26.ts index fc79e23972..380337a014 100644 --- a/demos/vanilla/src/examples/example26.ts +++ b/demos/vanilla/src/examples/example26.ts @@ -122,6 +122,7 @@ export default class Example26 { enableSelection: true, enableGrouping: true, headerMenu: { + hidePinColumnCommand: false, hidePinningColumnsCommand: false, }, presets: { diff --git a/demos/vanilla/src/examples/example32.ts b/demos/vanilla/src/examples/example32.ts index c6d1aaf187..d220db17a6 100644 --- a/demos/vanilla/src/examples/example32.ts +++ b/demos/vanilla/src/examples/example32.ts @@ -177,7 +177,7 @@ export default class Example32 { editable: false, datasetIdPropertyName: 'employeeID', // Keep the Employee ID visible while horizontally scrolling. The old - // frozen-pane option is intentionally no longer used by the POC. + // pinned-pane option is intentionally no longer used by the POC. pinning: { columns: { left: 0 }, }, diff --git a/demos/vanilla/src/examples/example44.ts b/demos/vanilla/src/examples/example44.ts index 874fc1d86f..a347e3369b 100644 --- a/demos/vanilla/src/examples/example44.ts +++ b/demos/vanilla/src/examples/example44.ts @@ -90,7 +90,7 @@ export default class Example44 { const owners = ['Alex', 'Priya', 'Mia', 'Sam', 'Chris']; const fragments = [ 'Refactor keyboard shortcut handling for better readability.', - 'Adjust frozen rows when view-model updates after grouping.', + 'Adjust pinned rows when view-model updates after grouping.', 'Improve screen-reader labels on grid menu actions.', 'Align batch editor validation with backend constraints.', 'Capture edge-case around hidden columns and row-span.', diff --git a/demos/vanilla/src/examples/example45.ts b/demos/vanilla/src/examples/example45.ts index 55f18cd6f3..0b5eb4395e 100644 --- a/demos/vanilla/src/examples/example45.ts +++ b/demos/vanilla/src/examples/example45.ts @@ -88,7 +88,7 @@ export default class Example45 { }, rowHeight: 40, // Use the single-viewport pinning model for the two top rows. The - // legacy frozen-row option creates a pane that no longer participates in + // legacy pinned-row option creates a pane that no longer participates in // the POC renderer. pinning: { rows: { top: [0, 1] } }, gridHeight: 560, @@ -123,7 +123,7 @@ export default class Example45 { const statuses: Array = ['Todo', 'In Progress', 'Done']; const notesPool = [ 'Short note.', - 'Need to validate keyboard navigation and ensure screen reader output remains stable across frozen panes.', + 'Need to validate keyboard navigation and ensure screen reader output remains stable across pinned panes.', 'Review row height invalidation path when data changes quickly due to live updates from backend polling.', 'Longer QA note: validate scrolling behavior at top and bottom boundaries, compare rendered range against expected rows, and confirm no visual clipping for wrapped cells.', ]; diff --git a/demos/vue/src/assets/locales/en/translation.json b/demos/vue/src/assets/locales/en/translation.json index 600083fa11..4f039dd3c3 100644 --- a/demos/vue/src/assets/locales/en/translation.json +++ b/demos/vue/src/assets/locales/en/translation.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Clear all Filters", "CLEAR_ALL_GROUPING": "Clear all Grouping", "CLEAR_ALL_SORTING": "Clear all Sorting", - "CLEAR_PINNING": "Unfreeze Columns/Rows", + "CLEAR_PINNING": "Unpin Columns/Rows", "CLONE": "Clone", "COLLAPSE_ALL_GROUPS": "Collapse all Groups", "COLUMNS": "Columns", @@ -29,7 +29,10 @@ "FILTER_SHORTCUTS": "Filter Shortcuts", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} of {{totalItems}} items", "FORCE_FIT_COLUMNS": "Force fit columns", - "FREEZE_COLUMNS": "Freeze Columns", + "PIN_COLUMN": "Column Pinning", + "PIN_COLUMNS": "Pin Through Here", + "PIN_LEFT": "Pin Left", + "PIN_RIGHT": "Pin Right", "INVALID_FLOAT": "The number must be valid and have a maximum of {{maxDecimal}} decimals.", "GREATER_THAN": "Greater than", "GREATER_THAN_OR_EQUAL_TO": "Greater than or equal to", @@ -68,7 +71,8 @@ "TOGGLE_DARK_MODE": "Toggle Dark Mode", "TOGGLE_FILTER_ROW": "Toggle Filter Row", "TOGGLE_PRE_HEADER_ROW": "Toggle Pre-Header Row", - "UNFREEZE_COLUMNS": "Unfreeze Columns", + "UNPIN_COLUMN": "Unpin Column", + "UNPIN_COLUMNS": "Unpin All Columns", "X_OF_Y_SELECTED": "# of % selected", "X_OF_Y_MASS_SELECTED": "{{x}} of {{y}} selected", "BILLING": { diff --git a/demos/vue/src/assets/locales/fr/translation.json b/demos/vue/src/assets/locales/fr/translation.json index 084d95bbb8..bc86353024 100644 --- a/demos/vue/src/assets/locales/fr/translation.json +++ b/demos/vue/src/assets/locales/fr/translation.json @@ -7,7 +7,7 @@ "CLEAR_ALL_FILTERS": "Supprimer tous les filtres", "CLEAR_ALL_GROUPING": "Supprimer tous les groupes", "CLEAR_ALL_SORTING": "Supprimer tous les tris", - "CLEAR_PINNING": "Dégeler les colonnes/rangées", + "CLEAR_PINNING": "Désépingler les colonnes/rangées", "CLONE": "Cloner", "COLLAPSE_ALL_GROUPS": "Réduire tous les groupes", "COLUMNS": "Colonnes", @@ -29,7 +29,10 @@ "FILTER_SHORTCUTS": "Raccourcis de filtre", "FROM_TO_OF_TOTAL_ITEMS": "{{from}}-{{to}} de {{totalItems}} éléments", "FORCE_FIT_COLUMNS": "Ajustement forcé des colonnes", - "FREEZE_COLUMNS": "Geler les colonnes", + "PIN_COLUMN": "Épinglage de colonnes", + "PIN_COLUMNS": "Épingler jusqu'ici", + "PIN_LEFT": "Épingler à gauche", + "PIN_RIGHT": "Épingler à droite", "GREATER_THAN": "Plus grand que", "GREATER_THAN_OR_EQUAL_TO": "Plus grand ou égal à", "GROUP_BY": "Grouper par", @@ -68,7 +71,8 @@ "TOGGLE_DARK_MODE": "Basculer le mode clair/sombre", "TOGGLE_FILTER_ROW": "Basculer la ligne des filtres", "TOGGLE_PRE_HEADER_ROW": "Basculer la ligne de pré-en-tête", - "UNFREEZE_COLUMNS": "Dégeler les colonnes", + "UNPIN_COLUMN": "Désépingler la colonne", + "UNPIN_COLUMNS": "Désépingler toutes les colonnes", "X_OF_Y_SELECTED": "# de % sélectionnés", "X_OF_Y_MASS_SELECTED": "{{x}} de {{y}} sélectionnés", "BILLING": { diff --git a/demos/vue/src/components/Example07.vue b/demos/vue/src/components/Example07.vue index ee341527ea..d088ed2fa1 100644 --- a/demos/vue/src/components/Example07.vue +++ b/demos/vue/src/components/Example07.vue @@ -59,8 +59,6 @@ function defineGrids() { ...gridOptions1.value, enableHeaderMenu: true, enableFiltering: true, - // frozenColumn: 2, - // frozenRow: 2, headerButton: { onCommand: (_e: any, args) => handleOnCommand(_e, args, 2), }, diff --git a/demos/vue/src/components/Example14.vue b/demos/vue/src/components/Example14.vue index 452cf6290d..1f8c96e946 100644 --- a/demos/vue/src/components/Example14.vue +++ b/demos/vue/src/components/Example14.vue @@ -45,6 +45,7 @@ function definedGrid1() { preHeaderPanelHeight: 28, gridHeight: 275, gridWidth: 800, + rowHeight: 33, enablePdfExport: true, enableExcelExport: true, excelExportOptions: { @@ -93,15 +94,16 @@ function definedGrid2() { explicitInitialization: true, gridHeight: 275, gridWidth: 800, - frozenColumn: 2, + rowHeight: 33, + pinning: { columns: { left: 2 } }, enablePdfExport: true, enableExcelExport: true, excelExportOptions: { exportWithFormatter: false, }, externalResources: [new ExcelExportService(), new PdfExportService()], - gridMenu: { hideClearFrozenColumnsCommand: false }, - headerMenu: { hideFreezeColumnsCommand: false }, + gridMenu: { hideClearPinningCommand: false }, + headerMenu: { hidePinColumnCommand: false, hidePinningColumnsCommand: false }, }; } @@ -123,8 +125,14 @@ function getData(count: number) { return mockDataset; } -function setFrozenColumns2(frozenCols: number) { - vueGrid2.slickGrid.setOptions({ frozenColumn: frozenCols }); +function setPinnedColumns2(pinnedCols: number) { + vueGrid2.slickGrid.setOptions({ + pinning: { + columns: { + left: pinnedCols >= 0 ? pinnedCols : [], + }, + }, + }); gridOptions2.value = vueGrid2.slickGrid.getOptions(); } @@ -227,14 +235,14 @@ function vueGrid2Ready(grid: SlickgridVueInstance) {
-

Grid 2 (with Header Grouping & Frozen/Pinned Columns)

+

Grid 2 (with Header Grouping & Pinned Columns)

- -
@@ -249,11 +257,11 @@ function vueGrid2Ready(grid: SlickgridVueInstance) { diff --git a/demos/vue/src/components/Example20.vue b/demos/vue/src/components/Example20.vue index 6e116d6edc..e2435620df 100644 --- a/demos/vue/src/components/Example20.vue +++ b/demos/vue/src/components/Example20.vue @@ -487,9 +487,3 @@ function vueGridReady(grid: SlickgridVueInstance) { @onVueGridCreated="vueGridReady($event.detail)" /> - - diff --git a/docs/migrations/migration-to-11.x.md b/docs/migrations/migration-to-11.x.md index 3611e6f7bb..82e4671939 100644 --- a/docs/migrations/migration-to-11.x.md +++ b/docs/migrations/migration-to-11.x.md @@ -259,6 +259,7 @@ interfaces or runtime. | `Locale.TEXT_UNFREEZE_COLUMNS` | `Locale.TEXT_UNPIN_COLUMNS` | Bulk unpinning translation key renamed | | `$slick-frozen-border-bottom` | `$slick-pinned-border-bottom` | Pinned-row separator variable renamed | | `$slick-frozen-border-right` | `$slick-pinned-border-color` plus `$slick-pinned-border-box-shadow-left/right` | Pinned-column separator now uses non-layout shadows | +| `$slick-pane-top-border-top` | `$slick-content-border-top` | Content-root border variable renamed | For example, update the renamed menu and validation options together: @@ -362,7 +363,9 @@ them to the corresponding `$slick-pinned-*` variables when customizing pinned-re ### Single-viewport DOM and CSS -The old pane roots and their independent scroll containers are removed. Do not target selectors +The old pane roots and their independent scroll containers are removed. The single live structure +uses `.slick-header-root` and `.slick-content-root` as its two outer roots; these are layout roots, +not left/right docking panes. Do not target selectors such as `.slick-pane-left`, `.slick-pane-right`, `.slick-viewport-top`, `.slick-viewport-bottom`, `.grid-canvas-left`, or `.grid-canvas-right` in application CSS or Cypress tests. diff --git a/frameworks/angular-slickgrid/src/demos/examples/example14.component.scss b/frameworks/angular-slickgrid/src/demos/examples/example14.component.scss deleted file mode 100644 index 9a7adce829..0000000000 --- a/frameworks/angular-slickgrid/src/demos/examples/example14.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.slick-pane-bottom { - border-top: 1px solid #969696 !important; -} diff --git a/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts b/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts index 0fb5b38dc0..4250618474 100644 --- a/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts +++ b/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts @@ -5,7 +5,6 @@ import { AngularSlickgridComponent, type AngularGridInstance, type Column, type @Component({ templateUrl: './example14.component.html', - styleUrls: ['./example14.component.scss'], imports: [AngularSlickgridComponent], }) export class Example14Component implements OnInit { diff --git a/frameworks/angular-slickgrid/src/demos/examples/example20.component.scss b/frameworks/angular-slickgrid/src/demos/examples/example20.component.scss deleted file mode 100644 index 9a7adce829..0000000000 --- a/frameworks/angular-slickgrid/src/demos/examples/example20.component.scss +++ /dev/null @@ -1,3 +0,0 @@ -.slick-pane-bottom { - border-top: 1px solid #969696 !important; -} diff --git a/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts b/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts index c16e275307..6ae39560cd 100644 --- a/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts +++ b/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts @@ -16,7 +16,6 @@ import { showToast } from './utilities'; @Component({ templateUrl: './example20.component.html', - styleUrls: ['./example20.component.scss'], encapsulation: ViewEncapsulation.None, imports: [AngularSlickgridComponent, FormsModule], }) diff --git a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts index bf40a60de4..4241632972 100644 --- a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts @@ -47,10 +47,10 @@ describe('SlickGrid unified pinning', () => { expect(slickGrid.getPinnedColumns('right').map((column) => column.id)).toEqual(['d']); expect(container.querySelector('.slick-docking-horizontal-scroller')).toBeTruthy(); expect(container.querySelector('.slick-horizontal-scroller')).toBe(container.querySelector('.slick-docking-horizontal-scroller')); - expect(container.querySelector('.slick-vertical-scroller')).toBe((slickGrid as any)._viewportTopL); + expect(container.querySelector('.slick-vertical-scroller')).toBe((slickGrid as any)._viewportNode); expect(container.querySelector('.slick-docking-overlay')).toBeTruthy(); - expect((slickGrid as any)._paneTopL.style.left).toBe(''); - expect((slickGrid as any)._paneTopL.style.width).toBe('100%'); + expect((slickGrid as any)._contentRoot.style.left).toBe(''); + expect((slickGrid as any)._contentRoot.style.width).toBe('100%'); expect(slickGrid.getOptions().pinning?.rows).toEqual({ top: [0], bottom: [2] }); }); @@ -117,7 +117,7 @@ describe('SlickGrid unified pinning', () => { const internals = slickGrid as any; expect(container.querySelector('.slick-docking-horizontal-scroller')).toBeNull(); - expect(internals._viewportTopL.style.overflowX).toBe('auto'); + expect(internals._viewportNode.style.overflowX).toBe('auto'); slickGrid.setColumnPinning('a', 'left'); @@ -139,7 +139,7 @@ describe('SlickGrid unified pinning', () => { internals._options.invalidColumnPinningWidthCallback = invalidWidth; internals._options.skipPinningValidation = false; internals._invalidPinningAlerted = false; - Object.defineProperty(internals._viewportTopL, 'clientWidth', { configurable: true, value: 100 }); + Object.defineProperty(internals._viewportNode, 'clientWidth', { configurable: true, value: 100 }); vi.spyOn(container, 'getBoundingClientRect').mockReturnValue({ width: 100 } as DOMRect); const invalidPinned = new Map([ [0, 'left'], @@ -327,7 +327,7 @@ describe('SlickGrid unified pinning', () => { slickGrid.scrollTo(10); const dockingScrollerSpy = vi.spyOn(internals, 'hasDockingHorizontalScroller').mockReturnValue(false); - internals._viewport = [internals._viewportTopL]; + internals._viewport = [internals._viewportNode]; internals._options.createFooterRow = true; internals._footerRow = []; internals._options.createPreHeaderPanel = true; @@ -336,14 +336,13 @@ describe('SlickGrid unified pinning', () => { internals._preHeaderPanel = document.createElement('div'); internals._topHeaderPanel = document.createElement('div'); slickGrid.scrollToX(10); - internals._viewport = [internals._viewportTopL, document.createElement('div'), internals._viewportBottomL, internals._viewportBottomR]; + internals._viewport = [internals._viewportNode, document.createElement('div'), document.createElement('div'), document.createElement('div')]; internals._headerScrollContainer = document.createElement('div'); internals._topPanelScrollers = [document.createElement('div')]; internals._footerRowScrollContainer = document.createElement('div'); internals._preHeaderPanelScroller = document.createElement('div'); internals._preHeaderPanelScrollerR = document.createElement('div'); internals._topHeaderPanelScroller = document.createElement('div'); - internals._viewportTopR = document.createElement('div'); internals._headerRowScrollerR = document.createElement('div'); internals._headerRowScrollerL = document.createElement('div'); slickGrid.scrollToX(20); @@ -897,13 +896,13 @@ describe('SlickGrid unified pinning', () => { const leftOnlyGrid = createGrid({ pinning: { rows: { bottom: [2] } }, showHeaderRow: true }); (leftOnlyGrid as any).scrollToX(10); - expect((leftOnlyGrid as any)._viewportTopL).toBeTruthy(); + expect((leftOnlyGrid as any)._viewportNode).toBeTruthy(); }); it('reserves horizontal scrollbar height during a resize with docking overflow', () => { const slickGrid = createGrid({ pinning: { columns: { left: 0 } } }); const internals = slickGrid as any; - Object.defineProperty(internals._viewportTopL, 'clientWidth', { configurable: true, value: 100 }); + Object.defineProperty(internals._viewportNode, 'clientWidth', { configurable: true, value: 100 }); internals.scrollbarDimensions = { width: 15, height: 15 }; const initialViewportHeight = internals.viewportH; diff --git a/packages/common/src/core/__tests__/slickGrid.spec.ts b/packages/common/src/core/__tests__/slickGrid.spec.ts index 0371bb9429..268bbd8dcd 100644 --- a/packages/common/src/core/__tests__/slickGrid.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid.spec.ts @@ -1322,13 +1322,13 @@ describe('SlickGrid core file', () => { expect(grid.getTopHeaderPanel()).toBeTruthy(); expect(grid.getTopHeaderPanel()).toEqual(grid.getTopHeaderPanel()); - const paneHeader = container.querySelector('.slick-pane-header'); + const paneHeader = container.querySelector('.slick-header-root'); vi.spyOn(paneHeader!, 'getBoundingClientRect').mockReturnValue({ left: 25, top: 10, right: 0, bottom: 0, height: paneHeight } as DOMRect); // calling resize should add top offset of pane + topHeader grid.resizeCanvas(); - const paneTopLeftElm = container.querySelector('.slick-pane-top') as HTMLDivElement; + const paneTopLeftElm = container.querySelector('.slick-content-root') as HTMLDivElement; expect(paneTopLeftElm.style.top).toBe(`${paneHeight + topHeaderPanelHeight}px`); }); diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index 0c41d386f2..d2875636fb 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -532,12 +532,8 @@ export class SlickGrid = Column, O e protected counter_rows_rendered = 0; protected counter_rows_removed = 0; - protected _paneHeaderL!: HTMLDivElement; - protected _paneHeaderR!: HTMLDivElement; - protected _paneTopL!: HTMLDivElement; - protected _paneTopR!: HTMLDivElement; - protected _paneBottomL!: HTMLDivElement; - protected _paneBottomR!: HTMLDivElement; + protected _headerRoot!: HTMLDivElement; + protected _contentRoot!: HTMLDivElement; protected _headerScrollerL!: HTMLDivElement; protected _headerScrollerR!: HTMLDivElement; protected _headerL!: HTMLDivElement; @@ -556,14 +552,8 @@ export class SlickGrid = Column, O e protected _topPanelScrollerR!: HTMLDivElement; protected _topPanelL!: HTMLDivElement; protected _topPanelR!: HTMLDivElement; - protected _viewportTopL!: HTMLDivElement; - protected _viewportTopR!: HTMLDivElement; - protected _viewportBottomL!: HTMLDivElement; - protected _viewportBottomR!: HTMLDivElement; - protected _canvasTopL!: HTMLDivElement; - protected _canvasTopR!: HTMLDivElement; - protected _canvasBottomL!: HTMLDivElement; - protected _canvasBottomR!: HTMLDivElement; + protected _viewportNode!: HTMLDivElement; + protected _canvasNode!: HTMLDivElement; protected _dockingOverlay?: HTMLDivElement; protected _dockingHorizontalScroller?: HTMLDivElement; protected _dockingHorizontalSpacer?: HTMLDivElement; @@ -768,17 +758,12 @@ export class SlickGrid = Column, O e } } - // The grid uses one live header/body container. Legacy aliases are kept - // temporarily for internal call sites that are being migrated. - this._paneHeaderL = createDomElement('div', { className: 'slick-pane slick-pane-header' }, this._container); - this._paneHeaderR = this._paneHeaderL; - this._paneTopL = createDomElement('div', { className: 'slick-pane slick-pane-top' }, this._container); - this._paneTopR = this._paneTopL; - this._paneBottomL = this._paneTopL; - this._paneBottomR = this._paneTopL; + // The grid uses one live header and one live content root. + this._headerRoot = createDomElement('div', { className: 'slick-header-root' }, this._container); + this._contentRoot = createDomElement('div', { className: 'slick-content-root' }, this._container); if (this._options.createPreHeaderPanel) { - const headerContainer = createDomElement('div', { className: 'slick-preheader-container' }, this._paneHeaderL); + const headerContainer = createDomElement('div', { className: 'slick-preheader-container' }, this._headerRoot); this._preHeaderPanelScroller = createDomElement( 'div', { className: 'slick-preheader-panel slick-state-default', style: { overflow: 'hidden', position: 'relative' } }, @@ -802,7 +787,7 @@ export class SlickGrid = Column, O e } // Append the header scroller containers - const headerContainerL = createDomElement('div', { className: 'slick-header-container' }, this._paneHeaderL); + const headerContainerL = createDomElement('div', { className: 'slick-header-container' }, this._headerRoot); this._headerScrollerL = createDomElement( 'div', { className: 'slick-header slick-state-default slick-header-left', role: 'rowgroup' }, @@ -827,7 +812,7 @@ export class SlickGrid = Column, O e this._headerRowScrollerL = createDomElement( 'div', { className: 'slick-headerrow slick-state-default', role: 'rowgroup' }, - this._paneTopL + this._contentRoot ); this._headerRowScrollerR = this._headerRowScrollerL; @@ -850,7 +835,7 @@ export class SlickGrid = Column, O e this._headerRows = [this._headerRowL]; // Append the top panel scroller - this._topPanelScrollerL = createDomElement('div', { className: 'slick-top-panel-scroller slick-state-default' }, this._paneTopL); + this._topPanelScrollerL = createDomElement('div', { className: 'slick-top-panel-scroller slick-state-default' }, this._contentRoot); this._topPanelScrollerR = this._topPanelScrollerL; this._topPanelScrollers = [this._topPanelScrollerL]; @@ -879,37 +864,31 @@ export class SlickGrid = Column, O e }); } - // Append the viewport containers - this._viewportTopL = createDomElement('div', { className: 'slick-viewport slick-viewport-top slick-viewport-left' }, this._paneTopL); - this._viewportTopR = this._viewportTopL; - this._viewportBottomL = this._viewportTopL; - this._viewportBottomR = this._viewportTopL; + // Append the viewport + this._viewportNode = createDomElement('div', { className: 'slick-viewport slick-viewport-top slick-viewport-left' }, this._contentRoot); // Cache the viewports - this._viewport = [this._viewportTopL]; + this._viewport = [this._viewportNode]; if (this._options.viewportClass) { this._viewport.forEach((view) => { view.classList.add(...classNameToList(this._options.viewportClass)); }); } - // Default the active viewport to the top left - this._activeViewportNode = this._viewportTopL; + // Default the active viewport + this._activeViewportNode = this._viewportNode; - // Append the canvas containers - this._canvasTopL = createDomElement('div', { className: 'grid-canvas grid-canvas-top grid-canvas-left' }, this._viewportTopL); - this._canvasTopR = this._canvasTopL; - this._canvasBottomL = this._canvasTopL; - this._canvasBottomR = this._canvasTopL; + // Append the canvas + this._canvasNode = createDomElement('div', { className: 'grid-canvas grid-canvas-top grid-canvas-left' }, this._viewportNode); // Cache the canvases - this._canvas = [this._canvasTopL]; + this._canvas = [this._canvasNode]; this.scrollbarDimensions = this.scrollbarDimensions || this.measureScrollbar(); const canvasWithScrollbarWidth = this.getCanvasWidth() + this.scrollbarDimensions.width; - // Default the active canvas to the top left - this._activeCanvasNode = this._canvasTopL; + // Default the active canvas + this._activeCanvasNode = this._canvasNode; // top-header if (this._topHeaderPanelSpacer) { @@ -1184,7 +1163,7 @@ export class SlickGrid = Column, O e this._activeCanvasNode = ((e as Event & { target: HTMLElement })?.target?.closest('.grid-canvas') as HTMLDivElement | null) || this._activeCanvasNode || - this._canvasTopL; + this._canvasNode; return this._activeCanvasNode; } @@ -1217,7 +1196,7 @@ export class SlickGrid = Column, O e this._activeViewportNode = ((e as Event & { target: HTMLDivElement })?.target?.closest('.slick-viewport') as HTMLDivElement | null) || this._activeViewportNode || - this._viewportTopL; + this._viewportNode; return this._activeViewportNode; } @@ -1352,7 +1331,7 @@ export class SlickGrid = Column, O e const { minWidth = 0, maxWidth = 0, width = this._options.defaultColumnWidth! } = column; widths[side] += Math.min(maxWidth || Number.POSITIVE_INFINITY, Math.max(width, minWidth)); }); - const viewportWidth = this._viewportTopL?.clientWidth || this.getViewportInnerWidth() || Utils.width(this._container) || 0; + const viewportWidth = this._viewportNode?.clientWidth || this.getViewportInnerWidth() || Utils.width(this._container) || 0; if (viewportWidth > 0 && widths.left + widths.right >= viewportWidth && !this._options.skipPinningValidation) { if ((forceAlert || !this._invalidPinningAlerted) && this._options.invalidColumnPinningWidthCallback) { this._options.invalidColumnPinningWidthCallback(this._options.invalidColumnPinningWidthMessage!); @@ -1418,19 +1397,16 @@ export class SlickGrid = Column, O e this.canvasWidth !== oldCanvasWidth || this.canvasWidthL !== oldCanvasWidthL || this.canvasWidthR !== oldCanvasWidthR; if (widthChanged) { - Utils.width(this._canvasTopL, this.canvasWidthL); + Utils.width(this._canvasNode, this.canvasWidthL); this.getHeadersWidth(); Utils.width(this._headerL, this.getDockingChromeRootWidth()); - // v11 uses one live pane for both pinned and center columns. The old - // split-pane sizing must not be applied to the aliased right/bottom - // nodes, otherwise pinning can collapse the viewport to the scrollbar - // strip by assigning the pane's left offset and residual width. - this._paneHeaderL.style.left = ''; - this._paneTopL.style.left = ''; - Utils.width(this._paneHeaderL, '100%'); - Utils.width(this._paneTopL, '100%'); + // v11 uses one live content root for both pinned and center columns. + this._headerRoot.style.left = ''; + this._contentRoot.style.left = ''; + Utils.width(this._headerRoot, '100%'); + Utils.width(this._contentRoot, '100%'); Utils.width(this._headerRowScrollerL, '100%'); Utils.width(this._headerRowL, this.canvasWidth); @@ -1443,12 +1419,10 @@ export class SlickGrid = Column, O e const panelWidth = this._options.preHeaderPanelWidth ?? this.canvasWidth; this._preHeaderPanel.style.width = typeof panelWidth === 'string' ? panelWidth : `${panelWidth}px`; } - Utils.width(this._viewportTopL, '100%'); + Utils.width(this._viewportNode, '100%'); if (this.rowDockingLayout.bottom.length > 0) { - this._paneBottomL.style.left = ''; - Utils.width(this._viewportBottomL, '100%'); - Utils.width(this._canvasBottomL, this.canvasWidthL); + this._contentRoot.style.left = ''; } } @@ -1479,7 +1453,7 @@ export class SlickGrid = Column, O e * is wider than its unpinned center columns. */ protected getDockingRenderedWidth(): number { - const viewportWidth = this._viewportTopL?.clientWidth || this._dockingHorizontalScroller?.clientWidth || this.getViewportInnerWidth(); + const viewportWidth = this._viewportNode?.clientWidth || this._dockingHorizontalScroller?.clientWidth || this.getViewportInnerWidth(); return Math.max(this.dockingLayout.contentWidth, viewportWidth || this.viewportW); } @@ -1859,7 +1833,7 @@ export class SlickGrid = Column, O e protected materializeFooterRow(): void { const canvasWithScrollbarWidth = this.getCanvasWidth() + (this.scrollbarDimensions?.width || 0); - this._footerRowScrollerL = createDomElement('div', { className: 'slick-footerrow slick-state-default' }, this._paneTopL); + this._footerRowScrollerL = createDomElement('div', { className: 'slick-footerrow slick-state-default' }, this._contentRoot); this._footerRowScrollerR = this._footerRowScrollerL; this._footerRowScroller = [this._footerRowScrollerL]; @@ -2306,7 +2280,7 @@ export class SlickGrid = Column, O e // the body does. Right pins must stop at the body's visible edge, not the // wider chrome scroller edge, otherwise they drift right by the scrollbar // width (for example 1551.11px instead of 1536px). - const dockingViewportWidth = this._viewportTopL?.clientWidth || chromeScroller.clientWidth; + const dockingViewportWidth = this._viewportNode?.clientWidth || chromeScroller.clientWidth; // The chrome container itself is translated by -scrollLeft. Add it back // before converting the target screen coordinate to the local `left`. const untransformedContainerLeft = chromeContainer.getBoundingClientRect().left + this.scrollLeft; @@ -3072,8 +3046,8 @@ export class SlickGrid = Column, O e this._headerRows = [this._headerRowL]; this._topPanelScrollers = [this._topPanelScrollerL]; this._topPanels = [this._topPanelL]; - this._viewport = [this._viewportTopL]; - this._canvas = [this._canvasTopL]; + this._viewport = [this._viewportNode]; + this._canvas = [this._canvasNode]; // Keep the original viewport as the horizontal scroll owner for ordinary // grids. The dedicated scrollbar is only required once pinning/sticky // docking is configured; creating it for every grid breaks integrations @@ -3084,7 +3058,7 @@ export class SlickGrid = Column, O e this._dockingHorizontalScroller ??= createDomElement( 'div', { className: 'slick-docking-horizontal-scroller', role: 'presentation' }, - this._paneTopL + this._contentRoot ); this._dockingHorizontalSpacer ??= createDomElement( 'div', @@ -3193,7 +3167,7 @@ export class SlickGrid = Column, O e /** Create the row overlay once for a configured row-docking grid. */ protected ensureDockingOverlay(): HTMLDivElement { - this._dockingOverlay ??= createDomElement('div', { className: 'slick-docking-overlay', role: 'presentation' }, this._paneTopL); + this._dockingOverlay ??= createDomElement('div', { className: 'slick-docking-overlay', role: 'presentation' }, this._contentRoot); if (this.initialized) { this.bindDockingOverlayEvents(); } @@ -3220,8 +3194,8 @@ export class SlickGrid = Column, O e } protected setOverflow(): void { - this._viewportTopL.style.overflowX = this.hasDockingHorizontalScroller() ? 'hidden' : 'auto'; - this._viewportTopL.style.overflowY = this._options.autoHeight ? 'hidden' : this._options.alwaysShowVerticalScroll ? 'scroll' : 'auto'; + this._viewportNode.style.overflowX = this.hasDockingHorizontalScroller() ? 'hidden' : 'auto'; + this._viewportNode.style.overflowY = this._options.autoHeight ? 'hidden' : this._options.alwaysShowVerticalScroll ? 'scroll' : 'auto'; if (this._dockingHorizontalScroller) { this._dockingHorizontalScroller.style.overflowX = 'auto'; this._dockingHorizontalScroller.style.overflowY = 'hidden'; @@ -3229,7 +3203,7 @@ export class SlickGrid = Column, O e if (this._options.viewportClass) { const viewportClasses = classNameToList(this._options.viewportClass); - this._viewportTopL.classList.add(...viewportClasses); + this._viewportNode.classList.add(...viewportClasses); } } @@ -3237,12 +3211,12 @@ export class SlickGrid = Column, O e this._headerScrollContainer = this._headerScrollerL; this._headerRowScrollContainer = this._headerRowScrollerL; this._footerRowScrollContainer = this._footerRowScrollerL; - this._viewportScrollContainerY = this._viewportTopL; - this._viewportScrollContainerX = this._dockingHorizontalScroller ?? this._viewportTopL; + this._viewportScrollContainerY = this._viewportNode; + this._viewportScrollContainerX = this._dockingHorizontalScroller ?? this._viewportNode; // Expose the active horizontal scroll element through one stable selector. // The docking-specific class remains available for styling and diagnostics. - this._viewportTopL.classList.toggle('slick-horizontal-scroller', this._viewportScrollContainerX === this._viewportTopL); + this._viewportNode.classList.toggle('slick-horizontal-scroller', this._viewportScrollContainerX === this._viewportNode); this._dockingHorizontalScroller?.classList.toggle( 'slick-horizontal-scroller', this._viewportScrollContainerX === this._dockingHorizontalScroller @@ -4053,7 +4027,7 @@ export class SlickGrid = Column, O e widths[side] += effectiveWidth; }); - const viewportWidth = this._viewportTopL?.clientWidth || this.getViewportInnerWidth() || Utils.width(this._container) || 0; + const viewportWidth = this._viewportNode?.clientWidth || this.getViewportInnerWidth() || Utils.width(this._container) || 0; // Include the reserved scrollbar strip; clientWidth excludes it while the // legacy validation compared against the outer grid width. const scrollbarWidth = this.viewportHasVScroll ? this.scrollbarDimensions?.width || 0 : 0; @@ -4342,7 +4316,7 @@ export class SlickGrid = Column, O e /** Keep permanent/active docked rows outside the native scrolling canvas. */ protected syncDockedRowContainers(): void { - if (!this._dockingOverlay || !this._canvasTopL) { + if (!this._dockingOverlay || !this._canvasNode) { return; } Object.entries(this.rowsCache).forEach(([rowId, cacheEntry]) => { @@ -4352,7 +4326,7 @@ export class SlickGrid = Column, O e return; } const dockingBand = this.dockingByRow.get(row)?.band; - const target = dockingBand && dockingBand !== 'center' ? this._dockingOverlay! : this._canvasTopL; + const target = dockingBand && dockingBand !== 'center' ? this._dockingOverlay! : this._canvasNode; if (rowNode.parentElement !== target) { target.appendChild(rowNode); } @@ -5058,12 +5032,8 @@ export class SlickGrid = Column, O e this.vScrollDir = this.prevScrollTop + oldOffset < newScrollTop + this.offset ? 1 : -1; this.scrollTop = this.prevScrollTop = newScrollTop; - if (this.hasDockedColumns()) { - this._viewportTopL.scrollTop = newScrollTop; - } - - if (this.rowDockingLayout.bottom.length > 0) { - this._viewportBottomL.scrollTop = this._viewportBottomR.scrollTop = newScrollTop; + if (this.hasDockedColumns() || this.rowDockingLayout.bottom.length > 0) { + this._viewportNode.scrollTop = newScrollTop; } if (this._viewportScrollContainerY) { @@ -5093,7 +5063,7 @@ export class SlickGrid = Column, O e if (this.hasDockingHorizontalScroller()) { const translateX = `translate3d(${-x}px, 0, 0)`; - this._canvasTopL.style.transform = translateX; + this._canvasNode.style.transform = translateX; if (this._dockingOverlay) { this._dockingOverlay.style.transform = translateX; } @@ -5988,7 +5958,7 @@ export class SlickGrid = Column, O e } if (this._options.autoHeight) { - let fullHeight = this._paneHeaderL.offsetHeight; + let fullHeight = this._headerRoot.offsetHeight; fullHeight += this._options.showPreHeaderPanel ? this._options.preHeaderPanelHeight! + this.getVBoxDelta(this._preHeaderPanelScroller) : 0; @@ -6057,7 +6027,7 @@ export class SlickGrid = Column, O e // sibling of the body viewport. Unlike a native viewport scrollbar it // does not reduce `clientHeight` on its own, so reserve its measured // height before calculating virtual rows and the body viewport. - const dockingViewportWidth = this._viewportTopL?.clientWidth || this.viewportW; + const dockingViewportWidth = this._viewportNode?.clientWidth || this.viewportW; const dockingContentWidth = this.dockingLayout.contentWidth || this.canvasWidth; const hasDockingHorizontalOverflow = dockingContentWidth > dockingViewportWidth; const dockingHorizontalScrollbarHeight = @@ -6083,10 +6053,10 @@ export class SlickGrid = Column, O e fullHeight += this._options.preHeaderPanelHeight!; } Utils.height(this._container, fullHeight); - this._paneTopL.style.position = 'relative'; + this._contentRoot.style.position = 'relative'; } - let topHeightOffset = Utils.height(this._paneHeaderL); + let topHeightOffset = Utils.height(this._headerRoot); if (topHeightOffset) { topHeightOffset += this._options.showTopHeaderPanel ? this._options.topHeaderPanelHeight! : 0; } else { @@ -6094,11 +6064,11 @@ export class SlickGrid = Column, O e (this._options.showHeaderRow ? this._options.headerRowHeight! : 0) + (this._options.showPreHeaderPanel ? this._options.preHeaderPanelHeight! : 0); } - Utils.setStyleSize(this._paneTopL, 'top', topHeightOffset); - Utils.height(this._paneTopL, this.paneTopH); + Utils.setStyleSize(this._contentRoot, 'top', topHeightOffset); + Utils.height(this._contentRoot, this.paneTopH); if (!this._options.autoHeight) { - Utils.height(this._viewportTopL, this.viewportTopH); + Utils.height(this._viewportNode, this.viewportTopH); } this.updateDockingOverlayDimensions(); this.updateDockingHorizontalScrollerDimensions(); @@ -6112,7 +6082,7 @@ export class SlickGrid = Column, O e this.scrollLeft = this._viewportScrollContainerX?.scrollLeft ?? this.scrollLeft; dockingChanged = this.refreshDockingLayout(this.scrollLeft) || dockingChanged; - Utils.height(this._viewportTopL, this.viewportTopH); + Utils.height(this._viewportNode, this.viewportTopH); if (!this.scrollbarDimensions || !this.scrollbarDimensions.width) { this.scrollbarDimensions = this.measureScrollbar(); @@ -6146,32 +6116,32 @@ export class SlickGrid = Column, O e * * The layer itself receives the horizontal `-scrollLeft` transform used by * the dedicated scrollbar, so a viewport-width clipping box would expose a - * blank strip at the trailing edge. Its pane is the clipping boundary. + * blank strip at the trailing edge. Its viewport is the clipping boundary. */ protected updateDockingOverlayDimensions(): void { - if (!this._dockingOverlay || !this._viewportTopL) { + if (!this._dockingOverlay || !this._viewportNode) { return; } - this._dockingOverlay.style.top = `${this._viewportTopL.offsetTop}px`; - this._dockingOverlay.style.left = `${this._viewportTopL.offsetLeft}px`; - const overlayWidth = Math.max(this.canvasWidth, this.dockingLayout.contentWidth, this._viewportTopL.clientWidth); + this._dockingOverlay.style.top = `${this._viewportNode.offsetTop}px`; + this._dockingOverlay.style.left = `${this._viewportNode.offsetLeft}px`; + const overlayWidth = Math.max(this.canvasWidth, this.dockingLayout.contentWidth, this._viewportNode.clientWidth); this._dockingOverlay.style.width = `${overlayWidth}px`; - this._dockingOverlay.style.height = `${this._viewportTopL.clientHeight}px`; + this._dockingOverlay.style.height = `${this._viewportNode.clientHeight}px`; } /** Keep the single horizontal scrollbar aligned with the vertical body viewport. */ protected updateDockingHorizontalScrollerDimensions(): void { - if (!this._dockingHorizontalScroller || !this._dockingHorizontalSpacer || !this._viewportTopL) { + if (!this._dockingHorizontalScroller || !this._dockingHorizontalSpacer || !this._viewportNode) { return; } const scrollbarHeight = this.scrollbarDimensions?.height || 0; - const viewportWidth = this._viewportTopL.clientWidth; + const viewportWidth = this._viewportNode.clientWidth; const contentWidth = this.dockingLayout.contentWidth || this.canvasWidth; const hasHorizontalOverflow = contentWidth > viewportWidth; this._dockingHorizontalScroller.style.width = `${viewportWidth}px`; this._dockingHorizontalScroller.style.height = hasHorizontalOverflow ? `${scrollbarHeight}px` : '0px'; this._dockingHorizontalSpacer.style.width = `${Math.max(contentWidth, viewportWidth)}px`; - this._container.style.setProperty('--slick-docking-viewport-width', `${this._viewportTopL.clientWidth}px`); + this._container.style.setProperty('--slick-docking-viewport-width', `${this._viewportNode.clientWidth}px`); this._container.style.setProperty('--slick-docking-scroll-left', `${this.scrollLeft}px`); this._container.style.setProperty( '--slick-docking-right-offset', @@ -6248,7 +6218,7 @@ export class SlickGrid = Column, O e const dataLengthIncludingAddNew = this.getDataLengthIncludingAddNew(); let numberOfRows = 0; - let oldH = Utils.height(this._canvasTopL) as number; + let oldH = Utils.height(this._canvasNode) as number; numberOfRows = dataLengthIncludingAddNew + (this._options.leaveSpaceForNewRows ? this.numVisibleRows - 1 : 0); // (re)build the row position index (variable row height mode) before any height computations @@ -6304,7 +6274,7 @@ export class SlickGrid = Column, O e } if (this.h !== oldH) { - Utils.height(this._canvasTopL, this.h); + Utils.height(this._canvasNode, this.h); this.scrollTop = this._viewportScrollContainerY.scrollTop; this.scrollHeight = this._viewportScrollContainerY.scrollHeight; @@ -6741,7 +6711,7 @@ export class SlickGrid = Column, O e const rowNode = x.firstChild as HTMLElement; this.rowsCache[row].rowNode = [rowNode]; const dockingBand = this.dockingByRow.get(row)?.band; - (dockingBand && dockingBand !== 'center' ? this.ensureDockingOverlay() : this._canvasTopL).appendChild(rowNode); + (dockingBand && dockingBand !== 'center' ? this.ensureDockingOverlay() : this._canvasNode).appendChild(rowNode); } } @@ -6884,7 +6854,7 @@ export class SlickGrid = Column, O e const scrollOwner = this._viewportScrollContainerX; const sources = new Set([ - this._viewportTopL, + this._viewportNode, this._headerScrollerL, this._headerRowScrollerL, this._footerRowScrollerL, diff --git a/packages/common/src/extensions/__tests__/slickDraggableGrouping.spec.ts b/packages/common/src/extensions/__tests__/slickDraggableGrouping.spec.ts index 1163a73ea2..d2483dbe01 100644 --- a/packages/common/src/extensions/__tests__/slickDraggableGrouping.spec.ts +++ b/packages/common/src/extensions/__tests__/slickDraggableGrouping.spec.ts @@ -258,9 +258,9 @@ describe('Draggable Grouping Plugin', () => { beforeEach(() => { mockDivPaneContainer1 = document.createElement('div'); const mockDivPaneContainerElm = document.createElement('div'); - mockDivPaneContainerElm.className = 'slick-pane-header'; + mockDivPaneContainerElm.className = 'slick-header-root'; const mockDivPaneContainerElm2 = document.createElement('div'); - mockDivPaneContainerElm2.className = 'slick-pane-header'; + mockDivPaneContainerElm2.className = 'slick-header-root'; mockHeaderLeftDiv1 = document.createElement('div'); mockHeaderCenterDiv = document.createElement('div'); mockHeaderLeftDiv2 = document.createElement('div'); diff --git a/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts b/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts index ce8705c5da..b170cbdf6e 100644 --- a/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts +++ b/packages/common/src/extensions/__tests__/slickGridMenu.spec.ts @@ -91,14 +91,14 @@ const gridStub = { const template = `
-
+
-
+
diff --git a/packages/common/src/services/__tests__/headerGrouping.service.spec.ts b/packages/common/src/services/__tests__/headerGrouping.service.spec.ts index e0b248d84b..1f78d8b913 100644 --- a/packages/common/src/services/__tests__/headerGrouping.service.spec.ts +++ b/packages/common/src/services/__tests__/headerGrouping.service.spec.ts @@ -68,7 +68,7 @@ vi.useFakeTimers(); const template = `
-
+
All your colums div here
diff --git a/packages/common/src/styles/_variables-theme-fluent.scss b/packages/common/src/styles/_variables-theme-fluent.scss index 57554a5cdb..5adb534e68 100644 --- a/packages/common/src/styles/_variables-theme-fluent.scss +++ b/packages/common/src/styles/_variables-theme-fluent.scss @@ -129,7 +129,7 @@ $slick-dark-text-color: #d4d4d4; --slick-container-border-left: 1px solid #505050; --slick-container-border-bottom: 1px solid #505050; --slick-form-control-focus-border-color: #96c6fa; - --slick-pane-top-border-top: 1px solid #505050; + --slick-content-border-top: 1px solid #505050; --slick-filled-filter-color: #96c6fa; --slick-pagination-icon-color: #62abf5; --slick-checkbox-icon-color: #96c6fa; diff --git a/packages/common/src/styles/_variables-theme-material.scss b/packages/common/src/styles/_variables-theme-material.scss index 8b0aa1f731..bed98ac1e4 100644 --- a/packages/common/src/styles/_variables-theme-material.scss +++ b/packages/common/src/styles/_variables-theme-material.scss @@ -41,7 +41,7 @@ $slick-editor-input-group-clear-btn-icon-color: inherit !default; $slick-header-resizable-hover-border-radius: 8px !default, $slick-header-resizable-hover-right: 0 !default, $slick-header-resizable-hover-opacity: 0.4 !default, - $slick-pane-top-border-top: 1px solid #d0d0d0 !default, + $slick-content-border-top: 1px solid #d0d0d0 !default, $slick-container-border-top: 1px solid #ccc !default, $slick-container-border-bottom: 1px solid #ccc !default, $slick-container-border-left: 1px solid #ccc !default, @@ -125,7 +125,7 @@ $slick-dark-text-color: #d4d4d4; --slick-container-border-bottom: 1px solid #505050; --slick-form-control-focus-border-color: #66bb6a; --slick-form-control-focus-box-shadow: #{0 0 0 2px rgba(#66bb6a, 0.25)}; - --slick-pane-top-border-top: 1px solid #505050; + --slick-content-border-top: 1px solid #505050; --slick-filled-filter-color: #66bb6a; --slick-highlight-color: #49a54e; --slick-pagination-icon-color: #49a54e; diff --git a/packages/common/src/styles/_variables-theme-salesforce.scss b/packages/common/src/styles/_variables-theme-salesforce.scss index 58a0de202d..e9008d00b9 100644 --- a/packages/common/src/styles/_variables-theme-salesforce.scss +++ b/packages/common/src/styles/_variables-theme-salesforce.scss @@ -48,7 +48,7 @@ $slick-editor-grid-cell-border-width-modified: 1px 7px 1px 1px $slick-sorting-header-color: #514f4d !default, $slick-header-border-bottom: transparent !default, $slick-header-filter-row-border-bottom: 2px solid #{$slick-primary-color-dark} !default, - $slick-pane-top-border-top: 1px solid #d0d0d0 !default, + $slick-content-border-top: 1px solid #d0d0d0 !default, $slick-header-resizable-hover: 2px solid #{$slick-highlight-color} !default, $slick-header-resizable-hover-border-bottom: 2px solid #{$slick-highlight-color} !default, $slick-header-resizable-hover-border-left: 2px solid #{$slick-highlight-color} !default, diff --git a/packages/common/src/styles/_variables.scss b/packages/common/src/styles/_variables.scss index 9bfea7e506..cc47dbc256 100644 --- a/packages/common/src/styles/_variables.scss +++ b/packages/common/src/styles/_variables.scss @@ -101,8 +101,8 @@ $slick-icon-with-text-valign: middle !default; $slick-drag-selection-handle-color: gray !default; $slick-drag-selection-handle-size: 7px !default; -/** 4x available slick-pane (top, bottom, left, right) */ -$slick-pane-top-border-top: none !default; +/** Border above the grid content root */ +$slick-content-border-top: none !default; /* row */ $slick-row-mouse-hover-color: #eff5fc !default; @@ -1049,7 +1049,7 @@ $slick-dark-text-color: #d4d4d4 !default; --slick-hover-header-color: var(--slick-base-dark-text-color); --slick-header-row-background-color: #2d2d2d; --slick-preheader-border-color: #3e3e3e; - --slick-pane-top-border-top: 1px solid #606060; + --slick-content-border-top: 1px solid #606060; --slick-empty-data-warning-color: var(--slick-base-dark-text-color); --slick-grid-header-background: #2d2d2d; --slick-icon-sort-color: var(--slick-primary-color); diff --git a/packages/common/src/styles/slick-grid.scss b/packages/common/src/styles/slick-grid.scss index 8726e28792..e4bd45fb32 100644 --- a/packages/common/src/styles/slick-grid.scss +++ b/packages/common/src/styles/slick-grid.scss @@ -537,14 +537,15 @@ overflow: hidden; } - .slick-pane { + .slick-header-root, + .slick-content-root { position: absolute; outline: 0; overflow: hidden; width: 100%; } - .slick-pane-header { + .slick-header-root { display: block; background-color: var(--slick-header-background-color, v.$slick-header-background-color); border-bottom: var(--slick-header-border-bottom, v.$slick-header-border-bottom); @@ -569,9 +570,9 @@ } } - .slick-pane-top { + .slick-content-root { box-sizing: border-box; - border-top: var(--slick-pane-top-border-top, v.$slick-pane-top-border-top); + border-top: var(--slick-content-border-top, v.$slick-content-border-top); } .slick-viewport { diff --git a/packages/empty-warning-component/src/slick-empty-warning.component.ts b/packages/empty-warning-component/src/slick-empty-warning.component.ts index e9f81c8cf1..2e0b8777d7 100644 --- a/packages/empty-warning-component/src/slick-empty-warning.component.ts +++ b/packages/empty-warning-component/src/slick-empty-warning.component.ts @@ -71,9 +71,9 @@ export class SlickEmptyWarningComponent implements ExternalResource { // when dealing with a grid that has "autoHeight" option, we need to override 2 height that get miscalculated // that is because it is not aware that we are adding this slick empty element in this grid DOM if (this.gridOptions.autoHeight) { - const leftPaneElm = document.querySelector(`.${gridUid} .slick-pane.slick-pane-top`); - if (leftPaneElm && leftPaneElm.style && gridCanvasElm && gridCanvasElm.style) { - const leftPaneHeight = parseInt(leftPaneElm.style.height, 10) || 0; // this field auto calc by row height + const contentRootElm = document.querySelector(`.${gridUid} .slick-content-root`); + if (contentRootElm && contentRootElm.style && gridCanvasElm && gridCanvasElm.style) { + const contentRootHeight = parseInt(contentRootElm.style.height, 10) || 0; // this field auto calc by row height // get row height of each feature when enabled (rowHeight will always be defined because that is the cell height) const cellRowHeight = this.gridOptions?.rowHeight ?? 0; @@ -84,9 +84,9 @@ export class SlickEmptyWarningComponent implements ExternalResource { // use when height with rows more that 100px // AutoHeight option collapse dataview to 100px when show message without data in huge grid // (default autoHeight for message - 100px you can add as param if needed) - let leftPaneMinHeight = leftPaneHeight !== null && leftPaneHeight < 100 ? leftPaneHeight : 100; - leftPaneMinHeight += filterRowHeight + preHeaderRowHeight; // add preHeader & filter height when enabled - leftPaneElm.style.minHeight = `${leftPaneMinHeight}px`; + let contentRootMinHeight = contentRootHeight !== null && contentRootHeight < 100 ? contentRootHeight : 100; + contentRootMinHeight += filterRowHeight + preHeaderRowHeight; // add preHeader & filter height when enabled + contentRootElm.style.minHeight = `${contentRootMinHeight}px`; gridCanvasElm.style.minHeight = `${cellRowHeight}px`; } } diff --git a/packages/empty-warning-component/src/slick-empty-warning.spec.ts b/packages/empty-warning-component/src/slick-empty-warning.spec.ts index 342aabf881..ffd7bad247 100644 --- a/packages/empty-warning-component/src/slick-empty-warning.spec.ts +++ b/packages/empty-warning-component/src/slick-empty-warning.spec.ts @@ -38,20 +38,20 @@ describe('Slick-Empty-Warning Component', () => { beforeEach(() => { div = document.createElement('div'); - const paneLeft = document.createElement('div'); - paneLeft.className = 'slick-pane slick-pane-top slick-pane-left'; - paneLeft.style.height = '44px'; + const contentRoot = document.createElement('div'); + contentRoot.className = 'slick-content-root'; + contentRoot.style.height = '44px'; const paneRight = document.createElement('div'); - paneRight.className = 'slick-pane slick-pane-top slick-pane-right'; + paneRight.className = 'slick-right-root'; paneRight.style.height = '44px'; const canvasLeft = document.createElement('div'); const canvasRight = document.createElement('div'); canvasLeft.className = 'grid-canvas grid-canvas-left'; canvasRight.className = 'grid-canvas grid-canvas-right'; div.className = `slickgrid-container ${GRID_UID}`; - div.appendChild(paneLeft); + div.appendChild(contentRoot); div.appendChild(paneRight); - paneLeft.appendChild(canvasLeft); + contentRoot.appendChild(canvasLeft); paneRight.appendChild(canvasRight); document.body.appendChild(div); @@ -182,7 +182,7 @@ describe('Slick-Empty-Warning Component', () => { const componentRightElm = document.querySelector( 'div.slickgrid_123456 .grid-canvas.grid-canvas-right .slick-empty-data-warning' ) as HTMLSelectElement; - const gridPaneElm = document.querySelector('.slick-pane.slick-pane-top.slick-pane-left'); + const contentRootElm = document.querySelector('.slick-content-root'); expect(component).toBeTruthy(); expect(component.constructor).toBeDefined(); @@ -193,8 +193,8 @@ describe('Slick-Empty-Warning Component', () => { expect(componentRightElm.style.marginLeft).toBe('0px'); expect(componentLeftElm.textContent).toBe('No data to display.'); expect(componentRightElm.textContent).toBe('No data to display.'); - expect(gridPaneElm!.style.minHeight).toBe('44px'); - expect(gridPaneElm!.style.height).toBe('44px'); + expect(contentRootElm!.style.minHeight).toBe('44px'); + expect(contentRootElm!.style.height).toBe('44px'); }); it('should expect the Slick-Empty-Warning to be created with calculated height including preHeader & filter headerRow when they are both defined in the grid options with "autoHeight" as well', () => { @@ -218,7 +218,7 @@ describe('Slick-Empty-Warning Component', () => { const componentRightElm = document.querySelector( 'div.slickgrid_123456 .grid-canvas.grid-canvas-right .slick-empty-data-warning' ) as HTMLSelectElement; - const gridPaneElm = document.querySelector('.slick-pane.slick-pane-top.slick-pane-left'); + const contentRootElm = document.querySelector('.slick-content-root'); expect(component).toBeTruthy(); expect(component.constructor).toBeDefined(); @@ -229,8 +229,8 @@ describe('Slick-Empty-Warning Component', () => { expect(componentRightElm.style.marginLeft).toBe('0px'); expect(componentLeftElm.textContent).toBe('No data to display.'); expect(componentRightElm.textContent).toBe('No data to display.'); - expect(gridPaneElm!.style.minHeight).toBe('117px'); - expect(gridPaneElm!.style.height).toBe('44px'); + expect(contentRootElm!.style.minHeight).toBe('117px'); + expect(contentRootElm!.style.height).toBe('44px'); }); it('should expect the Slick-Empty-Warning to be created when defining a grid that has the "autoHeight" grid option but hidden when calling it the show warning with True then False', () => { @@ -251,7 +251,7 @@ describe('Slick-Empty-Warning Component', () => { const componentRightElm = document.querySelector( 'div.slickgrid_123456 .grid-canvas.grid-canvas-right .slick-empty-data-warning' ) as HTMLSelectElement; - const gridPaneElm = document.querySelector('.slick-pane.slick-pane-top.slick-pane-left'); + const contentRootElm = document.querySelector('.slick-content-root'); expect(component).toBeTruthy(); expect(component.constructor).toBeDefined(); @@ -262,8 +262,8 @@ describe('Slick-Empty-Warning Component', () => { expect(componentRightElm.style.marginLeft).toBe('0px'); expect(componentLeftElm.textContent).toBe('No data to display.'); expect(componentRightElm.textContent).toBe('No data to display.'); - expect(gridPaneElm!.style.minHeight).toBe('44px'); - expect(gridPaneElm!.style.height).toBe('44px'); + expect(contentRootElm!.style.minHeight).toBe('44px'); + expect(contentRootElm!.style.height).toBe('44px'); }); it('should expect the Slick-Empty-Warning to be created and use different left margin when "rightViewportMarginLeft" is set', () => { From 77e28d6ffcd82444159617d1d6d4882f77a0ca52 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 10 Sep 2026 23:10:23 -0400 Subject: [PATCH 21/48] chore: add top/bottom/left/right pinning in same example --- PINNING-POC-PROGRESS.md | 37 ++-- .../src/examples/slickgrid/example20.html | 53 +++--- .../src/examples/slickgrid/example20.ts | 41 +++-- .../aurelia/test/cypress/e2e/example20.cy.ts | 29 +-- .../src/examples/slickgrid/Example20.tsx | 165 ++++++++---------- demos/react/test/cypress/e2e/example20.cy.ts | 29 +-- demos/vanilla/src/examples/example04.html | 75 ++++++-- demos/vanilla/src/examples/example04.ts | 61 ++++--- demos/vanilla/src/renderer.ts | 10 +- demos/vue/src/components/Example20.vue | 99 ++++++----- demos/vue/test/cypress/e2e/example20.cy.ts | 29 +-- .../demos/examples/example20.component.html | 57 ++++-- .../src/demos/examples/example20.component.ts | 40 +++-- .../test/cypress/e2e/example20.cy.ts | 29 +-- .../__tests__/slickHeaderMenu.spec.ts | 30 ++++ .../common/src/extensions/slickHeaderMenu.ts | 34 ++-- test/cypress/e2e/example04.cy.ts | 27 +-- 17 files changed, 488 insertions(+), 357 deletions(-) diff --git a/PINNING-POC-PROGRESS.md b/PINNING-POC-PROGRESS.md index cdbf090910..e752a3dbf2 100644 --- a/PINNING-POC-PROGRESS.md +++ b/PINNING-POC-PROGRESS.md @@ -167,8 +167,7 @@ references, while the active stylesheet now uses `--slick-pinned-*`. Old command IDs, locale keys, and demo selectors are removed from active examples/runtime and remain only in migration docs where needed. Do not reintroduce legacy runtime branches. -The previous working-tree LOC estimate is stale after the deletion pass. Recalculate production -LOC after the remaining alias/style audit rather than using the earlier planning estimate. +The production LOC estimate below has been recalculated after the alias/style audit. ## Maintainability acceptance gate @@ -625,19 +624,16 @@ are separate pre-existing typing issues and should be handled independently. These are rough **library-only** figures for `packages/common` (including SCSS and public interfaces, excluding Example 04, tests, generated output, and framework-wrapper changes). -They are calculated from the current diff, counting the two new files as additions: +They are calculated from the current diff: -- current working-tree library diff: approximately `+2,886 / -387`, or **+2,499 net LOC** - relative to `HEAD` (packages, excluding tests/demos); -- this includes the new `DockingController` and docking types, single-viewport/per-row - routing, sticky/pinning hardening, and the pinning/docking stylesheet changes; -- this remains a provisional figure because the working tree includes the broader docking - implementation and intentional migration-facing historical style names separately from the - removed runtime pane implementation. +- current production-ish library diff: approximately `+3,430 / -1,509`, or **+1,921 net LOC** + relative to base commit `e757539c2` (packages, excluding tests/demos); +- this includes the new `DockingController` and docking types, single-viewport/per-row routing, + sticky/pinning hardening, and the pinning/docking stylesheet changes; +- this excludes test files and changelogs; historical migration references are documentation-only. -The earlier 800–1,200-line removal estimate is retained only as a planning range. It is not a -final forecast: the exact permanent-pinning LOC must be measured after the full working-tree -diff is finalized. +The earlier 800–1,200-line removal estimate is retained only as a planning range and is not a +forecast of the current implementation. Hardening basic sticky columns/rows would add roughly **+150–300 LOC**, giving an estimated **+120 to +670 net LOC** after cleanup. Supporting grouped quarterly sticky headers would add @@ -774,15 +770,13 @@ requirements are the two largest sources of variance. header-row root also receives `headerRowHeight`; without that root height, `display: contents` band wrappers collapsed the visible filter bar to the 1px spacer height. -- Example 17 Cypress migration is in progress. Its demo now uses canonical +- Example 17 Cypress migration is complete for the current scope. Its demo uses canonical `pinning` instead of inert legacy options, and the shared drag helper reads the docking horizontal scrollbar for single-viewport grids. Active canvas/viewport fallback now also supports drag selection from pinned-row overlays. A related `scrollRowIntoView()` fix accounts for top/bottom docked-row height when - determining the usable center viewport. Firefox currently reports 12 passing, - 1 pending (the intentionally skipped grouping auto-scroll case), and one - remaining bottom-edge assertion that still needs follow-up; do not spend more - migration time here until the other spec failures are triaged. + determining the usable center viewport. Bottom-edge drag coverage is complete; the + only pending case is the intentionally skipped flaky grouping auto-scroll test. 1. **Legacy runtime removal is complete.** The old options, interfaces, state/service fields, validation names, pane behavior, and redundant viewport/canvas aliases have been @@ -842,7 +836,8 @@ deferred until the vanilla guide and API cleanup are settled; do not add framewo neutral single-node fields. Continue to: - document the current `--slick-pinned-*` theme variables and their v11-and-lower names; - update remaining demo labels/selectors only where it does not conflict with migration coverage. -8. Recalculate production LOC after the structural audit. +8. [x] Recalculate production LOC after the structural audit: `+3,430 / -1,509` + (**+1,921 net LOC**) from `e757539c2`, excluding tests and changelogs. 9. Keep unit, coverage, Cypress, framework, and documentation work aligned with the cleaned API; do not reintroduce the removed runtime options or pane renderer. @@ -861,8 +856,8 @@ deferred until the vanilla guide and API cleanup are settled; do not add framewo - During the structural audit, distinguish intentional migration references (docs, command IDs, locale text, demo selectors, and theme variables) from runtime configuration. Verify pane aliases, synchronized scroll branches, header-width slack, GridState/GridService, resizer, header menus, - extensions, and all four framework wrappers, then recalculate LOC from `git diff HEAD` and update - this file again. + extensions, and all four framework wrappers; the current production LOC estimate is recorded + above. ## Suggested resume prompt diff --git a/demos/aurelia/src/examples/slickgrid/example20.html b/demos/aurelia/src/examples/slickgrid/example20.html index b46b22f78d..c7d7fdc63a 100644 --- a/demos/aurelia/src/examples/slickgrid/example20.html +++ b/demos/aurelia/src/examples/slickgrid/example20.html @@ -14,25 +14,39 @@

- -
-
-
- -
-
-
- + + +
-
@@ -54,13 +68,6 @@

- diff --git a/demos/aurelia/src/examples/slickgrid/example20.ts b/demos/aurelia/src/examples/slickgrid/example20.ts index 9cb311b0c0..5d18b91925 100644 --- a/demos/aurelia/src/examples/slickgrid/example20.ts +++ b/demos/aurelia/src/examples/slickgrid/example20.ts @@ -20,9 +20,9 @@ export class Example20 { dataset: any[] = []; pinnedColumnCount = 2; pinnedRightColumnCount = 1; - pinnedRowCount = 3; + pinnedTopRowCount = 3; + pinnedBottomRowCount = 2; hideSubTitle = false; - isPinnedBottom = false; isSelectAllShownAsColumnTitle = false; checkboxSelectorInstance: any; constructor() { @@ -166,7 +166,10 @@ export class Example20 { name: 'Sel', onExtensionRegistered: (i: any) => (this.checkboxSelectorInstance = i), }, - pinning: { columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, rows: { top: [0, 1, 2] } }, + pinning: { + columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, + rows: { top: [0, 1, 2], bottom: [498, 499] }, + }, enableCellMenu: true, cellMenu: { onCommand: (_e: any, a: any) => this.executeCommand(a), @@ -267,27 +270,35 @@ export class Example20 { this.pinnedColumnCount = left; this.pinnedRightColumnCount = n; } - changePinnedColumnCount() { - this.setPinnedColumns(this.pinnedColumnCount); + changePinnedColumnCount(value: number | string, side: 'left' | 'right') { + const count = Number(value); + this.setPinnedColumns(side === 'left' ? count : this.pinnedColumnCount, side === 'right' ? count : this.pinnedRightColumnCount); } - changePinnedRowCount() { - const rows = this.getPinnedRowIndexes(); - this.gridObj?.setOptions({ pinning: { rows: this.isPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } } }); + changePinnedRowCount(value: number | string, side: 'top' | 'bottom') { + if (side === 'top') { + this.pinnedTopRowCount = Number(value); + } else { + this.pinnedBottomRowCount = Number(value); + } + this.gridObj?.setOptions({ + pinning: { + rows: { + top: this.getPinnedRowIndexes(this.pinnedTopRowCount), + bottom: this.getPinnedRowIndexes(this.pinnedBottomRowCount, true), + }, + }, + }); } - private getPinnedRowIndexes() { - const rowCount = Math.max(0, Number(this.pinnedRowCount) || 0); + private getPinnedRowIndexes(rowCount: number, isBottom = false) { + rowCount = Math.max(0, Number(rowCount) || 0); const dataLength = this.gridObj?.getDataLength?.() ?? this.dataset.length; - const firstPinnedRow = this.isPinnedBottom ? Math.max(0, dataLength - rowCount) : 0; + const firstPinnedRow = isBottom ? Math.max(0, dataLength - rowCount) : 0; return Array.from({ length: rowCount }, (_v, index) => firstPinnedRow + index); } toggleRightPinning() { this.setPinnedColumns(this.pinnedColumnCount, this.pinnedRightColumnCount > 0 ? 0 : 1); } - togglePinnedBottomRows() { - this.isPinnedBottom = !this.isPinnedBottom; - this.changePinnedRowCount(); - } toggleSelectAllRow() { this.isSelectAllShownAsColumnTitle = !this.isSelectAllShownAsColumnTitle; this.checkboxSelectorInstance?.setOptions({ diff --git a/demos/aurelia/test/cypress/e2e/example20.cy.ts b/demos/aurelia/test/cypress/e2e/example20.cy.ts index 4b95bbe256..f60ce53543 100644 --- a/demos/aurelia/test/cypress/e2e/example20.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example20.cy.ts @@ -10,8 +10,7 @@ describe('Example 20 - Pinned Grid', () => { const getCell = (rowIndex: number, columnIndex: number) => cy.get(`#grid20 .slick-row[data-row="${rowIndex}"] .slick-cell.l${columnIndex}`); const setRightPinning = (count: number) => { - cy.get('.pinned-right-column-count').clear().type(`${count}`); - cy.get('[data-test="set-pinned-right-column"]').click(); + cy.get('.pinned-right-column-count').select(`${count}`); }; it('should display Example title', () => { @@ -43,12 +42,13 @@ describe('Example 20 - Pinned Grid', () => { cy.get('body').trigger('mousedown'); }); - it('should have three top-pinned rows and left/center/right cells on page load', () => { + it('should have top/bottom-pinned rows and left/center/right cells on page load', () => { const row0 = '#grid20 .slick-row[data-row="0"]'; // Pinning uses one row node split into regions and a single docking overlay; // it no longer duplicates rows into legacy left/right pinned canvases. cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 2); cy.get(`${row0} .slick-pinned-left-cells > .slick-cell`).should('have.length', 3); cy.get(`${row0} .slick-scrolling-cells > .slick-cell`).should('have.length', 5); cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 1); @@ -61,14 +61,20 @@ describe('Example 20 - Pinned Grid', () => { cy.get(`${row0} .slick-pinned-right-cells > .slick-cell:nth(0) .cell-menu-dropdown`).should('contain', 'Action'); }); - it('should pin the last dataset rows when toggling pinned rows to the bottom', () => { - cy.get('[data-test="toggle-pinned-bottom"]').click(); + it('should clear top-pinned rows when selecting zero rows', () => { + cy.get('.pinned-top-row-count').select('0'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('not.exist'); - cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 3); - cy.get('#grid20 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="497"] .slick-cell.l1').should( - 'contain', - 'Task 497' - ); + cy.get('.pinned-top-row-count').select('3'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + }); + + it('should independently clear and restore bottom-pinned rows', () => { + cy.get('.pinned-bottom-row-count').select('0'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('not.exist'); + + cy.get('.pinned-bottom-row-count').select('2'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 2); cy.get('#grid20 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="498"] .slick-cell.l1').should( 'contain', 'Task 498' @@ -77,9 +83,6 @@ describe('Example 20 - Pinned Grid', () => { 'contain', 'Task 499' ); - cy.get('#grid20 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="0"]').should('not.exist'); - - cy.get('[data-test="toggle-pinned-bottom"]').click(); cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); }); diff --git a/demos/react/src/examples/slickgrid/Example20.tsx b/demos/react/src/examples/slickgrid/Example20.tsx index 47db7763e3..8286312fb8 100644 --- a/demos/react/src/examples/slickgrid/Example20.tsx +++ b/demos/react/src/examples/slickgrid/Example20.tsx @@ -18,15 +18,13 @@ const Example20: React.FC = () => { const [dataset] = useState(getData()); const [gridOptions, setGridOptions] = useState(undefined); const [pinnedColumnCount, setPinnedColumnCount] = useState(2); - const [pinnedRowCount, setPinnedRowCount] = useState(3); + const [pinnedTopRowCount, setPinnedTopRowCount] = useState(3); + const [pinnedBottomRowCount, setPinnedBottomRowCount] = useState(2); const [pinnedRightColumnCount, setPinnedRightColumnCount] = useState(1); - const [isPinnedBottom, setIsPinnedBottom] = useState(false); const [hideSubTitle, setHideSubTitle] = useState(false); const [isSelectAllShownAsColumnTitle, setIsSelectAllShownAsColumnTitle] = useState(false); const checkboxSelectorRef = useRef(null); const pinnedColumnCountRef = useRef(2); - const pinnedRowCountInputRef = useRef(null); - const pinnedRightColumnInputRef = useRef(null); const pinnedRightColumnCountRef = useRef(1); const reactGridRef = useRef(null); @@ -233,8 +231,10 @@ const Example20: React.FC = () => { name: 'Sel', onExtensionRegistered: (instance: any) => (checkboxSelectorRef.current = instance), }, - pinning: { columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, rows: { top: [0, 1, 2] } }, - // pinnedBottom: true, // if you want to pin the bottom instead of the top, you can enable this property + pinning: { + columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, + rows: { top: [0, 1, 2], bottom: [498, 499] }, + }, // show both single-column and bulk pinning commands in HeaderMenu & GridMenu; these are opt-in commands gridMenu: { @@ -282,29 +282,26 @@ const Example20: React.FC = () => { return mockDataset; } - /** change dynamically, through slickgrid "setOptions()" the number of pinned columns */ - function changePinnedColumnCount(e: React.FormEvent) { - const value = +(e.currentTarget.value || 0); - setPinnedColumnCount(value); - } - - function updatePinnedColumnCount() { - setPinnedColumns(pinnedColumnCount, pinnedRightColumnCount); - } - /** change dynamically, through slickgrid "setOptions()" the number of pinned rows */ - function updatePinnedRowCount() { - const inputValue = pinnedRowCountInputRef.current?.value; - const nextPinnedRowCount = Math.max(0, Number(inputValue ?? pinnedRowCount) || 0); + function updatePinnedRowCount(value: number, side: 'top' | 'bottom') { + const nextPinnedRowCount = Math.max(0, Number(value) || 0); const slickGrid = reactGridRef.current?.slickGrid; const dataLength = slickGrid?.getDataLength?.() ?? dataset.length; - const firstPinnedRow = isPinnedBottom ? Math.max(0, dataLength - nextPinnedRowCount) : 0; - const rows = Array.from({ length: nextPinnedRowCount }, (_v, index) => firstPinnedRow + index); + const topCount = side === 'top' ? nextPinnedRowCount : pinnedTopRowCount; + const bottomCount = side === 'bottom' ? nextPinnedRowCount : pinnedBottomRowCount; + const getRows = (count: number, isBottom = false) => { + const firstPinnedRow = isBottom ? Math.max(0, dataLength - count) : 0; + return Array.from({ length: count }, (_v, index) => firstPinnedRow + index); + }; slickGrid?.setOptions({ - pinning: { rows: isPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } }, + pinning: { rows: { top: getRows(topCount), bottom: getRows(bottomCount, true) } }, }); - setPinnedRowCount(nextPinnedRowCount); + if (side === 'top') { + setPinnedTopRowCount(nextPinnedRowCount); + } else { + setPinnedBottomRowCount(nextPinnedRowCount); + } } function getContextMenuOptions(): any { @@ -418,9 +415,6 @@ const Example20: React.FC = () => { }); setPinnedColumnCount(left); setPinnedRightColumnCount(nextRight); - if (pinnedRightColumnInputRef.current) { - pinnedRightColumnInputRef.current.value = `${nextRight}`; - } } function reapplyPinnedColumns() { @@ -430,11 +424,6 @@ const Example20: React.FC = () => { reactGridRef.current?.slickGrid.setOptions({ pinning: { columns: { left: left >= 0 ? left : [], right: rightIds } } }); } - function updatePinnedRightColumnCount() { - const nextRight = Math.max(0, Number(pinnedRightColumnInputRef.current?.value ?? pinnedRightColumnCount) || 0); - setPinnedColumns(pinnedColumnCount, nextRight); - } - function toggleRightPinning() { setPinnedColumns(pinnedColumnCount, pinnedRightColumnCount > 0 ? 0 : 1); } @@ -464,20 +453,6 @@ const Example20: React.FC = () => { setPinnedColumns(pinnedColumnCount, pinnedRightColumnCount); } - /** toggle dynamically, through slickgrid "setOptions()" the top/bottom pinned location */ - function togglePinnedBottomRows() { - const newIsPinnedBottom = !isPinnedBottom; - const slickGrid = reactGridRef.current?.slickGrid; - const dataLength = slickGrid?.getDataLength?.() ?? dataset.length; - const rowCount = Math.max(0, pinnedRowCount); - const firstPinnedRow = newIsPinnedBottom ? Math.max(0, dataLength - rowCount) : 0; - const rows = Array.from({ length: rowCount }, (_value, index) => firstPinnedRow + index); - slickGrid?.setOptions({ - pinning: { rows: newIsPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } }, - }); - setIsPinnedBottom(newIsPinnedBottom); - } - function toggleSubTitle() { const newHideSubTitle = !hideSubTitle; setHideSubTitle(newHideSubTitle); @@ -527,45 +502,57 @@ const Example20: React.FC = () => {
-
-
- - - - - - - - changePinnedColumnCount($event)} - /> - - - - - - - +
+
+ + + +
+
+ + +
@@ -592,16 +579,6 @@ const Example20: React.FC = () => { > Toggle Pinned Right - - - : {isPinnedBottom ? 'Bottom' : 'Top'} - + +
+ +
+
+ +
- - - - - - - - + +
+ +
+
+ +
-

-
-
- +
+ + {{ count }} + + {{ count }} + +
+
+ + +
@@ -462,9 +472,6 @@ function vueGridReady(grid: SlickgridVueInstance) { -
- -
-
-
- -
-
-
- + + +
-
@@ -50,9 +72,6 @@

- diff --git a/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts b/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts index 6ae39560cd..7bee8db440 100644 --- a/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts +++ b/frameworks/angular-slickgrid/src/demos/examples/example20.component.ts @@ -27,9 +27,9 @@ export class Example20Component implements OnInit { dataset: any[] = []; pinnedColumnCount = 2; pinnedRightColumnCount = 1; - pinnedRowCount = 3; + pinnedTopRowCount = 3; + pinnedBottomRowCount = 2; hideSubTitle = false; - isPinnedBottom = false; isSelectAllShownAsColumnTitle = false; checkboxSelectorInstance: any; ngOnInit(): void { @@ -157,7 +157,10 @@ export class Example20Component implements OnInit { name: 'Sel', onExtensionRegistered: (i: any) => (this.checkboxSelectorInstance = i), }, - pinning: { columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, rows: { top: [0, 1, 2] } }, + pinning: { + columns: { left: ['_checkbox_selector', 'title', 'percentComplete'], right: ['action'] }, + rows: { top: [0, 1, 2], bottom: [498, 499] }, + }, enableCellMenu: true, cellMenu: { onCommand: (_e: any, a: any) => this.executeCommand(a), @@ -269,27 +272,34 @@ export class Example20Component implements OnInit { this.pinnedColumnCount = left; this.pinnedRightColumnCount = n; } - changePinnedColumnCount() { - this.setPinnedColumns(this.pinnedColumnCount); + changePinnedColumnCount(value: number, side: 'left' | 'right') { + this.setPinnedColumns(side === 'left' ? value : this.pinnedColumnCount, side === 'right' ? value : this.pinnedRightColumnCount); } - changePinnedRowCount() { - const rows = this.getPinnedRowIndexes(); - this.gridObj?.setOptions({ pinning: { rows: this.isPinnedBottom ? { top: [], bottom: rows } : { top: rows, bottom: [] } } }); + changePinnedRowCount(value: number, side: 'top' | 'bottom') { + if (side === 'top') { + this.pinnedTopRowCount = value; + } else { + this.pinnedBottomRowCount = value; + } + this.gridObj?.setOptions({ + pinning: { + rows: { + top: this.getPinnedRowIndexes(this.pinnedTopRowCount), + bottom: this.getPinnedRowIndexes(this.pinnedBottomRowCount, true), + }, + }, + }); } - private getPinnedRowIndexes() { - const rowCount = Math.max(0, Number(this.pinnedRowCount) || 0); + private getPinnedRowIndexes(rowCount: number, isBottom = false) { + rowCount = Math.max(0, Number(rowCount) || 0); const dataLength = this.gridObj?.getDataLength?.() ?? this.dataset.length; - const firstPinnedRow = this.isPinnedBottom ? Math.max(0, dataLength - rowCount) : 0; + const firstPinnedRow = isBottom ? Math.max(0, dataLength - rowCount) : 0; return Array.from({ length: rowCount }, (_v, index) => firstPinnedRow + index); } toggleRightPinning() { this.setPinnedColumns(this.pinnedColumnCount, this.pinnedRightColumnCount > 0 ? 0 : 1); } - togglePinnedBottomRows() { - this.isPinnedBottom = !this.isPinnedBottom; - this.changePinnedRowCount(); - } toggleSelectAllRow() { this.isSelectAllShownAsColumnTitle = !this.isSelectAllShownAsColumnTitle; this.checkboxSelectorInstance?.setOptions({ diff --git a/frameworks/angular-slickgrid/test/cypress/e2e/example20.cy.ts b/frameworks/angular-slickgrid/test/cypress/e2e/example20.cy.ts index 4b95bbe256..f60ce53543 100644 --- a/frameworks/angular-slickgrid/test/cypress/e2e/example20.cy.ts +++ b/frameworks/angular-slickgrid/test/cypress/e2e/example20.cy.ts @@ -10,8 +10,7 @@ describe('Example 20 - Pinned Grid', () => { const getCell = (rowIndex: number, columnIndex: number) => cy.get(`#grid20 .slick-row[data-row="${rowIndex}"] .slick-cell.l${columnIndex}`); const setRightPinning = (count: number) => { - cy.get('.pinned-right-column-count').clear().type(`${count}`); - cy.get('[data-test="set-pinned-right-column"]').click(); + cy.get('.pinned-right-column-count').select(`${count}`); }; it('should display Example title', () => { @@ -43,12 +42,13 @@ describe('Example 20 - Pinned Grid', () => { cy.get('body').trigger('mousedown'); }); - it('should have three top-pinned rows and left/center/right cells on page load', () => { + it('should have top/bottom-pinned rows and left/center/right cells on page load', () => { const row0 = '#grid20 .slick-row[data-row="0"]'; // Pinning uses one row node split into regions and a single docking overlay; // it no longer duplicates rows into legacy left/right pinned canvases. cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 2); cy.get(`${row0} .slick-pinned-left-cells > .slick-cell`).should('have.length', 3); cy.get(`${row0} .slick-scrolling-cells > .slick-cell`).should('have.length', 5); cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 1); @@ -61,14 +61,20 @@ describe('Example 20 - Pinned Grid', () => { cy.get(`${row0} .slick-pinned-right-cells > .slick-cell:nth(0) .cell-menu-dropdown`).should('contain', 'Action'); }); - it('should pin the last dataset rows when toggling pinned rows to the bottom', () => { - cy.get('[data-test="toggle-pinned-bottom"]').click(); + it('should clear top-pinned rows when selecting zero rows', () => { + cy.get('.pinned-top-row-count').select('0'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('not.exist'); - cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 3); - cy.get('#grid20 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="497"] .slick-cell.l1').should( - 'contain', - 'Task 497' - ); + cy.get('.pinned-top-row-count').select('3'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + }); + + it('should independently clear and restore bottom-pinned rows', () => { + cy.get('.pinned-bottom-row-count').select('0'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('not.exist'); + + cy.get('.pinned-bottom-row-count').select('2'); + cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 2); cy.get('#grid20 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="498"] .slick-cell.l1').should( 'contain', 'Task 498' @@ -77,9 +83,6 @@ describe('Example 20 - Pinned Grid', () => { 'contain', 'Task 499' ); - cy.get('#grid20 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="0"]').should('not.exist'); - - cy.get('[data-test="toggle-pinned-bottom"]').click(); cy.get('#grid20 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); }); diff --git a/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts b/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts index 95c58889ec..26f7773402 100644 --- a/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts +++ b/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts @@ -1348,6 +1348,36 @@ describe('HeaderMenu Plugin', () => { vi.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); }); + it('should offer bulk pinning commands around non-pinnable columns', () => { + const testColumns = [ + { ...columnsMock[0], header: undefined }, + { ...columnsMock[1], header: undefined, pinnable: false }, + { ...columnsMock[2], header: undefined }, + ] as Column[]; + const getColumnsSpy = vi.spyOn(gridStub, 'getColumns').mockReturnValue(testColumns); + const setOptionsSpy = vi.spyOn(gridStub, 'setOptions'); + + plugin.init(); + plugin.addonOptions = { hidePinColumnCommand: false }; + gridStub.onBeforeSetColumns.notify({ previousColumns: [], newColumns: testColumns, grid: gridStub }, eventData, gridStub); + gridStub.onHeaderCellRendered.notify({ column: testColumns[0], node: headerDiv, grid: gridStub }, eventData, gridStub); + (headerDiv.querySelector('.slick-header-menu-button') as HTMLDivElement).dispatchEvent( + new Event('click', { bubbles: true, cancelable: true, composed: false }) + ); + + const pinColumnCommand = testColumns[0].header?.menu?.commandItems?.find( + (item) => item !== 'divider' && item.command === 'pin-column' + ) as MenuCommandItem; + const pinRightCommand = pinColumnCommand.commandItems?.find((item) => item !== 'divider' && item.command === 'pin-columns-right') as MenuCommandItem; + expect(pinRightCommand).toBeTruthy(); + + pinRightCommand.action?.(new SlickEventData(), { column: testColumns[0] } as any); + expect(setOptionsSpy).toHaveBeenCalledWith({ pinning: { columns: { right: ['field1', 'field3'] } } }, false, true); + + getColumnsSpy.mockRestore(); + vi.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); + }); + it('should place only one separator between each visible pinning command group', () => { vi.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue({ ...gridOptionsMock, diff --git a/packages/common/src/extensions/slickHeaderMenu.ts b/packages/common/src/extensions/slickHeaderMenu.ts index f69a88e047..0be5447efe 100644 --- a/packages/common/src/extensions/slickHeaderMenu.ts +++ b/packages/common/src/extensions/slickHeaderMenu.ts @@ -330,23 +330,16 @@ export class SlickHeaderMenu extends MenuBaseClass { // consistent regardless of the selected column's current state. let hasPinningOrResizeCommand = false; const bulkPinningCommandItems: MenuCommandItem[] = []; - const columnPosition = columns.findIndex((col) => col.id === columnDef.id); - const hasNonPinnableColumnInRange = (side: 'left' | 'right') => - columnPosition >= 0 && - (side === 'left' ? columns.slice(0, columnPosition + 1) : columns.slice(columnPosition)).some( - (column) => column?.pinnable === false - ); if (headerMenuOptions && !headerMenuOptions.hidePinningColumnsCommand) { - for (const [side, command, titleKey, title] of [ + for (const [command, titleKey, title] of [ [ - 'left', 'pin-columns-left', 'PIN_COLUMNS_LEFT', commandLabels?.pinningColumnsLeftCommand || commandLabels?.pinningColumnsCommand || '', ], - ['right', 'pin-columns-right', 'PIN_COLUMNS_RIGHT', commandLabels?.pinningColumnsRightCommand || ''], + ['pin-columns-right', 'PIN_COLUMNS_RIGHT', commandLabels?.pinningColumnsRightCommand || ''], ] as const) { - if (!hasNonPinnableColumnInRange(side)) { + if (columnDef.pinnable !== false) { bulkPinningCommandItems.push({ _orgTitle: title, iconCssClass: headerMenuOptions.iconPinningColumns || 'mdi mdi-pin-outline', @@ -713,13 +706,12 @@ export class SlickHeaderMenu extends MenuBaseClass { /** Apply or clear the bulk index-based pinning option. */ protected pinOrUnpinColumns(column: Column, command: 'pin-columns-left' | 'pin-columns-right' | 'unpin-columns'): void { - const columnPosition = this.grid.getColumns().findIndex((col) => col.id === column.id); const pinning = command === 'unpin-columns' ? { left: [], right: [] } : command === 'pin-columns-right' - ? { right: columnPosition < 0 ? -1 : this.grid.getColumns().length - columnPosition } - : { left: columnPosition }; + ? { right: this.getPinnableColumnReferences(column, 'right') } + : { left: this.getPinnableColumnReferences(column, 'left') }; this.grid.setOptions( { pinning: { columns: pinning }, @@ -735,6 +727,22 @@ export class SlickHeaderMenu extends MenuBaseClass { this.recreateHeaderMenu(this.grid.getColumns()); } + /** Return the numeric range when all columns are pinnable, otherwise use pinnable column ids. */ + protected getPinnableColumnReferences(column: Column, side: 'left' | 'right'): number | (string | number)[] { + const columns = this.grid.getColumns(); + const columnPosition = columns.findIndex((col) => col.id === column.id); + if (columnPosition < 0) { + return -1; + } + + const columnRange = side === 'left' ? columns.slice(0, columnPosition + 1) : columns.slice(columnPosition); + return columnRange.some((col) => col.pinnable === false) + ? columnRange.filter((col) => col.pinnable !== false).map((col) => col.id) + : side === 'left' + ? columnPosition + : columns.length - columnPosition; + } + protected createParentMenu(e: DOMMouseOrTouchEvent, columnDef: Column, menu: HeaderMenuItems): void { // let the user modify the menu or cancel altogether, // or provide alternative menu implementation. diff --git a/test/cypress/e2e/example04.cy.ts b/test/cypress/e2e/example04.cy.ts index 343a0a2c48..314ba5b03d 100644 --- a/test/cypress/e2e/example04.cy.ts +++ b/test/cypress/e2e/example04.cy.ts @@ -4,8 +4,7 @@ describe('Example 04 - Pinned Grid', () => { const getCell = (rowIndex: number, columnIndex: number) => cy.get(`.grid4 .slick-row[data-row="${rowIndex}"] .slick-cell.l${columnIndex}`); const setRightPinning = (count: number) => { - cy.get('.pinned-right-column-count').clear().type(`${count}`); - cy.get('.pinned-right-column-count').next('button').click(); + cy.get('.pinned-right-column-count').select(`${count}`); }; it('should display Example title', () => { @@ -33,12 +32,13 @@ describe('Example 04 - Pinned Grid', () => { cy.get('body').trigger('mousedown'); }); - it('should have three top-pinned rows and left/center/right cells on page load', () => { + it('should have top/bottom-pinned rows and left/center/right cells on page load', () => { const row0 = '.grid4 .slick-row[data-row="0"]'; // Pinning uses one row node split into regions and a single docking overlay; // it no longer duplicates rows into legacy left/right pinned canvases. cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 2); cy.get(`${row0} .slick-pinned-left-cells > .slick-cell`).should('have.length', 3); cy.get(`${row0} .slick-scrolling-cells > .slick-cell`).should('have.length', 5); cy.get(`${row0} .slick-pinned-right-cells > .slick-cell`).should('have.length', 1); @@ -51,17 +51,22 @@ describe('Example 04 - Pinned Grid', () => { cy.get(`${row0} .slick-pinned-right-cells > .slick-cell:nth(0) .cell-menu-dropdown`).should('contain', 'Action'); }); - it('should pin the last dataset rows when toggling pinned rows to the bottom', () => { - cy.get('[data-test="toggle-pinned-bottom"]').click(); + it('should clear top-pinned rows when selecting zero rows', () => { + cy.get('.pinned-top-row-count').select('0'); + cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('not.exist'); - cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 3); - cy.get('.grid4 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="497"] .slick-cell.l1').should('contain', 'Task 497'); + cy.get('.pinned-top-row-count').select('3'); + cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); + }); + + it('should independently update and clear bottom-pinned rows', () => { + cy.get('.pinned-bottom-row-count').select('0'); + cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('not.exist'); + + cy.get('.pinned-bottom-row-count').select('2'); + cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-bottom').should('have.length', 2); cy.get('.grid4 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="498"] .slick-cell.l1').should('contain', 'Task 498'); cy.get('.grid4 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="499"] .slick-cell.l1').should('contain', 'Task 499'); - cy.get('.grid4 .slick-docking-overlay .slick-row.slick-row-pinned-bottom[data-row="0"]').should('not.exist'); - - cy.get('[data-test="toggle-pinned-bottom"]').click(); - cy.get('.grid4 .slick-docking-overlay > .slick-row.slick-row-pinned-top').should('have.length', 3); }); it('should pin multiple columns on the right and render matching header and filter regions', () => { From a10ee37d067517077ace66d021882aef2660aec2 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 10 Sep 2026 23:34:00 -0400 Subject: [PATCH 22/48] chore: dropzone should always be full grid width --- .../src/core/__tests__/slickGrid.spec.ts | 21 +++++++++++++++++++ packages/common/src/core/slickGrid.ts | 8 +++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/common/src/core/__tests__/slickGrid.spec.ts b/packages/common/src/core/__tests__/slickGrid.spec.ts index e31b3678ce..af131dd01d 100644 --- a/packages/common/src/core/__tests__/slickGrid.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid.spec.ts @@ -1358,6 +1358,27 @@ describe('SlickGrid core file', () => { }); describe('Top-Header Panel', () => { + it('should keep a draggable grouping dropzone fixed during horizontal scrolling', () => { + const columns = [ + { id: 'firstName', field: 'firstName', name: 'First Name' }, + { id: 'lastName', field: 'lastName', name: 'Last Name' }, + ] as Column[]; + grid = new SlickGrid(container, [], columns, { + ...defaultOptions, + pinning: { columns: { left: ['firstName'] } }, + topHeaderPanelHeight: 30, + showTopHeaderPanel: true, + createTopHeaderPanel: true, + }); + grid.init(); + + const topheaderElm = grid.getTopHeaderPanel(); + topheaderElm.classList.add('slick-dropzone'); + grid.scrollToX(25); + + expect(topheaderElm.style.transform).toBe(''); + }); + it('should create a topheader panel when enabled', () => { const paneHeight = 25; const topHeaderPanelHeight = 30; diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index 10c640e9be..afc4987168 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -5088,10 +5088,10 @@ export class SlickGrid = Column, O e this._footerRowL.style.transform = translateX; } if (this._options.createPreHeaderPanel) { - this._preHeaderPanel.style.transform = translateX; + this._preHeaderPanel.style.transform = this._preHeaderPanel.classList.contains('slick-dropzone') ? '' : translateX; } if (this._options.createTopHeaderPanel) { - this._topHeaderPanel.style.transform = translateX; + this._topHeaderPanel.style.transform = this._topHeaderPanel.classList.contains('slick-dropzone') ? '' : translateX; } this._container.style.setProperty('--slick-docking-scroll-left', `${x}px`); return; @@ -5108,10 +5108,10 @@ export class SlickGrid = Column, O e this._footerRowL.style.transform = translateX; } if (this._options.createPreHeaderPanel) { - this._preHeaderPanel.style.transform = translateX; + this._preHeaderPanel.style.transform = this._preHeaderPanel.classList.contains('slick-dropzone') ? '' : translateX; } if (this._options.createTopHeaderPanel) { - this._topHeaderPanel.style.transform = translateX; + this._topHeaderPanel.style.transform = this._topHeaderPanel.classList.contains('slick-dropzone') ? '' : translateX; } } From ffd925fd78da6475645f916cd39c88fdabfc4f5c Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Fri, 11 Sep 2026 02:04:13 -0400 Subject: [PATCH 23/48] chore: pinning should also work with colspan --- PINNING-POC-PROGRESS.md | 23 +- .../src/examples/slickgrid/example14.ts | 4 + .../src/examples/slickgrid/Example14.tsx | 4 + demos/vanilla/src/examples/example08.ts | 4 + demos/vue/src/components/Example14.vue | 4 + demos/vue/test/cypress/e2e/example14.cy.ts | 102 +++++++-- docs/grid-functionalities/pinning.md | 2 + .../docs/grid-functionalities/pinning.md | 2 + .../src/demos/examples/example14.component.ts | 4 + .../docs/grid-functionalities/pinning.md | 2 + .../docs/grid-functionalities/pinning.md | 2 + .../docs/grid-functionalities/pinning.md | 2 + .../core/__tests__/slickGrid-pinning.spec.ts | 67 ++++++ packages/common/src/core/slickGrid.ts | 215 ++++++++++++++++-- .../__tests__/slickHeaderMenu.spec.ts | 1 + .../src/interfaces/gridOption.interface.ts | 3 + .../styles/_variables-theme-salesforce.scss | 1 + packages/common/src/styles/_variables.scss | 1 + packages/common/src/styles/slick-grid.scss | 35 ++- test/cypress/e2e/example08.cy.ts | 43 +++- 20 files changed, 475 insertions(+), 46 deletions(-) diff --git a/PINNING-POC-PROGRESS.md b/PINNING-POC-PROGRESS.md index e752a3dbf2..7f5b3d01fc 100644 --- a/PINNING-POC-PROGRESS.md +++ b/PINNING-POC-PROGRESS.md @@ -1,6 +1,6 @@ # Single-viewport pinning/stickiness POC — progress handoff -Last updated: 2026-09-10 (core/service DRY and render-path audit, directional pin-through commands, separator filtering, header flex cleanup, horizontal scroll hardening, grouping/pinning visual hardening, hidden-column docking alignment, Example 58 framework parity, pinning locale audit, progress/TODO review, and pane-root cleanup) +Last updated: 2026-09-11 (core/service DRY and render-path audit, directional pin-through commands, separator filtering, header flex cleanup, horizontal scroll hardening, grouping/pinning visual hardening, hidden-column docking alignment, cross-band colspan rendering, Example 58 framework parity, pinning locale audit, progress/TODO review, and pane-root cleanup) ## Goal @@ -28,6 +28,14 @@ There is no separate `pinnedColumn` or `pinnedRows` grid option; those temporary aliases were removed after the canonical shape was wired through core and state. The POC does not target compatibility with the old pane-based UX. +For ordinary colspans that cross docking bands, pinning is accepted only when the +resolved bands remain sequential (`left → center → right`). A non-sequential +change such as pinning the second column while leaving the first column in the +center is rejected through `invalidColumnPinningPickerCallback`; the default +message can be customized with `invalidColumnPinningSequenceMessage`. This +validation runs during pinning changes and does not add work to horizontal +scrolling. + The canonical grid-state shape is now a single nested `GridOption.pinning` object: `{ columns: { left, right }, rows: { top, bottom } }`. `Column.pinned` remains the per-column representation. `GridService.setPinning()` and `GridStateService` now read @@ -132,6 +140,14 @@ title's measured outer width but no longer extend into the vertical-scrollbar gu header chrome aligned and prevents a right-edge filter such as `Effort-Driven` from overlapping its neighboring `Action` cell. +Ordinary colspans that cross left, center, or right docking bands now keep one logical/content host +cell and render lightweight visual continuation fragments in each affected band. Fragments share +the host's styling but are excluded from logical-cell caching and are removed/rebuilt with the host, +so formatters, selection, and virtualization continue to operate on one cell. Clicking any +fragment activates the complete span; keyboard arrows continue to navigate between logical cells, +skipping continuation fragments. The docking separator is suppressed only at an internal colspan +split, so the span remains visually continuous while real outer docking boundaries keep their cue. + Docked body cells now calculate center-band right offsets from the rendered center-region width when left/right pinning expands that region to the viewport. This prevents the last remaining center cell, such as `Action` after hiding `Finish`, from stretching away from its header. Vanilla Example @@ -787,7 +803,10 @@ requirements are the two largest sources of variance. references only; active menus use `Pin Columns Left`/`Pin Columns Right` and `Unpin All Columns` and write the canonical `pinning` option. `GridService.setPinning()` accepts the unified nested shape. 3. **Visual/browser validation is incomplete.** Left/right pinning, bottom rows, sticky transitions, resize, reorder, RTL, variable row height, row/column spans, editors, selection, and all four framework wrappers need manual follow-up. -4. **Colspans crossing docking bands are not defined.** A colspan beginning in one band and ending in another can produce incorrect geometry. The final design should reject, split, or explicitly define this case. +4. **Cross-band colspans are defined.** The logical cell remains one host while visual continuation + fragments are rendered in each affected docking region; full-width group rows retain their + dedicated viewport-wide rendering. A separator is omitted only when it would cut through the + logical span. 5. **Grouped/pre-header chrome is dock-aware.** `HeaderGroupingService` orders visible columns by docking band and splits a repeated `columnGroup` title at each left/center/right boundary. Remaining cross-framework visual validation is covered by item 3. 6. **Sticky activation can be skipped by a very large scroll jump.** Candidates currently must first be fully visible. A final implementation should detect that the scroll path crossed a candidate even if no intermediate frame showed it fully. 7. **Numeric row reference ambiguity.** An in-range number is treated as a row index before it is treated as a dataset ID. A tagged `{ id } | { index }` row reference would remove this ambiguity. diff --git a/demos/aurelia/src/examples/slickgrid/example14.ts b/demos/aurelia/src/examples/slickgrid/example14.ts index 82565fdbdf..aafe691b39 100644 --- a/demos/aurelia/src/examples/slickgrid/example14.ts +++ b/demos/aurelia/src/examples/slickgrid/example14.ts @@ -68,6 +68,10 @@ export class Example14 { getRowMetadata: (item: any, row: number) => this.renderDifferentColspan(item, row), }, }, + headerMenu: { + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, + }, gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, diff --git a/demos/react/src/examples/slickgrid/Example14.tsx b/demos/react/src/examples/slickgrid/Example14.tsx index 7fc4a5fa09..c923733562 100644 --- a/demos/react/src/examples/slickgrid/Example14.tsx +++ b/demos/react/src/examples/slickgrid/Example14.tsx @@ -63,6 +63,10 @@ const Example14: React.FC = () => { getRowMetadata: (item: any, row: number) => renderDifferentColspan(item, row), }, }, + headerMenu: { + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, + }, gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, diff --git a/demos/vanilla/src/examples/example08.ts b/demos/vanilla/src/examples/example08.ts index b28d58810c..da613e31a0 100644 --- a/demos/vanilla/src/examples/example08.ts +++ b/demos/vanilla/src/examples/example08.ts @@ -74,6 +74,10 @@ export default class Example08 { gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, + headerMenu: { + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, + }, externalResources: [new TextExportService(), new ExcelExportService(), new PdfExportService()], enableCellNavigation: true, enableColumnReorder: false, diff --git a/demos/vue/src/components/Example14.vue b/demos/vue/src/components/Example14.vue index 8bebedf1cc..a713e38b82 100644 --- a/demos/vue/src/components/Example14.vue +++ b/demos/vue/src/components/Example14.vue @@ -58,6 +58,10 @@ function definedGrid1() { getRowMetadata: (item: any, row: number) => renderDifferentColspan(item, row), }, }, + headerMenu: { + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, + }, gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, diff --git a/demos/vue/test/cypress/e2e/example14.cy.ts b/demos/vue/test/cypress/e2e/example14.cy.ts index 70eae17cc8..b11f44c643 100644 --- a/demos/vue/test/cypress/e2e/example14.cy.ts +++ b/demos/vue/test/cypress/e2e/example14.cy.ts @@ -169,52 +169,52 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and land on a full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and be on Task 8 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=8] > .slick-cell.l0.r5.active').should('have.length', 1); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and land on a colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and be on Task 15 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=15] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should navigate PageUp twice from a colspan of 3 back to the starting colspan of 3', () => { - cy.get('#grid1 [data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 15 on Duration colspan 5 days and type "PageUp" key 2x times and be on Task 1 with full colspan', () => { + cy.get('[data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); }); @@ -237,7 +237,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -295,7 +295,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -355,6 +355,66 @@ describe('Example 14 - Column Span & Header Grouping', () => { .children() .each(($child, index) => expect($child.text()).to.eq(newHeaderTitles[index])); }); + + it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { + cy.get('#grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-center [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('contain', '5 days') + .and('have.class', 'slick-cell-colspan-crossing-docking'); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part') + .click({ force: true }) + .should('have.class', 'active'); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should(($cell) => { + const style = getComputedStyle($cell[0], '::after'); + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(style.borderLeftStyle).to.eq('none'); + expect(style.borderRightStyle).to.eq('solid'); + }); + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('have.class', 'active') + .and('contain', '5 days') + .should(($cell) => { + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-left [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 .slick-header-columns-left [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + }); }); describe('First Grid - Key Navigation', () => { diff --git a/docs/grid-functionalities/pinning.md b/docs/grid-functionalities/pinning.md index 98d10389e2..f5771d3d8a 100644 --- a/docs/grid-functionalities/pinning.md +++ b/docs/grid-functionalities/pinning.md @@ -47,6 +47,8 @@ export class GridBasicComponent { > **Caution** > The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. +> When a rendered row contains a colspan crossing docking bands, columns must be pinned sequentially from the left or right edge. A non-sequential change is rejected through `invalidColumnPinningPickerCallback` and can be customized with `invalidColumnPinningSequenceMessage`. + ## Rows Pinning starting from bottom To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component diff --git a/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md b/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md index e47c15ea0f..8cec8d0caf 100644 --- a/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md +++ b/frameworks/angular-slickgrid/docs/grid-functionalities/pinning.md @@ -57,6 +57,8 @@ export class GridBasicComponent implements OnInit { > **Caution** > The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. +> When a rendered row contains a colspan crossing docking bands, columns must be pinned sequentially from the left or right edge. A non-sequential change is rejected through `invalidColumnPinningPickerCallback` and can be customized with `invalidColumnPinningSequenceMessage`. + ## Rows Pinning starting from bottom To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component diff --git a/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts b/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts index 4250618474..d92bf1b543 100644 --- a/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts +++ b/frameworks/angular-slickgrid/src/demos/examples/example14.component.ts @@ -60,6 +60,10 @@ export class Example14Component implements OnInit { getRowMetadata: (item: any) => this.renderDifferentColspan(item), }, }, + headerMenu: { + hidePinColumnCommand: false, + hidePinningColumnsCommand: false, + }, gridMenu: { iconButtonContainer: 'preheader', // we can display the grid menu icon in either the preheader or in the column header (default) }, diff --git a/frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md b/frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md index 1812c55cdd..61e0f802cf 100644 --- a/frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md +++ b/frameworks/aurelia-slickgrid/docs/grid-functionalities/pinning.md @@ -56,6 +56,8 @@ export class GridBasicComponent { > **Caution** > The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. +> When a rendered row contains a colspan crossing docking bands, columns must be pinned sequentially from the left or right edge. A non-sequential change is rejected through `invalidColumnPinningPickerCallback` and can be customized with `invalidColumnPinningSequenceMessage`. + ## Rows Pinning starting from bottom To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component diff --git a/frameworks/slickgrid-react/docs/grid-functionalities/pinning.md b/frameworks/slickgrid-react/docs/grid-functionalities/pinning.md index 238101baf1..307b9af853 100644 --- a/frameworks/slickgrid-react/docs/grid-functionalities/pinning.md +++ b/frameworks/slickgrid-react/docs/grid-functionalities/pinning.md @@ -57,6 +57,8 @@ const Example: React.FC = () => { > **Caution** > The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. +> When a rendered row contains a colspan crossing docking bands, columns must be pinned sequentially from the left or right edge. A non-sequential change is rejected through `invalidColumnPinningPickerCallback` and can be customized with `invalidColumnPinningSequenceMessage`. + ## Rows Pinning starting from bottom To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component diff --git a/frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md b/frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md index 3108fc996d..789f12613a 100644 --- a/frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md +++ b/frameworks/slickgrid-vue/docs/grid-functionalities/pinning.md @@ -63,6 +63,8 @@ function defineGrid() { > **Caution** > The Column Picker and Grid Menu also validate that at least one center column remains available. You can customize this with `invalidColumnPinningPickerCallback` or disable pinning validation with `skipPinningValidation`. +> When a rendered row contains a colspan crossing docking bands, columns must be pinned sequentially from the left or right edge. A non-sequential change is rejected through `invalidColumnPinningPickerCallback` and can be customized with `invalidColumnPinningSequenceMessage`. + ## Rows Pinning starting from bottom To pin rows at the bottom, provide the row indexes in `pinning.rows.bottom` and leave the top list empty. ##### Component diff --git a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts index 4241632972..cbd61e6bfb 100644 --- a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts @@ -112,6 +112,73 @@ describe('SlickGrid unified pinning', () => { expect(groupCell.parentElement).toBe(groupRow); }); + it('splits colspans that cross docking regions into visual fragments', () => { + container = document.createElement('div'); + container.style.width = '800px'; + container.style.height = '400px'; + document.body.appendChild(container); + const spanData = { + getLength: () => data.length, + getItem: (row: number) => data[row], + getItemMetadata: (row: number) => (row === 0 ? { columns: { 0: { colspan: columns.length, formatter: () => 'Spanned' } } } : undefined), + }; + grid = new SlickGrid(container, spanData as any, columns.map((column) => ({ ...column })) as Column[], { + devMode: { ownerNodeIndex: 0 }, + pinning: { columns: { left: ['a'], right: ['d'] } }, + }); + + const row = container.querySelector('[data-row="0"]')!; + const host = row.querySelector('.slick-cell.l0:not(.slick-cell-colspan-part)')!; + const fragments = row.querySelectorAll('.slick-cell-colspan-part'); + + expect(host.textContent).toBe('Spanned'); + expect(row.querySelectorAll('.slick-cell-colspan-crossing-docking')).toHaveLength(3); + expect(fragments[1].classList.contains('slick-cell-colspan-end')).toBe(true); + expect(fragments).toHaveLength(2); + expect(host.parentElement?.classList.contains('slick-pinned-left-cells')).toBe(true); + expect(fragments[0].parentElement?.classList.contains('slick-scrolling-cells')).toBe(true); + expect(fragments[1].parentElement?.classList.contains('slick-pinned-right-cells')).toBe(true); + expect(fragments[0].getAttribute('aria-hidden')).toBe('true'); + expect((grid as any).getCellNode(0, 0)).toBe(host); + + grid.setActiveCell(0, 0); + expect(row.querySelectorAll('.slick-cell.active')).toHaveLength(3); + expect((grid as any).getCellFromEvent({ target: fragments[0] })).toEqual({ row: 0, cell: 0 }); + }); + + it('rejects non-sequential pinning when a rendered colspan crosses docking regions', () => { + const invalidPinning = vi.fn(); + const spanData = { + getLength: () => data.length, + getItem: (row: number) => data[row], + getItemMetadata: (row: number) => (row === 0 ? { columns: { 1: { colspan: 3 } } } : undefined), + }; + container = document.createElement('div'); + container.style.width = '800px'; + container.style.height = '400px'; + document.body.appendChild(container); + grid = new SlickGrid(container, spanData as any, columns.map((column) => ({ ...column })) as Column[], { + devMode: { ownerNodeIndex: 0 }, + invalidColumnPinningPickerCallback: invalidPinning, + }); + + grid.setColumnPinning('b', 'left'); + expect(invalidPinning).toHaveBeenCalledWith(expect.stringContaining('non-sequential order')); + expect(grid.getColumns()[1].pinned).toBeFalsy(); + + grid.setColumnPinning('c', 'right'); + expect(grid.getColumns()[2].pinned).toBeFalsy(); + + grid.setColumnPinning('a', 'left'); + grid.setColumnPinning('b', 'left'); + expect( + grid + .getColumns() + .slice(0, 2) + .every((column) => column.pinned === 'left') + ).toBe(true); + }); + it('keeps the native viewport scroll owner until docking is enabled', () => { const slickGrid = createGrid(); const internals = slickGrid as any; diff --git a/packages/common/src/core/slickGrid.ts b/packages/common/src/core/slickGrid.ts index afc4987168..7bbcd40803 100755 --- a/packages/common/src/core/slickGrid.ts +++ b/packages/common/src/core/slickGrid.ts @@ -149,6 +149,8 @@ interface RowCaching { cellColSpans: Array; cellNodesByColumnIdx: HTMLElement[]; cellRenderQueue: any[]; + cellSpanFragments: Record; + cellSpanSegments: Record>; } const EMPTY_DOCKING_LAYOUT: ColumnDockingLayout = { @@ -264,6 +266,9 @@ export class SlickGrid = Column, O e invalidColumnPinningPickerMessage: '[SlickGrid] Action not allowed and aborted, you need to have at least one or more column in the center section of the grid. ' + 'You could alternatively unpin columns before trying again.', + invalidColumnPinningSequenceMessage: + '[SlickGrid] Action not allowed and aborted because pinning would split a colspan across the grid in a non-sequential order. ' + + 'Pin columns from the left or right edge without skipping columns.', skipPinningValidation: false, allowDragFromClosest: 'div.slick-cell.dnd, div.slick-cell.cell-reorder', alwaysShowVerticalScroll: false, @@ -1357,12 +1362,10 @@ export class SlickGrid = Column, O e ? columns.map((column) => (column?.id === columnId && !column.hidden ? { ...column, hidden: true } : column)) : columns; const pinnedIndexes = this.getPinnedColumnIndexes(this._options.pinning?.columns); - const visibleIndexes = prospectiveColumns.reduce((indexes, column, index) => { - if (column && !column.hidden) { - indexes.push(index); - } - return indexes; - }, []); + if (!this.validateColspanPinningSequence(pinnedIndexes, forceAlert, prospectiveColumns)) { + return false; + } + const visibleIndexes = this.getVisibleColumnIndexes(prospectiveColumns); const hasCenterColumn = visibleIndexes.some((index) => !pinnedIndexes.has(index)); if (!hasCenterColumn && visibleIndexes.length && !this._options.skipPinningValidation) { if ((forceAlert || !this._invalidPinningAlerted) && this._options.invalidColumnPinningPickerCallback) { @@ -3591,6 +3594,15 @@ export class SlickGrid = Column, O e return this.columns.filter((c) => !c.hidden); } + protected getVisibleColumnIndexes(columns: C[] = this.columns): number[] { + return columns.reduce((indexes, column, index) => { + if (column && !column.hidden) { + indexes.push(index); + } + return indexes; + }, []); + } + // General triggerEvent(evt: SlickEvent, args?: ArgType, e?: Event | SlickEventData): SlickEventData { @@ -4014,12 +4026,11 @@ export class SlickGrid = Column, O e return true; } - const visibleIndexes = columns.reduce((indexes, column, index) => { - if (column && !column.hidden) { - indexes.push(index); - } - return indexes; - }, []); + if (!this.validateColspanPinningSequence(pinnedIndexes, forceAlert, columns)) { + return false; + } + + const visibleIndexes = this.getVisibleColumnIndexes(columns); if (visibleIndexes.length && visibleIndexes.every((index) => pinnedIndexes.has(index))) { if ((forceAlert || !this._invalidPinningAlerted) && this._options.invalidColumnPinningPickerCallback) { this._options.invalidColumnPinningPickerCallback(this._options.invalidColumnPinningPickerMessage!); @@ -4058,6 +4069,68 @@ export class SlickGrid = Column, O e return true; } + /** Reject only non-sequential pinning that would visually split a rendered colspan. */ + protected validateColspanPinningSequence( + pinnedIndexes: Map, + forceAlert = false, + columns: C[] = this.columns + ): boolean { + const visibleIndexes = this.getVisibleColumnIndexes(columns); + + let previousBandOrder = 0; + const isSequential = visibleIndexes.every((index) => { + const band = pinnedIndexes.get(index) || 'center'; + const bandOrder = band === 'left' ? 0 : band === 'center' ? 1 : 2; + if (bandOrder < previousBandOrder) { + return false; + } + previousBandOrder = bandOrder; + return true; + }); + if (isSequential) { + return true; + } + + const hasCrossBandColspan = Object.keys(this.rowsCache).some((rowId) => { + const metadata = this.getItemMetadaWhenExists(Number(rowId)); + if (!metadata?.columns || metadata.isGroup) { + return false; + } + + return Object.entries(metadata.columns).some(([columnRef, columnMetadata]) => { + const columnIndex = Number(columnRef); + const start = Number.isNaN(columnIndex) ? this.getColumnIndex(columnRef) : columnIndex; + const span = columnMetadata?.colspan === '*' ? columns.length - start : Number(columnMetadata?.colspan || 1); + if (!isDefinedNumber(start) || span <= 1) { + return false; + } + const end = Math.min(columns.length - 1, start + span - 1); + let firstBand: ColumnDockingBand | undefined; + return visibleIndexes.some((index) => { + if (index < start || index > end) { + return false; + } + const band = pinnedIndexes.get(index) || 'center'; + if (!firstBand) { + firstBand = band; + return false; + } + return band !== firstBand; + }); + }); + }); + + if (!hasCrossBandColspan) { + return true; + } + + if ((forceAlert || !this._invalidPinningAlerted) && this._options.invalidColumnPinningPickerCallback) { + this._options.invalidColumnPinningPickerCallback(this._options.invalidColumnPinningSequenceMessage!); + this._invalidPinningAlerted = true; + } + return false; + } + /** Merge a partial pinning update before validating it. */ protected getProspectivePinnedColumnIndexes(incomingColumns: PinnedColumns): Map { const currentColumns = this._options.pinning?.columns; @@ -4173,6 +4246,9 @@ export class SlickGrid = Column, O e if (rowNode && !rowNode.classList.contains('slick-row-docked')) { return false; } + if (Object.keys(cacheEntry.cellSpanFragments || {}).length) { + return false; + } } for (const cacheEntry of Object.values(this.rowsCache)) { @@ -5400,16 +5476,19 @@ export class SlickGrid = Column, O e rowspan: number, columnMetadata: ColumnMetadata | null, item: TData, - isFullWidthGroup = false + isFullWidthGroup = false, + deferFragments = false ): void { // divRow: the html element to append items too // row, cell: row and column index // colspan: HTML colspan // item: grid data for row + const segments = colspan > 1 && !isFullWidthGroup && this.usesDockingRowRegions() ? this.getColspanSegments(cell, colspan) : []; + const renderedColspan = segments.length > 1 ? segments[0].end - cell + 1 : colspan; const m = this.columns[cell]; let cellCss = - `slick-cell l${cell} r${Math.min(this.columns.length - 1, cell + colspan - 1)}` + + `slick-cell l${cell} r${Math.min(this.columns.length - 1, cell + renderedColspan - 1)}` + (m.cssClass ? ` ${m.cssClass}` : '') + (rowspan > 1 ? ' rowspan' : '') + (columnMetadata?.cssClass ? ` ${columnMetadata.cssClass}` : ''); @@ -5511,6 +5590,9 @@ export class SlickGrid = Column, O e this.rowsCache[row].cellRenderQueue.push(cell); this.rowsCache[row].cellColSpans[cell] = colspan; + if (segments.length > 1) { + this.appendColspanFragments(row, cell, cellDiv, segments, deferFragments); + } } protected cleanupRows(rangeToKeep: { bottom: number; top: number }): void { @@ -6420,7 +6502,8 @@ export class SlickGrid = Column, O e // Row detail formatters can insert a non-cell sibling after the detail-toggle cell. // Only actual cells belong in cellNodesByColumnIdx; otherwise cache rebuilding tries // to parse a column index from classes such as `dynamic-cell-detail`. - const cellChildren = (nodes: HTMLElement[]) => nodes.filter((node) => node.classList.contains('slick-cell')); + const cellChildren = (nodes: HTMLElement[]) => + nodes.filter((node) => node.classList.contains('slick-cell') && !node.classList.contains('slick-cell-colspan-part')); if (!rowNode.classList.contains('slick-row-docked')) { return cellChildren(children); } @@ -6458,6 +6541,82 @@ export class SlickGrid = Column, O e return (rowNode.querySelector(`:scope > ${selector}`) as HTMLElement) || rowNode; } + protected toggleCellSpanFragmentsActive(row: number, cell: number, active: boolean): void { + this.rowsCache[row]?.cellSpanFragments?.[cell]?.forEach((fragment) => fragment.classList.toggle('active', active)); + } + + protected getColspanSegments(cell: number, colspan: number): Array<{ start: number; end: number; band: ColumnDockingBand }> { + const segments: Array<{ start: number; end: number; band: ColumnDockingBand }> = []; + const end = Math.min(this.columns.length - 1, cell + colspan - 1); + + for (let index = cell; index <= end; index++) { + if (this.columns[index]?.hidden) { + continue; + } + const band = this.getColumnDockingBand(index); + const previous = segments[segments.length - 1]; + if (previous?.band === band) { + previous.end = index; + } else { + segments.push({ start: index, end: index, band }); + } + } + return segments; + } + + protected appendColspanFragments( + row: number, + cell: number, + host: HTMLElement, + segments: Array<{ start: number; end: number; band: ColumnDockingBand }>, + deferToRow: boolean + ): void { + host.classList.add('slick-cell-colspan-crossing-docking'); + const fragments = segments.slice(1).map((segment, index, allFragments) => { + const fragment = host.cloneNode(false) as HTMLElement; + fragment.classList.add('slick-cell-colspan-part'); + fragment.classList.toggle('slick-cell-colspan-end', index === allFragments.length - 1); + fragment.classList.remove('slick-cell-pinned-left', 'slick-cell-pinned-right', 'slick-cell-sticky'); + if (segment.band !== 'center') { + fragment.classList.add(`slick-cell-pinned-${segment.band}`); + if (this.dockingByColumn.get(segment.start)?.sticky) { + fragment.classList.add('slick-cell-sticky'); + } + } + fragment.setAttribute('aria-hidden', 'true'); + fragment.setAttribute('role', 'presentation'); + fragment.removeAttribute('aria-describedby'); + fragment.removeAttribute('tabindex'); + + const bandWidth = + segment.band === 'left' + ? this.dockingLayout.leftWidth + : segment.band === 'right' + ? this.dockingLayout.rightWidth + : this.getDockingRenderedCenterWidth(); + const left = this.columnPosLeft[segment.start] ?? 0; + const right = this.columnPosRight[segment.end] ?? left; + if (this._options.rtl) { + fragment.style.right = `${left}px`; + fragment.style.left = `${Math.max(0, bandWidth - right)}px`; + } else { + fragment.style.left = `${left}px`; + fragment.style.right = `${Math.max(0, bandWidth - right)}px`; + } + return fragment; + }); + + this.rowsCache[row].cellSpanFragments[cell] = fragments; + this.rowsCache[row].cellSpanSegments[cell] = segments; + fragments.forEach((fragment, index) => { + if (deferToRow) { + host.parentElement?.insertBefore(fragment, host); + } else { + this.getRowDockingRegion(host.closest('.slick-row') as HTMLElement, segments[index + 1].start).appendChild(fragment); + } + }); + } + protected cleanUpCells(range: CellViewportRange, row: number): void { if (this.isPinnedRowIdx(row)) { return; @@ -6510,8 +6669,12 @@ export class SlickGrid = Column, O e cellNode.parentElement?.removeChild(cellNode); } + cacheEntry.cellSpanFragments?.[cellToRemove]?.forEach((fragment) => fragment.remove()); + delete cacheEntry.cellColSpans[cellToRemove]; delete cacheEntry.cellNodesByColumnIdx[cellToRemove]; + delete cacheEntry.cellSpanFragments?.[cellToRemove]; + delete cacheEntry.cellSpanSegments?.[cellToRemove]; /* v8 ignore if */ if (this.postProcessedRows[row]) { delete this.postProcessedRows[row][cellToRemove]; @@ -6520,7 +6683,7 @@ export class SlickGrid = Column, O e } protected cleanUpAndRenderCells(range: CellViewportRange): void { - let cacheEntry; + let cacheEntry: RowCaching; const divRow: HTMLElement = document.createElement('div'); const processedRows: number[] = []; let cellsAdded: number; @@ -6601,7 +6764,7 @@ export class SlickGrid = Column, O e const rowspan = this.getRowspan(row, i); const isFullWidthGroup = this.usesDockingRowRegions() && this.isFullWidthGroupCell(metadata, columnData, i, ncolspan); cacheEntry.rowNode?.[0].classList.toggle('slick-row-full-width-group', isFullWidthGroup); - this.appendCellHtml(divRow, row, i, ncolspan, rowspan, columnData, d, isFullWidthGroup); + this.appendCellHtml(divRow, row, i, ncolspan, rowspan, columnData, d, isFullWidthGroup, true); cellsAdded++; } @@ -6635,6 +6798,12 @@ export class SlickGrid = Column, O e cacheEntry.rowNode![0].appendChild(node); } cacheEntry.cellNodesByColumnIdx![columnIdx] = node; + + const fragments = cacheEntry.cellSpanFragments?.[columnIdx]; + const segments = cacheEntry.cellSpanSegments?.[columnIdx]; + fragments?.forEach((fragment, index) => { + this.getRowDockingRegion(cacheEntry.rowNode![0], segments[index + 1].start).appendChild(fragment); + }); } } cacheEntry.rowNode?.forEach((rowNode) => this.applyRowTopOffset(rowNode, processedRow!)); @@ -6670,6 +6839,10 @@ export class SlickGrid = Column, O e // cellNodesByColumnIdx. These are in the same order as cell nodes added at the // end of the row. cellRenderQueue: [], + + // Continuation fragments for colspans crossing docking bands, keyed by host cell. + cellSpanFragments: {}, + cellSpanSegments: {}, }; } @@ -8041,8 +8214,13 @@ export class SlickGrid = Column, O e /** Clear active cell by making cell normal & removing "active" CSS class. */ unsetActiveCell(): void { if (isDefined(this.activeCellNode)) { + const activeRow = this.activeRow; + const activeCell = this.getCellFromNode(this.activeCellNode); this.makeActiveCellNormal(); this.activeCellNode.classList.remove('active'); + if (isDefinedNumber(activeRow)) { + this.toggleCellSpanFragmentsActive(activeRow, activeCell, false); + } this.rowsCache[this.activeRow]?.rowNode?.forEach((node) => node.classList.remove('active')); } } @@ -8179,6 +8357,9 @@ export class SlickGrid = Column, O e // v8 ignore next document.querySelectorAll('.slick-cell.active').forEach((node) => node.classList.remove('active')); this.activeCellNode.classList.add('active'); + if (isDefinedNumber(this.activeRow) && isDefinedNumber(this.activeCell)) { + this.toggleCellSpanFragmentsActive(this.activeRow, this.activeCell, true); + } this.rowsCache[this.activeRow]?.rowNode?.forEach((node) => node.classList.add('active')); } diff --git a/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts b/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts index 26f7773402..ad24754948 100644 --- a/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts +++ b/packages/common/src/extensions/__tests__/slickHeaderMenu.spec.ts @@ -1455,6 +1455,7 @@ describe('HeaderMenu Plugin', () => { ]); expect(commandIconElm.classList.contains('mdi-pin-outline')).toBeTruthy(); expect(commandLabelElm.textContent).toBe('Pin Columns Left'); + expect((plugin as any).getPinnableColumnReferences(columnsMock[1], 'right')).toBe(2); await translateService.use('fr'); plugin.translateHeaderMenu(); diff --git a/packages/common/src/interfaces/gridOption.interface.ts b/packages/common/src/interfaces/gridOption.interface.ts index 8e48cb73a1..f49873058a 100644 --- a/packages/common/src/interfaces/gridOption.interface.ts +++ b/packages/common/src/interfaces/gridOption.interface.ts @@ -965,6 +965,9 @@ export interface GridOption { /** Message to show when a pinning change would leave no center column. */ invalidColumnPinningPickerMessage?: string; + /** Message to show when pinning would split a colspan in a non-sequential order. */ + invalidColumnPinningSequenceMessage?: string; + /** * Defaults to `alert(error)`, which will trigger when a pinning change would hide all center columns. */ diff --git a/packages/common/src/styles/_variables-theme-salesforce.scss b/packages/common/src/styles/_variables-theme-salesforce.scss index e9008d00b9..7d63adca8f 100644 --- a/packages/common/src/styles/_variables-theme-salesforce.scss +++ b/packages/common/src/styles/_variables-theme-salesforce.scss @@ -31,6 +31,7 @@ $slick-editor-grid-cell-border-width-modified: 1px 7px 1px 1px $slick-icon-font-size: 18px !default, $slick-input-focus-border-color: color.adjust($slick-primary-color, $lightness: 12%) !default, $slick-cell-active-box-shadow: inset 0 0 0 1px #{$slick-highlight-color} !default, + $slick-cell-active-border-color: $slick-highlight-color !default, $slick-cell-border-top: none !default, $slick-cell-border-bottom: none !default, $slick-cell-box-shadow: #{inset 0px 1px 0px #dddbda, inset 0px -1px 0px #dddbda} !default, diff --git a/packages/common/src/styles/_variables.scss b/packages/common/src/styles/_variables.scss index cc47dbc256..78a34306a0 100644 --- a/packages/common/src/styles/_variables.scss +++ b/packages/common/src/styles/_variables.scss @@ -88,6 +88,7 @@ $slick-cell-border-right: 1px transparent !def $slick-cell-border-bottom: 1px transparent !default; $slick-cell-border-left: 1px transparent !default; $slick-cell-active-box-shadow: inset 0 0 0 1px #aaaaaa !default; +$slick-cell-active-border-color: #aaaaaa !default; $slick-cell-active-z-index: 6 !default; $slick-cell-even-background-color: #ffffff !default; $slick-cell-odd-background-color: color.adjust($slick-grid-cell-color, $lightness: -3%) !default; // for striping every second row diff --git a/packages/common/src/styles/slick-grid.scss b/packages/common/src/styles/slick-grid.scss index e4bd45fb32..9d19bcb8f8 100644 --- a/packages/common/src/styles/slick-grid.scss +++ b/packages/common/src/styles/slick-grid.scss @@ -298,8 +298,9 @@ } // Paint the separator only on boundary data cells, not full-width rows. - > .slick-pinned-left-cells.slick-pinned-left-cells-active > .slick-cell:last-child::after, - > .slick-pinned-right-cells.slick-pinned-right-cells-active > .slick-cell:first-child::after { + > .slick-pinned-left-cells.slick-pinned-left-cells-active > .slick-cell:last-child:not(.slick-cell-colspan-crossing-docking)::after, + > .slick-pinned-right-cells.slick-pinned-right-cells-active + > .slick-cell:first-child:not(.slick-cell-colspan-crossing-docking)::after { content: ''; position: absolute; inset: 0; @@ -307,14 +308,40 @@ z-index: 1; } - > .slick-pinned-left-cells.slick-pinned-left-cells-active > .slick-cell:last-child::after { + > .slick-pinned-left-cells.slick-pinned-left-cells-active > .slick-cell:last-child:not(.slick-cell-colspan-crossing-docking)::after { box-shadow: var(--slick-pinned-border-box-shadow-left, v.$slick-pinned-border-box-shadow-left); } - > .slick-pinned-right-cells.slick-pinned-right-cells-active > .slick-cell:first-child::after { + > .slick-pinned-right-cells.slick-pinned-right-cells-active + > .slick-cell:first-child:not(.slick-cell-colspan-crossing-docking)::after { box-shadow: var(--slick-pinned-border-box-shadow-right, v.$slick-pinned-border-box-shadow-right); } + // Use a border overlay for active cross-band colspans. It does not affect cell size. + > :is(.slick-pinned-left-cells, .slick-scrolling-cells, .slick-pinned-right-cells) { + > .slick-cell-colspan-crossing-docking.active { + box-shadow: none; + + &::after { + content: ''; + position: absolute; + inset: 0; + box-sizing: border-box; + border: 1px solid var(--slick-cell-active-border-color, v.$slick-cell-active-border-color); + pointer-events: none; + z-index: 1; + } + + &:not(.slick-cell-colspan-end)::after { + border-inline-end: 0; + } + } + + > .slick-cell-colspan-part.active::after { + border-inline-start: 0; + } + } + // A RowSpan cell extends beyond its host row. The normal region clip is // still required for virtualized rows, but would hide the spanning cell // (and make it impossible to click) when columns are docked. diff --git a/test/cypress/e2e/example08.cy.ts b/test/cypress/e2e/example08.cy.ts index 0e4b2583fc..252f6e6947 100644 --- a/test/cypress/e2e/example08.cy.ts +++ b/test/cypress/e2e/example08.cy.ts @@ -307,7 +307,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -365,7 +365,7 @@ describe('Example 08 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -425,6 +425,45 @@ describe('Example 08 - Column Span & Header Grouping', () => { .children() .each(($child, index) => expect($child.text()).to.eq(newHeaderTitles[index])); }); + + it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { + const setPinning = (headerSelector: string, command: 'pin-left' | 'unpin-column') => { + cy.get(headerSelector).trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get(`.slick-submenu:visible [data-command="${command}"]`).click(); + }; + const hostSelector = '.grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)'; + const fragmentSelector = '.grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part'; + + setPinning('.grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]', 'pin-left'); + + cy.get('.grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); + }); + + setPinning('.grid1 .slick-header-columns-center [data-id="duration"]', 'pin-left'); + + cy.get(hostSelector).should('contain', '5 days').and('have.class', 'slick-cell-colspan-crossing-docking'); + cy.get('.grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get(fragmentSelector).should('have.length', 1).click({ force: true }).should('have.class', 'active'); + cy.get(fragmentSelector).should(($cell) => { + const style = getComputedStyle($cell[0], '::after'); + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(style.borderLeftStyle).to.eq('none'); + expect(style.borderRightStyle).to.eq('solid'); + }); + cy.get(hostSelector) + .should('have.class', 'active') + .and('contain', '5 days') + .should(($cell) => { + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + }); + + setPinning('.grid1 .slick-header-columns-left [data-id="duration"]', 'unpin-column'); + setPinning('.grid1 .slick-header-columns-left [data-id="title"]', 'unpin-column'); + cy.get('.grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + }); }); describe('First Grid - Key Navigation', () => { From 3cc7251e46a49cc8093b48360182a1c17b4c0010 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Fri, 11 Sep 2026 02:17:09 -0400 Subject: [PATCH 24/48] chore: increase test coverage and fix missed cypress tests --- .../aurelia/test/cypress/e2e/example14.cy.ts | 102 ++++++++++++++---- demos/react/test/cypress/e2e/example14.cy.ts | 102 ++++++++++++++---- .../test/cypress/e2e/example14.cy.ts | 102 ++++++++++++++---- .../core/__tests__/slickGrid-pinning.spec.ts | 34 +++++- 4 files changed, 275 insertions(+), 65 deletions(-) diff --git a/demos/aurelia/test/cypress/e2e/example14.cy.ts b/demos/aurelia/test/cypress/e2e/example14.cy.ts index 70eae17cc8..b11f44c643 100644 --- a/demos/aurelia/test/cypress/e2e/example14.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example14.cy.ts @@ -169,52 +169,52 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and land on a full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and be on Task 8 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=8] > .slick-cell.l0.r5.active').should('have.length', 1); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and land on a colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and be on Task 15 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=15] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should navigate PageUp twice from a colspan of 3 back to the starting colspan of 3', () => { - cy.get('#grid1 [data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 15 on Duration colspan 5 days and type "PageUp" key 2x times and be on Task 1 with full colspan', () => { + cy.get('[data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); }); @@ -237,7 +237,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -295,7 +295,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -355,6 +355,66 @@ describe('Example 14 - Column Span & Header Grouping', () => { .children() .each(($child, index) => expect($child.text()).to.eq(newHeaderTitles[index])); }); + + it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { + cy.get('#grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-center [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('contain', '5 days') + .and('have.class', 'slick-cell-colspan-crossing-docking'); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part') + .click({ force: true }) + .should('have.class', 'active'); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should(($cell) => { + const style = getComputedStyle($cell[0], '::after'); + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(style.borderLeftStyle).to.eq('none'); + expect(style.borderRightStyle).to.eq('solid'); + }); + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('have.class', 'active') + .and('contain', '5 days') + .should(($cell) => { + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-left [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 .slick-header-columns-left [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + }); }); describe('First Grid - Key Navigation', () => { diff --git a/demos/react/test/cypress/e2e/example14.cy.ts b/demos/react/test/cypress/e2e/example14.cy.ts index 70eae17cc8..b11f44c643 100644 --- a/demos/react/test/cypress/e2e/example14.cy.ts +++ b/demos/react/test/cypress/e2e/example14.cy.ts @@ -169,52 +169,52 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and land on a full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and be on Task 8 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=8] > .slick-cell.l0.r5.active').should('have.length', 1); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and land on a colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and be on Task 15 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=15] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should navigate PageUp twice from a colspan of 3 back to the starting colspan of 3', () => { - cy.get('#grid1 [data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 15 on Duration colspan 5 days and type "PageUp" key 2x times and be on Task 1 with full colspan', () => { + cy.get('[data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); }); @@ -237,7 +237,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -295,7 +295,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -355,6 +355,66 @@ describe('Example 14 - Column Span & Header Grouping', () => { .children() .each(($child, index) => expect($child.text()).to.eq(newHeaderTitles[index])); }); + + it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { + cy.get('#grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-center [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('contain', '5 days') + .and('have.class', 'slick-cell-colspan-crossing-docking'); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part') + .click({ force: true }) + .should('have.class', 'active'); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should(($cell) => { + const style = getComputedStyle($cell[0], '::after'); + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(style.borderLeftStyle).to.eq('none'); + expect(style.borderRightStyle).to.eq('solid'); + }); + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('have.class', 'active') + .and('contain', '5 days') + .should(($cell) => { + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-left [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 .slick-header-columns-left [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + }); }); describe('First Grid - Key Navigation', () => { diff --git a/frameworks/angular-slickgrid/test/cypress/e2e/example14.cy.ts b/frameworks/angular-slickgrid/test/cypress/e2e/example14.cy.ts index 70eae17cc8..b11f44c643 100644 --- a/frameworks/angular-slickgrid/test/cypress/e2e/example14.cy.ts +++ b/frameworks/angular-slickgrid/test/cypress/e2e/example14.cy.ts @@ -169,52 +169,52 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('#grid2 .slick-header.slick-header-left .slick-header-columns .slick-header-column').should('have.length', 7); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and land on a full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key once and be on Task 8 with full colspan', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=8] > .slick-cell.l0.r5.active').should('have.length', 1); }); - it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and land on a colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 1 on Duration colspan 5 days and type "PageDown" key 2x times and be on Task 15 with colspan of 3', () => { + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=15] > .slick-cell.l1.r3.active').should('have.length', 1); }); - it('should navigate PageUp twice from a colspan of 3 back to the starting colspan of 3', () => { - cy.get('#grid1 [data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); + it('should start at Task 15 on Duration colspan 5 days and type "PageUp" key 2x times and be on Task 1 with full colspan', () => { + cy.get('[data-row=15] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" twice and be back to Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 2 on Duration colspan 5 days and type "PageDown" key 2x times and "PageUp" 3x times and be on Task 0 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{pagedown}{pagedown}{pageup}{pageup}{pageup}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=0] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key once and be on Task 2 with full colspan', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}'); - cy.get('#grid1 .slick-cell.l0.r5.active').should('have.length', 1); + cy.get('[data-row=2] > .slick-cell.l0.r5.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times and be on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=3] > .slick-cell.l1.r3.active').should('have.length', 1); }); it('should start at Task 1 on Duration colspan 5 days and type "ArrowDown" key 2x times, then "ArrowUp" key 2x times and be back on Task 1 with colspan of 3', () => { - cy.get('#grid1 [data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); + cy.get('[data-row=1] > .slick-cell.l1.r3').as('active_cell').click(); cy.get('@active_cell').type('{downarrow}{downarrow}{uparrow}{uparrow}'); - cy.get('#grid1 .slick-cell.l1.r3.active').should('have.length', 1); + cy.get('[data-row=1] > .slick-cell.l1.r3.active').should('have.length', 1); }); }); @@ -237,7 +237,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -295,7 +295,7 @@ describe('Example 14 - Column Span & Header Grouping', () => { cy.get('.slick-header-menu .slick-menu-command-list') .should('be.visible') - .children('.slick-menu-item:nth-of-type(3)') + .children('.slick-menu-item:nth-of-type(4)') .children('.slick-menu-content') .should('contain', 'Hide Column') .click(); @@ -355,6 +355,66 @@ describe('Example 14 - Column Span & Header Grouping', () => { .children() .each(($child, index) => expect($child.text()).to.eq(newHeaderTitles[index])); }); + + it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { + cy.get('#grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-center [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('contain', '5 days') + .and('have.class', 'slick-cell-colspan-crossing-docking'); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should('have.length', 1); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part') + .click({ force: true }) + .should('have.class', 'active'); + cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should(($cell) => { + const style = getComputedStyle($cell[0], '::after'); + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(style.borderLeftStyle).to.eq('none'); + expect(style.borderRightStyle).to.eq('solid'); + }); + cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + .should('have.class', 'active') + .and('contain', '5 days') + .should(($cell) => { + expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + }); + + cy.get('#grid1 .slick-header-columns-left [data-id="duration"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 .slick-header-columns-left [data-id="title"]') + .trigger('mouseover') + .children('.slick-header-menu-button') + .invoke('show') + .click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); + cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + }); }); describe('First Grid - Key Navigation', () => { diff --git a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts index cbd61e6bfb..c4c34b38cc 100644 --- a/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts +++ b/packages/common/src/core/__tests__/slickGrid-pinning.spec.ts @@ -141,9 +141,36 @@ describe('SlickGrid unified pinning', () => { expect(fragments[0].getAttribute('aria-hidden')).toBe('true'); expect((grid as any).getCellNode(0, 0)).toBe(host); + grid.getColumns()[1].hidden = true; + expect((grid as any).getColspanSegments(0, columns.length)).toHaveLength(3); + grid.getColumns()[1].hidden = false; + grid.setActiveCell(0, 0); expect(row.querySelectorAll('.slick-cell.active')).toHaveLength(3); expect((grid as any).getCellFromEvent({ target: fragments[0] })).toEqual({ row: 0, cell: 0 }); + + const deferredHost = document.createElement('div'); + row.appendChild(deferredHost); + (grid as any).dockingByColumn.get(0).sticky = true; + (grid as any)._options.rtl = true; + (grid as any).appendColspanFragments( + 0, + 5, + deferredHost, + [ + { start: 1, end: 1, band: 'center' }, + { start: 0, end: 0, band: 'left' }, + ], + true + ); + expect(deferredHost.previousElementSibling?.classList.contains('slick-cell-sticky')).toBe(true); + (grid as any)._options.rtl = false; + + const cacheEntry = (grid as any).rowsCache[0]; + delete cacheEntry.cellNodesByColumnIdx[0]; + delete cacheEntry.cellColSpans[0]; + (grid as any).cleanUpAndRenderCells({ top: 0, bottom: 0, leftPx: 0, rightPx: 320 }); + expect((grid as any).rowsCache[0].cellSpanFragments[0][0].parentElement).toBe(row.querySelector('.slick-scrolling-cells')); }); it('rejects non-sequential pinning when a rendered colspan crosses docking regions', () => { @@ -151,7 +178,7 @@ describe('SlickGrid unified pinning', () => { const spanData = { getLength: () => data.length, getItem: (row: number) => data[row], - getItemMetadata: (row: number) => (row === 0 ? { columns: { 1: { colspan: 3 } } } : undefined), + getItemMetadata: (row: number) => (row === 0 ? { columns: { 0: { colspan: 1 }, 1: { colspan: 3 } } } : undefined), }; container = document.createElement('div'); container.style.width = '800px'; @@ -165,6 +192,8 @@ describe('SlickGrid unified pinning', () => { grid.setColumnPinning('b', 'left'); expect(invalidPinning).toHaveBeenCalledWith(expect.stringContaining('non-sequential order')); expect(grid.getColumns()[1].pinned).toBeFalsy(); + (grid as any)._options.pinning = { columns: { left: ['b'], right: ['d'] } }; + expect((grid as any).validateColumnPinning(undefined, true)).toBe(false); grid.setColumnPinning('c', 'right'); expect(grid.getColumns()[2].pinned).toBeFalsy(); @@ -605,10 +634,11 @@ describe('SlickGrid unified pinning', () => { 0: { rowNode: [dockingRow], cellNodesByColumnIdx: { 0: document.createElement('div'), hasOwnProperty: () => false }, + cellSpanFragments: { 0: [] }, }, }; internals.dockingByColumn = new Map([[0, { band: 'left' }]]); - internals.updateRenderedCellDocking(); + expect(internals.updateRenderedCellDocking()).toBe(false); const planner = internals.formattedDataCachePlanner; expect(planner(slickGrid.getColumns()[0], { excelExportOptions: { exportWithFormatter: true } })).toBeDefined(); From a7e68c47e24068c60844e4007ec21e881507c44a Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Fri, 11 Sep 2026 20:16:35 -0400 Subject: [PATCH 25/48] chore: colspan should spread outside pinning boundary --- .../aurelia/test/cypress/e2e/example14.cy.ts | 64 +++++++------------ demos/react/test/cypress/e2e/example14.cy.ts | 64 +++++++------------ demos/vanilla/src/examples/example03.html | 2 +- demos/vanilla/src/examples/example04.html | 9 ++- demos/vanilla/src/examples/example05.html | 9 ++- demos/vanilla/src/examples/example06.html | 9 ++- demos/vanilla/src/examples/example09.html | 9 ++- demos/vanilla/src/examples/example10.html | 6 +- demos/vanilla/src/examples/example14.html | 8 ++- demos/vanilla/src/examples/example14.ts | 6 ++ demos/vanilla/src/examples/example15.html | 8 ++- demos/vanilla/src/examples/example15.ts | 6 ++ demos/vanilla/src/examples/example17.html | 8 ++- demos/vanilla/src/examples/example19.html | 7 +- demos/vanilla/src/examples/example19.ts | 6 ++ demos/vanilla/src/examples/example22.html | 5 +- demos/vanilla/src/examples/example22.ts | 6 ++ demos/vanilla/src/examples/example23.html | 7 +- demos/vanilla/src/examples/example23.ts | 6 ++ demos/vanilla/src/examples/example26.html | 8 ++- demos/vanilla/src/examples/example26.ts | 6 ++ demos/vanilla/src/examples/example27.html | 8 ++- demos/vanilla/src/examples/example27.ts | 6 ++ demos/vanilla/src/examples/example28.html | 8 ++- demos/vanilla/src/examples/example28.ts | 6 ++ demos/vanilla/src/examples/example29.html | 7 +- demos/vanilla/src/examples/example29.ts | 6 ++ demos/vanilla/src/examples/example30.html | 8 ++- demos/vanilla/src/examples/example30.ts | 6 ++ demos/vanilla/src/examples/example31.html | 7 +- demos/vanilla/src/examples/example31.ts | 6 ++ demos/vanilla/src/examples/example32.html | 7 +- demos/vanilla/src/examples/example32.ts | 6 ++ demos/vanilla/src/examples/example33.html | 7 +- demos/vanilla/src/examples/example33.ts | 6 ++ demos/vanilla/src/examples/example34.html | 7 +- demos/vanilla/src/examples/example34.ts | 6 ++ demos/vanilla/src/examples/example35.html | 7 +- demos/vanilla/src/examples/example35.ts | 6 ++ demos/vanilla/src/examples/example37.html | 8 ++- demos/vanilla/src/examples/example37.ts | 6 ++ demos/vanilla/src/examples/example38.html | 5 +- demos/vanilla/src/examples/example38.ts | 6 ++ demos/vanilla/src/examples/example40.html | 8 ++- demos/vanilla/src/examples/example41.html | 5 +- demos/vanilla/src/examples/example41.ts | 6 ++ demos/vanilla/src/examples/example42.html | 7 +- demos/vanilla/src/examples/example42.ts | 6 ++ demos/vanilla/src/examples/example43.html | 8 ++- demos/vanilla/src/examples/example44.html | 8 ++- demos/vanilla/src/examples/example45.html | 8 ++- demos/vanilla/src/examples/example47.html | 8 ++- demos/vue/test/cypress/e2e/example14.cy.ts | 64 +++++++------------ .../test/cypress/e2e/example14.cy.ts | 64 +++++++------------ .../core/__tests__/slickGrid-pinning.spec.ts | 17 ++++- packages/common/src/core/slickGrid.ts | 17 ++++- packages/common/src/styles/slick-grid.scss | 12 +++- test/cypress/e2e/example08.cy.ts | 9 ++- 58 files changed, 430 insertions(+), 221 deletions(-) diff --git a/demos/aurelia/test/cypress/e2e/example14.cy.ts b/demos/aurelia/test/cypress/e2e/example14.cy.ts index b11f44c643..3355647a9b 100644 --- a/demos/aurelia/test/cypress/e2e/example14.cy.ts +++ b/demos/aurelia/test/cypress/e2e/example14.cy.ts @@ -357,63 +357,47 @@ describe('Example 14 - Column Span & Header Grouping', () => { }); it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { - cy.get('#grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + const setPinning = (headerSelector: string, command: 'pin-left' | 'unpin-column') => { + cy.get(headerSelector).trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get(`.slick-submenu:visible [data-command="${command}"]`).click(); + }; + const hostSelector = '.grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)'; + const fragmentSelector = '.grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part'; + + setPinning('.grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]', 'pin-left'); - cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + cy.get('.grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); }); - cy.get('#grid1 .slick-header-columns-center [data-id="duration"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + setPinning('.grid1 .slick-header-columns-center [data-id="duration"]', 'pin-left'); - cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + cy.get(hostSelector) .should('contain', '5 days') - .and('have.class', 'slick-cell-colspan-crossing-docking'); - cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); - cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should('have.length', 1); - cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part') - .click({ force: true }) - .should('have.class', 'active'); - cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should(($cell) => { + .and('have.class', 'slick-cell-colspan-crossing-docking') + .and('have.class', 'r3') + .and('not.have.class', 'r4'); + cy.get('.grid1 [data-row="1"] .slick-cell.l4.r4').should('have.length', 1); + cy.get('.grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get(fragmentSelector).should('have.length', 1).click({ force: true }).should('have.class', 'active'); + cy.get(fragmentSelector).should(($cell) => { const style = getComputedStyle($cell[0], '::after'); expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); expect(style.borderLeftStyle).to.eq('none'); expect(style.borderRightStyle).to.eq('solid'); }); - cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + cy.get(hostSelector) .should('have.class', 'active') .and('contain', '5 days') .should(($cell) => { expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); - expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('solid'); }); - cy.get('#grid1 .slick-header-columns-left [data-id="duration"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); - cy.get('#grid1 .slick-header-columns-left [data-id="title"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); - cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + setPinning('.grid1 .slick-header-columns-left [data-id="duration"]', 'unpin-column'); + setPinning('.grid1 .slick-header-columns-left [data-id="title"]', 'unpin-column'); + cy.get('.grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); }); }); diff --git a/demos/react/test/cypress/e2e/example14.cy.ts b/demos/react/test/cypress/e2e/example14.cy.ts index b11f44c643..3355647a9b 100644 --- a/demos/react/test/cypress/e2e/example14.cy.ts +++ b/demos/react/test/cypress/e2e/example14.cy.ts @@ -357,63 +357,47 @@ describe('Example 14 - Column Span & Header Grouping', () => { }); it('should keep a Duration colspan visually continuous across a valid docking boundary', () => { - cy.get('#grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + const setPinning = (headerSelector: string, command: 'pin-left' | 'unpin-column') => { + cy.get(headerSelector).trigger('mouseover').children('.slick-header-menu-button').invoke('show').click(); + cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); + cy.get(`.slick-submenu:visible [data-command="${command}"]`).click(); + }; + const hostSelector = '.grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)'; + const fragmentSelector = '.grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part'; + + setPinning('.grid1 .slick-header:not(.slick-preheader-panel) .slick-header-columns [data-id="title"]', 'pin-left'); - cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { + cy.get('.grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l0').should(($cell) => { expect(getComputedStyle($cell[0], '::after').boxShadow).not.to.eq('none'); }); - cy.get('#grid1 .slick-header-columns-center [data-id="duration"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="pin-left"]').click(); + setPinning('.grid1 .slick-header-columns-center [data-id="duration"]', 'pin-left'); - cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + cy.get(hostSelector) .should('contain', '5 days') - .and('have.class', 'slick-cell-colspan-crossing-docking'); - cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); - cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should('have.length', 1); - cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part') - .click({ force: true }) - .should('have.class', 'active'); - cy.get('#grid1 [data-row="1"] .slick-scrolling-cells > .slick-cell-colspan-part').should(($cell) => { + .and('have.class', 'slick-cell-colspan-crossing-docking') + .and('have.class', 'r3') + .and('not.have.class', 'r4'); + cy.get('.grid1 [data-row="1"] .slick-cell.l4.r4').should('have.length', 1); + cy.get('.grid1 [data-row="1"] .slick-cell-colspan-part').should('have.length', 1); + cy.get(fragmentSelector).should('have.length', 1).click({ force: true }).should('have.class', 'active'); + cy.get(fragmentSelector).should(($cell) => { const style = getComputedStyle($cell[0], '::after'); expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); expect(style.borderLeftStyle).to.eq('none'); expect(style.borderRightStyle).to.eq('solid'); }); - cy.get('#grid1 [data-row="1"] .slick-pinned-left-cells > .slick-cell.l1:not(.slick-cell-colspan-part)') + cy.get(hostSelector) .should('have.class', 'active') .and('contain', '5 days') .should(($cell) => { expect(getComputedStyle($cell[0]).boxShadow).to.eq('none'); - expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('none'); + expect(getComputedStyle($cell[0], '::after').borderRightStyle).to.eq('solid'); }); - cy.get('#grid1 .slick-header-columns-left [data-id="duration"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); - cy.get('#grid1 .slick-header-columns-left [data-id="title"]') - .trigger('mouseover') - .children('.slick-header-menu-button') - .invoke('show') - .click(); - cy.get('.slick-header-menu:visible [data-command="pin-column"]').click(); - cy.get('.slick-submenu:visible [data-command="unpin-column"]').click(); - cy.get('#grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); + setPinning('.grid1 .slick-header-columns-left [data-id="duration"]', 'unpin-column'); + setPinning('.grid1 .slick-header-columns-left [data-id="title"]', 'unpin-column'); + cy.get('.grid1 [data-row="1"] .slick-cell-colspan-part').should('not.exist'); }); }); diff --git a/demos/vanilla/src/examples/example03.html b/demos/vanilla/src/examples/example03.html index b088035254..1bffecbedb 100644 --- a/demos/vanilla/src/examples/example03.html +++ b/demos/vanilla/src/examples/example03.html @@ -2,7 +2,7 @@

Example 03 - Draggable Grouping & Aggregators (with Salesforce Theme) - + + + +

-
+
(*) NO DATA SHOWN - just change any of Filters/Sorting/Pages and look at the "GraphQL Query" changing. Also note that the column Name has a filter with a custom %% operator that behaves like an SQL LIKE operator supporting % wildcards. Depending on your configuration, your GraphQL Server diff --git a/demos/vanilla/src/examples/example14.html b/demos/vanilla/src/examples/example14.html index 45046970bc..f8d5def065 100644 --- a/demos/vanilla/src/examples/example14.html +++ b/demos/vanilla/src/examples/example14.html @@ -2,6 +2,12 @@

Example 14 - Columns Resize by Content (with Salesforce Theme) + + + +

-
+

The grid below uses the optional resize by cell content (with a fixed 1000px for demo purposes), you can click on the 2 buttons to diff --git a/demos/vanilla/src/examples/example14.ts b/demos/vanilla/src/examples/example14.ts index 1cbc44ed1c..9757dd6bc5 100644 --- a/demos/vanilla/src/examples/example14.ts +++ b/demos/vanilla/src/examples/example14.ts @@ -104,6 +104,7 @@ export default class Example14 { { value: 3, label: 'Complex' }, { value: 4, label: 'Very Complex' }, ]; + subTitleStyle = 'display: block'; get slickerGridInstance() { return this.sgb?.instances; @@ -1093,4 +1094,9 @@ export default class Example14 { Type: ${item.itemTypeName === 'I' ? 'Item' : item.itemTypeName === 'C' ? 'PdCat' : 'Cat'}

`; } + + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } } diff --git a/demos/vanilla/src/examples/example15.html b/demos/vanilla/src/examples/example15.html index 0303cb9be2..fc36e5bb06 100644 --- a/demos/vanilla/src/examples/example15.html +++ b/demos/vanilla/src/examples/example15.html @@ -1,5 +1,11 @@

Example 15 - Grid with OData Backend Service using RxJS Observables + + + +

-
+
NOTE: For demo purposes, the last column (filter & sort) will always throw an error and its only purpose is to demo what would happen when you encounter a backend server error (the UI should rollback to previous state before you did the action). Also changing Page Size to 50,000 will also throw which again is for demo purposes. diff --git a/demos/vanilla/src/examples/example15.ts b/demos/vanilla/src/examples/example15.ts index a13a7d27d3..ccce216f00 100644 --- a/demos/vanilla/src/examples/example15.ts +++ b/demos/vanilla/src/examples/example15.ts @@ -32,6 +32,7 @@ export default class Example15 { { value: 'male', label: 'male' }, { value: 'female', label: 'female' }, ]; + subTitleStyle = 'display: block'; constructor() { this._bindingEventService = new BindingEventService(); @@ -492,4 +493,9 @@ export default class Example15 { this.sgb?.filterService.clearFilters(); return true; } + + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } } diff --git a/demos/vanilla/src/examples/example17.html b/demos/vanilla/src/examples/example17.html index 363a6777da..9e71c4ed77 100644 --- a/demos/vanilla/src/examples/example17.html +++ b/demos/vanilla/src/examples/example17.html @@ -1,9 +1,11 @@

Example 17 - Auto-Scroll with Range Selector (with Salesforce Theme) - + + +

-
+

Grid - using enableExcelCopyBuffer which uses SlickHybridSelectionModel

The complete first row and the cells C - E of the second row are not allowing to paste values.

diff --git a/demos/vanilla/src/examples/example19.ts b/demos/vanilla/src/examples/example19.ts index d43a1d64de..cb791d26f5 100644 --- a/demos/vanilla/src/examples/example19.ts +++ b/demos/vanilla/src/examples/example19.ts @@ -27,6 +27,7 @@ export default class Example19 { sgb: SlickVanillaGridBundle; isGridEditable = true; enableMultiSelection = false; + subTitleStyle = 'display: block'; attached() { this._eventHandler = new SlickEventHandler(); @@ -273,4 +274,9 @@ export default class Example19 { // remove unsaved css class from that cell this.sgb.slickGrid?.removeCellCssStyles(`unsaved_highlight_${[column.field]}${row}`); } + + toggleSubTitle() { + this.subTitleStyle = this.subTitleStyle === 'display: block' ? 'display: none' : 'display: block'; + this.sgb.resizerService.resizeGrid(); + } } diff --git a/demos/vanilla/src/examples/example22.html b/demos/vanilla/src/examples/example22.html index 208fca6467..eef4118eb4 100644 --- a/demos/vanilla/src/examples/example22.html +++ b/demos/vanilla/src/examples/example22.html @@ -1,6 +1,9 @@

Example 22 - Row Based Editing (with Salesforce Theme) + -

+