Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions cypress/e2e/example-rtl.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
describe('Example - RTL (Right-to-Left) Support', () => {
const titles = [
'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Effort Driven',
'Priority', 'Status', 'Assignee', 'Department', 'Project', 'Completed'
];

beforeEach(() => {
cy.visit(`${Cypress.config('baseUrl')}/examples/example1-simple-rtl.html`);
});

// Section 1: Basic Rendering

describe('Basic Rendering', () => {
it('should display Example title', () => {
cy.get('h2').should('contain', 'Simple Grid (RTL)');
});

it('should have exact Column Titles in the grid', () => {
cy.get('#myGrid')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
});

it('should render columns in right-to-left order', () => {
cy.get('#myGrid')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
});
});

// Section 2: Configuration & Setup

describe('Configuration', () => {
it('should have RTL class applied to grid container', () => {
cy.get('#myGrid')
.should('have.class', 'slick-rtl');
});

it('should have RTL option enabled in grid options', () => {
cy.window().then((win) => {
const grid = (win as any).grid;
if (grid && grid.getOptions) {
const options = grid.getOptions();
expect(options.rtl).to.be.true;
}
});
});

it('should have proper RTL cell content alignment', () => {
cy.get('#myGrid')
.find('.slick-cell:first')
.should('have.css', 'direction', 'rtl');
});
});

// Section 3: UI Interactions

describe('UI Interactions', () => {
it('should have resize handle on the left side', () => {
cy.get('#myGrid')
.find('.slick-header-column:first .slick-resizable-handle')
.should('exist')
.and('have.css', 'left', '0px');
});

it('should maintain RTL behavior after column resize', () => {
// Resize first column
cy.get('#myGrid')
.find('.slick-header-column:first .slick-resizable-handle')
.trigger('mousedown', { which: 1 })
.trigger('mousemove', { clientX: 30 })
.trigger('mouseup');

// Verify columns still in correct RTL order
cy.get('#myGrid')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
});
});

// Section 4: Scrolling Behavior

describe('Scrolling Behavior', () => {
it('should have horizontal scroll enabled', () => {
cy.get('.slick-viewport')
.should('have.prop', 'scrollWidth')
.then((scrollWidth) => {
cy.get('.slick-viewport')
.invoke('width')
.should((viewportWidth) => {
// @ts-ignore - scrollWidth and viewportWidth are numbers
expect(scrollWidth).to.be.greaterThan(viewportWidth);
});
});
});

it('should scroll horizontally in RTL mode', () => {
cy.get('.slick-viewport')
.then(($viewport) => {
const viewport = $viewport[0];
viewport.scrollLeft = -200;
cy.wait(100);
expect(viewport.scrollLeft).to.be.lessThan(0);
});
});

it('should update visible range when scrolling in RTL', () => {
let initialFirstColumn = '';
cy.get('.slick-header-column:visible')
.first()
.invoke('text')
.then((text) => {
initialFirstColumn = text;
});

cy.get('.slick-viewport')
.then(($viewport) => {
const viewport = $viewport[0];
viewport.scrollLeft = -300;
cy.wait(150);
});

cy.get('.slick-header-column:visible')
.first()
.invoke('text')
.should((newText) => {
expect(newText).not.to.equal(initialFirstColumn);
});
});

it('should calculate correct visible range in RTL mode', () => {
cy.window().then((win) => {
const grid = (win as any).grid;
if (grid && grid.getVisibleRange) {
const viewport = grid._viewport;
const originalScrollLeft = viewport.scrollLeft;
viewport.scrollLeft = -200;
const range = grid.getVisibleRange();
expect(range.leftPx).to.be.a('number');
expect(range.rightPx).to.be.a('number');
expect(range.rightPx).to.be.greaterThan(range.leftPx);
viewport.scrollLeft = originalScrollLeft;
}
});
});
});

// Section 5: Edge Cases & Stability

describe('Edge Cases & Stability', () => {
it('should handle max scroll in RTL mode', () => {
cy.get('.slick-viewport')
.then(($viewport) => {
const viewport = $viewport[0];
const maxScroll = viewport.scrollWidth - viewport.clientWidth;
viewport.scrollLeft = -maxScroll;
cy.wait(150);
cy.get('.slick-header-column:visible')
.last()
.should('exist');
});
});

it('should maintain scroll position after column updates', () => {
let currentScrollLeft = 0;
cy.get('.slick-viewport')
.then(($viewport) => {
const viewport = $viewport[0];
viewport.scrollLeft = -300;
currentScrollLeft = viewport.scrollLeft;
cy.wait(100);
cy.window().then((win) => {
const grid = (win as any).grid;
if (grid && grid.render) {
grid.render();
}
});
cy.wait(100);
expect(viewport.scrollLeft).to.equal(currentScrollLeft);
});
});

it('should scroll to the end and display last columns', () => {
cy.get('.slick-viewport')
.then(($viewport) => {
const viewport = $viewport[0];
const maxScroll = viewport.scrollWidth - viewport.clientWidth;
viewport.scrollLeft = -maxScroll;
cy.wait(300);
});

cy.get('.slick-header-column:visible')
.first()
.invoke('text')
.then((text) => {
expect(text).not.to.equal('Title');
});
});
});
});
103 changes: 103 additions & 0 deletions examples/example1-simple-rtl.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<!DOCTYPE HTML>
<html dir="rtl">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" type="image/ico" href="favicon.ico" />
<title>SlickGrid example 1: Basic grid (RTL)</title>
<link rel="stylesheet" href="../dist/styles/css/example-demo.css" type="text/css" />
<link rel="stylesheet" href="../dist/styles/css/slick-alpine-theme.css" type="text/css" />
</head>

<body>
<h2 class="title">Example 1 Simple Grid (RTL) - ESM</h2>
<table width="100%">
<tr>
<td valign="top" width="50%">
<div id="myGrid" class="slick-container" style="width:600px;height:500px;"></div>
</td>
<td valign="top">
<div>
<h2>
<a href="/examples/index.html" style="text-decoration: none; font-size: 22px">&#x2302;</a>
Demonstrates:
</h2>
</div>
<ul>
<li>basic grid with minimal configuration</li>
<li>RTL (right-to-left) support enabled</li>
</ul>
<h2>View Source:</h2>
<ul>
<li><a href="https://github.com/6pac/SlickGrid/blob/master/examples/example1-simple-rtl.html"
target="_sourcewindow"> View the source for this example on Github</a>
</li>
</ul>
</td>
</tr>
</table>

<script src="https://cdn.jsdelivr.net/npm/sortablejs/Sortable.min.js"></script>
<script src="sortable-cdn-fallback.js"></script>

<script src="../dist/browser/slick.core.js"></script>
<script src="../dist/browser/slick.interactions.js"></script>
<script src="../dist/browser/slick.grid.js"></script>

<script>
var grid;
var columns = [
{ id: "title", name: "Title", field: "title", width: 90 },
{ id: "duration", name: "Duration", field: "duration", with: 80 },
{ id: "%", name: "% Complete", field: "percentComplete", width: 90 },
{ id: "start", name: "Start", field: "start", width: 80 },
{ id: "finish", name: "Finish", field: "finish", width: 80 },
{ id: "effort-driven", name: "Effort Driven", field: "effortDriven", width: 100 },
{ id: "priority", name: "Priority", field: "priority", width: 80 },
{ id: "status", name: "Status", field: "status" },
{ id: "assignee", name: "Assignee", field: "assignee" },
{ id: "department", name: "Department", field: "department", width: 100 },
{ id: "project", name: "Project", field: "project" },
{ id: "completed", name: "Completed", field: "completed" }
];

var options = {
enableCellNavigation: true,
enableColumnReorder: false,
rtl: true
};

var data = [];
var statuses = ['Not Started', 'In Progress', 'Review', 'Completed', 'On Hold'];
var priorities = ['Low', 'Medium', 'High', 'Critical'];
var assignees = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve', 'Frank', 'Grace'];
var departments = ['Engineering', 'Marketing', 'Sales', 'Design', 'Support', 'Finance'];
var projects = ['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon', 'Zeta'];

for (var i = 0; i < 500; i++) {
var startDate = new Date(2025, Math.floor(Math.random() * 12), Math.floor(Math.random() * 28) + 1);
var finishDate = new Date(startDate);
finishDate.setDate(finishDate.getDate() + Math.floor(Math.random() * 14) + 3);

data[i] = {
title: "Task " + (i + 1),
duration: (Math.floor(Math.random() * 10) + 2) + " days",
percentComplete: Math.round(Math.random() * 100),
start: startDate.toLocaleDateString('en-US'),
finish: finishDate.toLocaleDateString('en-US'),
effortDriven: (i % 5 == 0),
priority: priorities[Math.floor(Math.random() * priorities.length)],
status: statuses[Math.floor(Math.random() * statuses.length)],
assignee: assignees[Math.floor(Math.random() * assignees.length)],
department: departments[Math.floor(Math.random() * departments.length)],
project: projects[Math.floor(Math.random() * projects.length)],
completed: (i % 3 == 0) ? 'Yes' : 'No'
};
}

grid = new Slick.Grid("#myGrid", data, columns, options);
</script>
</body>

</html>
1 change: 1 addition & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ <h2>Basic Use</h2>
<li><a href="./example7-events.html">Handling events and context menu</a></li>
<li><a href="./example14-highlighting.html">Highlighting and flashing cells</a></li>
<li><a href="./example2-formatters-event.html">Adding some formatting</a></li>
<li><a href="./example1-simple-rtl.html">Basic use with RTL support</a></li>
</ul>
</div>
<h2>Editing</h2>
Expand Down
3 changes: 3 additions & 0 deletions src/models/gridOption.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ export interface GridOption<C extends BaseColumn = BaseColumn> {
/** Defaults to 400, duration to show the row highlight (e.g. after CRUD executions) */
rowHighlightDuration?: number;

/** Defaults to false, sets the grid direction to RTL (Right-to-Left) for proper rendering of RTL languages */
rtl?: boolean;

/**
* Defaults to "top", what CSS style to we want to use to render each row top offset (we can use "top" or "transform").
* For example, with a default `rowHeight: 22`, the 2nd row will have a `top` offset of 44px and by default have a CSS style of `top: 44px`.
Expand Down
Loading
Loading