@@ -18,7 +18,6 @@ import {_alignStartEnd, _textX, _toLeftRightCenter} from '../helpers/helpers.ext
1818import { toTRBLCorners } from '../helpers/helpers.options.js' ;
1919
2020
21- // @ts -ignore
2221
2322/**
2423 * @typedef { import('../types/index.js').ChartEvent } ChartEvent
@@ -122,13 +121,24 @@ export class Legend extends Element {
122121
123122 this . legendItems = legendItems ;
124123 }
124+ fit ( ) {
125+ const { options, ctx } = this ;
126+ if ( ! options . display ) {
127+ this . width = this . height = 0 ;
128+ return ;
129+ }
125130fit ( ) {
126131 const { options, ctx } = this ;
127132 if ( ! options . display ) {
128133 this . width = this . height = 0 ;
129134 return ;
130135 }
131136
137+ const labelOpts = options . labels ;
138+ const labelFont = toFont ( labelOpts . font ) ;
139+ const fontSize = labelFont . size ;
140+ const titleHeight = this . _computeTitleHeight ( ) ;
141+ const { boxWidth, itemHeight } = getBoxSize ( labelOpts , fontSize ) ;
132142 const labelOpts = options . labels ;
133143 const labelFont = toFont ( labelOpts . font ) ;
134144 const fontSize = labelFont . size ;
@@ -137,7 +147,27 @@ fit() {
137147
138148 let width , height ;
139149 ctx . font = labelFont . string ;
150+ let width , height ;
151+ ctx . font = labelFont . string ;
152+
153+ if ( this . isHorizontal ( ) ) {
154+ width = this . maxWidth ;
155+ height = this . _fitRows ( titleHeight , fontSize , boxWidth , itemHeight ) + 10 ;
156+ } else {
157+ height = this . maxHeight ;
158+ width = this . _fitCols ( titleHeight , labelFont , boxWidth , itemHeight ) + 10 ;
159+ }
160+
161+ // --- handle scroll height limit ---
162+ const scrollOpts = options . scroll || { } ;
163+ if ( scrollOpts . enabled && scrollOpts . maxItems ) {
164+ const singleItemHeight = itemHeight + labelOpts . padding ;
165+ const visibleHeight = singleItemHeight * scrollOpts . maxItems + titleHeight + labelOpts . padding * 2 ;
166+ this . height = Math . min ( this . height , visibleHeight ) ;
140167
168+ // wrap legend in scroll container
169+ this . _wrapLegendScroll ( visibleHeight ) ;
170+ }
141171 if ( this . isHorizontal ( ) ) {
142172 width = this . maxWidth ;
143173 height = this . _fitRows ( titleHeight , fontSize , boxWidth , itemHeight ) + 10 ;
@@ -161,6 +191,39 @@ fit() {
161191 this . height = Math . min ( height , options . maxHeight || this . maxHeight ) ;
162192}
163193
194+ /**
195+ * Private helper to wrap the <ul> legend in a scrollable container
196+ */
197+ _wrapLegendScroll ( visibleHeight ) {
198+ const canvasContainer = this . chart . canvas . parentNode ;
199+ if ( ! canvasContainer ) return ;
200+
201+ const legendUL = canvasContainer . querySelector ( 'ul.chartjs-legend' ) ;
202+ if ( ! legendUL ) return ;
203+
204+ // Skip if already wrapped
205+ if ( canvasContainer . querySelector ( '.legend-scroll-wrapper' ) ) return ;
206+
207+ const legendWrapper = document . createElement ( 'div' ) ;
208+ legendWrapper . className = 'legend-scroll-wrapper' ;
209+ legendWrapper . style . overflowY = 'auto' ;
210+ legendWrapper . style . maxHeight = `${ visibleHeight } px` ;
211+ legendWrapper . style . display = 'inline-block' ;
212+
213+ // Force single-column layout
214+ legendUL . style . display = 'flex' ;
215+ legendUL . style . flexDirection = 'column' ;
216+ legendUL . style . margin = '0' ;
217+ legendUL . style . padding = '0' ;
218+
219+ legendUL . parentNode . insertBefore ( legendWrapper , legendUL ) ;
220+ legendWrapper . appendChild ( legendUL ) ;
221+ }
222+
223+ this . width = Math . min ( width , options . maxWidth || this . maxWidth ) ;
224+ this . height = Math . min ( height , options . maxHeight || this . maxHeight ) ;
225+ }
226+
164227/**
165228 * Private helper to wrap the <ul> legend in a scrollable container
166229 */
@@ -232,6 +295,13 @@ _wrapLegendScroll(visibleHeight) {
232295 const columnSizes = this . columnSizes = [ ] ;
233296
234297 const heightLimit = maxHeight - titleHeight ;
298+ _fitCols ( titleHeight , labelFont , boxWidth , _itemHeight ) {
299+ const { ctx, maxHeight, options : { labels : { padding} } } = this ;
300+ const scrollOpts = this . options . scroll || { } ;
301+ const hitboxes = this . legendHitBoxes = [ ] ;
302+ const columnSizes = this . columnSizes = [ ] ;
303+
304+ const heightLimit = maxHeight - titleHeight ;
235305
236306 // --- SCROLL ENABLED: force single column ---
237307 if ( scrollOpts . enabled && scrollOpts . maxItems ) {
@@ -258,10 +328,44 @@ _wrapLegendScroll(visibleHeight) {
258328 let currentColHeight = 0 ;
259329 let left = 0 ;
260330 let col = 0 ;
331+ // --- SCROLL ENABLED: force single column ---
332+ if ( scrollOpts . enabled && scrollOpts . maxItems ) {
333+ let totalWidth = 0 ;
334+ let top = 0 ;
335+ let maxItemWidth = 0 ;
336+
337+ this . legendItems . forEach ( ( legendItem , i ) => {
338+ const { itemWidth, itemHeight} = calculateItemSize ( boxWidth , labelFont , ctx , legendItem , _itemHeight ) ;
339+ hitboxes [ i ] = { left : 0 , top, col : 0 , width : itemWidth , height : itemHeight } ;
340+ top += itemHeight + padding ;
341+ maxItemWidth = Math . max ( maxItemWidth , itemWidth ) ;
342+ } ) ;
343+
344+ columnSizes . push ( { width : maxItemWidth , height : top } ) ;
345+ totalWidth = maxItemWidth + 2 * padding ;
346+
347+ return totalWidth ;
348+ }
261349
350+ // --- SCROLL DISABLED: original multi-column logic ---
351+ let totalWidth = padding ;
352+ let currentColWidth = 0 ;
353+ let currentColHeight = 0 ;
354+ let left = 0 ;
355+ let col = 0 ;
356+
357+ this . legendItems . forEach ( ( legendItem , i ) => {
358+ const { itemWidth, itemHeight} = calculateItemSize ( boxWidth , labelFont , ctx , legendItem , _itemHeight ) ;
262359 this . legendItems . forEach ( ( legendItem , i ) => {
263360 const { itemWidth, itemHeight} = calculateItemSize ( boxWidth , labelFont , ctx , legendItem , _itemHeight ) ;
264361
362+ if ( i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit ) {
363+ totalWidth += currentColWidth + padding ;
364+ columnSizes . push ( { width : currentColWidth , height : currentColHeight } ) ;
365+ left += currentColWidth + padding ;
366+ col ++ ;
367+ currentColWidth = currentColHeight = 0 ;
368+ }
265369 if ( i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit ) {
266370 totalWidth += currentColWidth + padding ;
267371 columnSizes . push ( { width : currentColWidth , height : currentColHeight } ) ;
@@ -270,15 +374,23 @@ _wrapLegendScroll(visibleHeight) {
270374 currentColWidth = currentColHeight = 0 ;
271375 }
272376
377+ hitboxes [ i ] = { left, top : currentColHeight , col, width : itemWidth , height : itemHeight } ;
273378 hitboxes [ i ] = { left, top : currentColHeight , col, width : itemWidth , height : itemHeight } ;
274379
275380 currentColWidth = Math . max ( currentColWidth , itemWidth ) ;
276381 currentColHeight += itemHeight + padding ;
277382 } ) ;
383+ currentColWidth = Math . max ( currentColWidth , itemWidth ) ;
384+ currentColHeight += itemHeight + padding ;
385+ } ) ;
278386
387+ totalWidth += currentColWidth ;
388+ columnSizes . push ( { width : currentColWidth , height : currentColHeight } ) ;
279389 totalWidth += currentColWidth ;
280390 columnSizes . push ( { width : currentColWidth , height : currentColHeight } ) ;
281391
392+ return totalWidth ;
393+ }
282394 return totalWidth ;
283395}
284396 adjustHitBoxes ( ) {
@@ -447,6 +559,12 @@ _wrapLegendScroll(visibleHeight) {
447559 const lineHeight = itemHeight + padding ;
448560 const scrollOpts = this . options . scroll || { } ;
449561 let legendItems = this . legendItems ;
562+ if ( scrollOpts . enabled && scrollOpts . maxItems ) {
563+ legendItems = legendItems . slice ( 0 , scrollOpts . maxItems ) ;
564+ }
565+ legendItems . forEach ( ( legendItem , i ) => {
566+ const scrollOpts = this . options . scroll || { } ;
567+ let legendItems = this . legendItems ;
450568 if ( scrollOpts . enabled && scrollOpts . maxItems ) {
451569 legendItems = legendItems . slice ( 0 , scrollOpts . maxItems ) ;
452570 }
@@ -620,6 +738,15 @@ _wrapLegendScroll(visibleHeight) {
620738
621739
622740
741+
742+
743+
744+
745+
746+
747+
748+
749+
623750function calculateItemSize ( boxWidth , labelFont , ctx , legendItem , _itemHeight ) {
624751 const itemWidth = calculateItemWidth ( legendItem , boxWidth , labelFont , ctx ) ;
625752 const itemHeight = calculateItemHeight ( _itemHeight , legendItem , labelFont . lineHeight ) ;
@@ -705,6 +832,20 @@ export default {
705832 console . log ( 'Legend wrapper created inside afterUpdate!' ) ;
706833 }
707834 }
835+ }
836+ const scrollOpts = legend . options . scroll || { } ;
837+ if ( scrollOpts . enabled && scrollOpts . maxItems ) {
838+ const canvasContainer = chart . canvas . parentNode ;
839+ if ( canvasContainer ) {
840+ const legendUL = canvasContainer . querySelector ( 'ul.chartjs-legend' ) ;
841+ if ( legendUL && ! canvasContainer . querySelector ( '.legend-scroll-wrapper' ) ) {
842+ const singleItemHeight = legend . legendHitBoxes [ 0 ] . height + legend . options . labels . padding ;
843+ const titleHeight = legend . _computeTitleHeight ( ) ;
844+ const visibleHeight = singleItemHeight * scrollOpts . maxItems + titleHeight + legend . options . labels . padding * 2 ;
845+ legend . _wrapLegendScroll ( visibleHeight ) ;
846+ console . log ( 'Legend wrapper created inside afterUpdate!' ) ;
847+ }
848+ }
708849 }
709850 } ,
710851
0 commit comments