From e19703ec25e47f540767218807f6c2bae590cae8 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Mon, 26 Oct 2026 21:46:28 +0530 Subject: [PATCH 1/4] Refactor restoreTextDirection function parameters Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- src/helpers/helpers.rtl.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/helpers/helpers.rtl.ts b/src/helpers/helpers.rtl.ts index 121c6e7f540..6ec78f6f9dd 100644 --- a/src/helpers/helpers.rtl.ts +++ b/src/helpers/helpers.rtl.ts @@ -66,7 +66,10 @@ export function overrideTextDirection(ctx: CanvasRenderingContext2D, direction: } } -export function restoreTextDirection(ctx: CanvasRenderingContext2D, original?: [string, string]) { +export function restoreTextDirection( + ctx: CanvasRenderingContext2D, + original = (ctx as { prevTextDirection?: [string, string] }).prevTextDirection +) { if (original !== undefined) { delete (ctx as { prevTextDirection?: [string, string] }).prevTextDirection; ctx.canvas.style.setProperty('direction', original[0], original[1]); From db4eb5edfa9898dbdbfbb17a7bc95a109572cfa2 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Mon, 26 Oct 2026 21:46:38 +0530 Subject: [PATCH 2/4] Restore saved canvas direction in legend Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- src/plugins/plugin.legend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 6ed99413536..2bf29c5c318 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -434,7 +434,7 @@ export class Legend extends Element { } }); - restoreTextDirection(this.ctx, opts.textDirection); + restoreTextDirection(this.ctx); } /** From 44f232df77cf3794ce5ebc48ffcb4c59caf17260 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Mon, 26 Oct 2026 21:46:50 +0530 Subject: [PATCH 3/4] Restore saved canvas direction in tooltip Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- src/plugins/plugin.tooltip.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/plugin.tooltip.js b/src/plugins/plugin.tooltip.js index b39681ce2ca..493bef95ae5 100644 --- a/src/plugins/plugin.tooltip.js +++ b/src/plugins/plugin.tooltip.js @@ -1084,7 +1084,7 @@ export class Tooltip extends Element { // Footer this.drawFooter(pt, ctx, options); - restoreTextDirection(ctx, options.textDirection); + restoreTextDirection(ctx); ctx.restore(); } From df6aaeda00529d853b0a848057137054db74edd5 Mon Sep 17 00:00:00 2001 From: generaltony007 <191973953+hakarrr@users.noreply.github.com> Date: Mon, 26 Oct 2026 21:47:23 +0530 Subject: [PATCH 4/4] Test canvas direction restoration and plugin isolation Co-authored-by: generaltony007 <144252170+GeneralTony007@users.noreply.github.com> --- test/specs/helpers.rtl.tests.js | 70 +++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 test/specs/helpers.rtl.tests.js diff --git a/test/specs/helpers.rtl.tests.js b/test/specs/helpers.rtl.tests.js new file mode 100644 index 00000000000..b53315965c8 --- /dev/null +++ b/test/specs/helpers.rtl.tests.js @@ -0,0 +1,70 @@ +describe('RTL direction restoration', function() { + ['ltr', 'rtl'].forEach(function(direction) { + ['', 'ltr', 'rtl'].forEach(function(original) { + ['', 'important'].forEach(function(priority) { + it('restores canvas style after legend direction ' + direction + '/' + original + '/' + priority, function() { + const chart = window.acquireChart({type: 'line', data: {labels: ['A'], datasets: [{label: 'Hello, World!', data: [1]}]}, options: {animation: false, responsive: false, plugins: {legend: {textDirection: direction}}}}); + const style = chart.canvas.style; + style.setProperty('direction', original, priority); + const expected = [style.getPropertyValue('direction'), style.getPropertyPriority('direction')]; + chart.draw(); + expect([style.getPropertyValue('direction'), style.getPropertyPriority('direction')]).toEqual(expected); + expect(chart.ctx.prevTextDirection).toBeUndefined(); + }); + }); + }); + }); +}); + +describe('Tooltip direction restoration', function() { + ['ltr', 'rtl'].forEach(function(direction) { + ['', 'ltr', 'rtl'].forEach(function(original) { + ['', 'important'].forEach(function(priority) { + it('restores canvas after tooltip direction ' + direction + '/' + original + '/' + priority, function() { + const chart = window.acquireChart({type: 'line', data: {labels: ['A'], datasets: [{label: 'Hello, World!', data: [1]}]}, options: {animation: false, responsive: false, plugins: {legend: false, tooltip: {textDirection: direction}}}}); + chart.tooltip.setActiveElements([{datasetIndex: 0, index: 0}], {x: 100, y: 100}); + const style = chart.canvas.style; + style.setProperty('direction', original, priority); + const expected = [style.getPropertyValue('direction'), style.getPropertyPriority('direction')]; + chart.draw(); + expect([style.getPropertyValue('direction'), style.getPropertyPriority('direction')]).toEqual(expected); + expect(chart.ctx.prevTextDirection).toBeUndefined(); + }); + }); + }); + }); +}); + +describe('Direction helper compatibility', function() { + it('retains an explicitly supplied restoration tuple', function() { + const ctx = document.createElement('canvas').getContext('2d'); + Chart.helpers.overrideTextDirection(ctx, 'rtl'); + Chart.helpers.restoreTextDirection(ctx, ['ltr', 'important']); + expect(ctx.canvas.style.getPropertyValue('direction')).toBe('ltr'); + expect(ctx.canvas.style.getPropertyPriority('direction')).toBe('important'); + expect(ctx.prevTextDirection).toBeUndefined(); + }); + + it('does not change style when no override was saved', function() { + const ctx = document.createElement('canvas').getContext('2d'); + ctx.canvas.style.setProperty('direction', 'rtl'); + Chart.helpers.restoreTextDirection(ctx); + expect(ctx.canvas.style.getPropertyValue('direction')).toBe('rtl'); + }); +}); + +describe('Legend and tooltip direction isolation', function() { + it('does not leak the legend override into a tooltip using the canvas default', function() { + const chart = window.acquireChart({type: 'line', data: {labels: ['A'], datasets: [{label: 'Hello, World!', data: [1]}]}, options: {animation: false, responsive: false, plugins: {legend: {textDirection: 'rtl'}}}}); + chart.canvas.style.setProperty('direction', 'ltr'); + chart.tooltip.setActiveElements([{datasetIndex: 0, index: 0}], {x: 100, y: 100}); + const drawBody = chart.tooltip.drawBody; + let direction; + chart.tooltip.drawBody = function() { + direction = getComputedStyle(chart.canvas).direction; + return drawBody.apply(this, arguments); + }; + chart.draw(); + expect(direction).toBe('ltr'); + }); +});