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
24 changes: 24 additions & 0 deletions demos/aurelia/src/examples/slickgrid/example57.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<h2>
Example 57: RTL (Right-to-Left)
<span class="float-end">
<a
style="font-size: 18px"
target="_blank"
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/aurelia/src/examples/slickgrid/example57.ts"
>
<span class="mdi mdi-link-variant"></span> code
</a>
</span>
</h2>

<div class="subtitle">Basic grid with RTL (Right-to-Left) enabled for RTL languages</div>

<div dir="rtl">
<aurelia-slickgrid
grid-id="grid57"
columns.bind="columns"
options.bind="gridOptions"
dataset.bind="dataset"
asg-on-aurelia-grid-created="aureliaGridReady($event.detail)"
></aurelia-slickgrid>
</div>
78 changes: 78 additions & 0 deletions demos/aurelia/src/examples/slickgrid/example57.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Formatters, type Column, type GridOption } from 'aurelia-slickgrid';

const NB_ITEMS = 100;

export class Example57 {
gridOptions!: GridOption;
columns: Column[] = [];
dataset: any[] = [];
previousBodyDir: string | null = null;

constructor() {
this.defineGrid();
}

attached() {
this.previousBodyDir = document.body.getAttribute('dir');
document.body.setAttribute('dir', 'rtl');
this.dataset = this.mockData(NB_ITEMS);
}

detached() {
if (this.previousBodyDir) {
document.body.setAttribute('dir', this.previousBodyDir);
} else {
document.body.removeAttribute('dir');
}
}

defineGrid() {
this.columns = [
{ id: 'id', name: 'ID', field: 'id', filterable: true, sortable: true, minWidth: 60 },
{ id: 'title', name: 'Title', field: 'title', filterable: true, sortable: true, minWidth: 100 },
{ id: 'duration', name: 'Duration (days)', field: 'duration', filterable: true, sortable: true, minWidth: 100, type: 'number' },
{ id: '%', name: '% Complete', field: 'percentComplete', filterable: true, sortable: true, minWidth: 100, type: 'number' },
{
id: 'start',
name: 'Start',
field: 'start',
formatter: Formatters.dateIso,
exportWithFormatter: true,
filterable: true,
},
{
id: 'finish',
name: 'Finish',
field: 'finish',
formatter: Formatters.dateIso,
exportWithFormatter: true,
filterable: true,
},
{ id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', minWidth: 80 },
];

this.gridOptions = {
enableFiltering: true,
gridHeight: 500,
gridWidth: 700,
rowHeight: 33,
rtl: true, // ← Enable RTL mode
};
}

mockData(count: number) {
const data: any[] = [];
for (let i = 0; i < count; i++) {
data.push({
id: i,
title: `Task ${i}`,
duration: Math.round(Math.random() * 100),
percentComplete: Math.round(Math.random() * 100),
start: new Date(2024, 0, 1 + Math.floor(Math.random() * 30)).toISOString().split('T')[0],
finish: new Date(2024, 1, 1 + Math.floor(Math.random() * 28)).toISOString().split('T')[0],
effortDriven: i % 5 === 0,
});
}
return data;
}
}
1 change: 1 addition & 0 deletions demos/aurelia/src/my-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const myRoutes: Routeable[] = [
{ path: 'example54', component: () => import('./examples/slickgrid/example54.js'), title: '54- AI / Web MCP Toolkit' },
{ path: 'example55', component: () => import('./examples/slickgrid/example55.js'), title: '55- Variable Row Height (provider)' },
{ path: 'example56', component: () => import('./examples/slickgrid/example56.js'), title: '56- Variable Row Height (metadata)' },
{ path: 'example57', component: () => import('./examples/slickgrid/example57.js'), title: '57- RTL (Right-to-Left)' },
{ path: 'home', component: () => import('./home-page.js'), title: 'Home' },
];
@route({
Expand Down
81 changes: 81 additions & 0 deletions demos/aurelia/test/cypress/e2e/example57.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
describe('Example 57 - RTL (Right-to-Left)', () => {
const titles = ['ID', 'Title', 'Duration (days)', '% Complete', 'Start', 'Finish', 'Effort Driven'];

beforeEach(() => {
cy.setCookie('serve-mode', 'cypress');
cy.visit(`${Cypress.config('baseUrl')}/example57`);
});

describe('Basic Rendering', () => {
it('should display Example title', () => {
cy.get('h2').should('contain', 'Example 57: RTL (Right-to-Left)');
});

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

describe('Configuration', () => {
it('should have RTL class applied to grid container', () => {
cy.get('#grid57').then(($grid) => {
const target = $grid.hasClass('slickgrid-container') ? $grid : $grid.find('.slickgrid-container');
cy.wrap(target).should('have.class', 'slick-rtl');
});
});

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

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

it('should maintain RTL column order after resize', () => {
cy.get('#grid57 .slick-header-column:first .slick-resizable-handle')
.trigger('mousedown', { which: 1 })
.then(() => {
cy.get('body').trigger('mousemove', { clientX: 260, clientY: 0 });
cy.get('body').trigger('mouseup');
});

cy.get('#grid57')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
});
});

describe('Scrolling Behavior', () => {
it('should have horizontal scroll enabled', () => {
cy.get('#grid57 .slick-viewport').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) => {
const viewport = $viewport[0] as HTMLElement;
const maxScroll = viewport.scrollWidth - viewport.clientWidth;
viewport.scrollLeft = maxScroll;
if (viewport.scrollLeft === 0) {
viewport.scrollLeft = -maxScroll;
}
});

cy.wait(150);

cy.get('#grid57 .slick-viewport').then(($viewport) => {
const viewport = $viewport[0] as HTMLElement;
expect(Math.abs(viewport.scrollLeft)).to.be.greaterThan(0);
});
});
});
});
1 change: 1 addition & 0 deletions demos/react/src/examples/slickgrid/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ const routes = [
{ path: 'example54', route: '/example54', element: lazy(() => import('./Example54.js')), title: '54- AI / Web MCP Toolkit' },
{ path: 'example55', route: '/example55', element: lazy(() => import('./Example55.js')), title: '55- Variable Row Height (provider)' },
{ path: 'example56', route: '/example56', element: lazy(() => import('./Example56.js')), title: '56- Variable Row Height (metadata)' },
{ path: 'example57', route: '/example57', element: lazy(() => import('./Example57.js')), title: '57- RTL (Right-to-Left)' },
];

export default function Routes() {
Expand Down
104 changes: 104 additions & 0 deletions demos/react/src/examples/slickgrid/Example57.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import React, { useEffect, useState } from 'react';
import { Formatters, SlickgridReact, type Column, type GridOption } from 'slickgrid-react';

const NB_ITEMS = 100;

const Example57: React.FC = () => {
const [gridOptions, setGridOptions] = useState<GridOption | undefined>(undefined);
const [columns, setColumns] = useState<Column[]>([]);
const [dataset, setDataset] = useState<any[]>([]);

useEffect(() => {
const previousBodyDir = document.body.getAttribute('dir');
document.body.setAttribute('dir', 'rtl');

defineGrid();
const mockData = mockDataset();
setDataset(mockData);

return () => {
if (previousBodyDir) {
document.body.setAttribute('dir', previousBodyDir);
} else {
document.body.removeAttribute('dir');
}
};
}, []);

const defineGrid = () => {
const cols: Column[] = [
{ id: 'id', name: 'ID', field: 'id', filterable: true, sortable: true, minWidth: 60 },
{ id: 'title', name: 'Title', field: 'title', filterable: true, sortable: true, minWidth: 100 },
{ id: 'duration', name: 'Duration (days)', field: 'duration', filterable: true, sortable: true, minWidth: 100, type: 'number' },
{ id: '%', name: '% Complete', field: 'percentComplete', filterable: true, sortable: true, minWidth: 100, type: 'number' },
{
id: 'start',
name: 'Start',
field: 'start',
formatter: Formatters.dateIso,
exportWithFormatter: true,
filterable: true,
},
{
id: 'finish',
name: 'Finish',
field: 'finish',
formatter: Formatters.dateIso,
exportWithFormatter: true,
filterable: true,
},
{ id: 'effort-driven', name: 'Effort Driven', field: 'effortDriven', minWidth: 80 },
];
setColumns(cols);

const opts: GridOption = {
enableFiltering: true,
gridHeight: 500,
gridWidth: 700,
rowHeight: 33,
rtl: true, // ← Enable RTL mode
};
setGridOptions(opts);
};

const mockDataset = () => {
const data = [];
for (let i = 0; i < NB_ITEMS; i++) {
data.push({
id: i,
title: `Task ${i}`,
duration: Math.round(Math.random() * 100),
percentComplete: Math.round(Math.random() * 100),
start: new Date(2024, 0, 1 + Math.floor(Math.random() * 30)).toISOString().split('T')[0],
finish: new Date(2024, 1, 1 + Math.floor(Math.random() * 28)).toISOString().split('T')[0],
effortDriven: i % 5 === 0,
});
}
return data;
};

return !gridOptions ? null : (
<div id="demo-container" className="container-fluid">
<h2>
Example 57: RTL (Right-to-Left)
<span className="float-end font18">
see&nbsp;
<a
target="_blank"
href="https://github.com/ghiscoding/slickgrid-universal/blob/master/demos/react/src/examples/slickgrid/Example57.tsx"
>
<span className="mdi mdi-link-variant"></span> code
</a>
</span>
</h2>

<div className="subtitle">Basic grid with RTL (Right-to-Left) enabled for RTL languages.</div>

<div dir="rtl">
<SlickgridReact gridId="grid57" columns={columns} options={gridOptions} dataset={dataset} />
</div>
</div>
);
};

export default Example57;
81 changes: 81 additions & 0 deletions demos/react/test/cypress/e2e/example57.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
describe('Example 57 - RTL (Right-to-Left)', () => {
const titles = ['ID', 'Title', 'Duration (days)', '% Complete', 'Start', 'Finish', 'Effort Driven'];

beforeEach(() => {
cy.setCookie('serve-mode', 'cypress');
cy.visit(`${Cypress.config('baseUrl')}/example57`);
});

describe('Basic Rendering', () => {
it('should display Example title', () => {
cy.get('h2').should('contain', 'Example 57: RTL (Right-to-Left)');
});

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

describe('Configuration', () => {
it('should have RTL class applied to grid container', () => {
cy.get('#grid57').then(($grid) => {
const target = $grid.hasClass('slickgrid-container') ? $grid : $grid.find('.slickgrid-container');
cy.wrap(target).should('have.class', 'slick-rtl');
});
});

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

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

it('should maintain RTL column order after resize', () => {
cy.get('#grid57 .slick-header-column:first .slick-resizable-handle')
.trigger('mousedown', { which: 1 })
.then(() => {
cy.get('body').trigger('mousemove', { clientX: 260, clientY: 0 });
cy.get('body').trigger('mouseup');
});

cy.get('#grid57')
.find('.slick-header-columns')
.children()
.each(($child, index) => expect($child.text()).to.eq(titles[index]));
});
});

describe('Scrolling Behavior', () => {
it('should have horizontal scroll enabled', () => {
cy.get('#grid57 .slick-viewport').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) => {
const viewport = $viewport[0] as HTMLElement;
const maxScroll = viewport.scrollWidth - viewport.clientWidth;
viewport.scrollLeft = maxScroll;
if (viewport.scrollLeft === 0) {
viewport.scrollLeft = -maxScroll;
}
});

cy.wait(150);

cy.get('#grid57 .slick-viewport').then(($viewport) => {
const viewport = $viewport[0] as HTMLElement;
expect(Math.abs(viewport.scrollLeft)).to.be.greaterThan(0);
});
});
});
});
Loading
Loading