@@ -22,6 +22,8 @@ import { ThemableMixin } from "@vaadin/vaadin-themable-mixin/vaadin-themable-mix
2222import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js' ;
2323import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js' ;
2424import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js' ;
25+ import '@vaadin/popover' ;
26+ import '@vaadin/vertical-layout' ;
2527
2628/**
2729 * A Web Component based on LitElement for displaying breadcrumbs.
@@ -31,6 +33,7 @@ import { ResizeMixin } from '@vaadin/component-base/src/resize-mixin.js';
3133 * - Uses `ResizeMixin` to dynamically update visibility based on available space.
3234 * - The first breadcrumb always remains visible and does not shrink.
3335 * - Implements accessibility attributes to improved usability.
36+ * - Uses a `vaadin-popover` to display hidden breadcrumbs when the ellipsis is clicked.
3437 * - Themeable via Vaadin's ThemableMixin.
3538 *
3639 * Example usage:
@@ -62,18 +65,18 @@ export class VcfBreadcrumbs extends ResizeMixin(ElementMixin(ThemableMixin(Polyl
6265 }
6366
6467 static get version ( ) {
65- return '2.0.1 ' ;
68+ return '2.1.0 ' ;
6669 }
6770
6871 static get styles ( ) {
6972 return css `
7073 :host {
7174 display: block;
72- }
75+ }
7376 ` ;
7477 }
7578
76- /**
79+ /**
7780 * Implement callback from `ResizeMixin` to update the vcf-breadcrumb elements visibility.
7881 *
7982 * @protected
@@ -87,12 +90,13 @@ export class VcfBreadcrumbs extends ResizeMixin(ElementMixin(ThemableMixin(Polyl
8790 * Updates the visibility of breadcrumbs based on available space.
8891 *
8992 * - If all breadcrumbs have enough space, they are fully visible with no shrinking.
90- * - If some breadcrumbs have the "collapse" attribute and space is limited :
93+ * - If space is limited and some breadcrumbs have the "collapse" attribute:
9194 * - Consecutive collapsed items are grouped into ranges.
92- * - Each range is hidden when necessary and replaced with an ellipsis element.
93- * - If space allows, previously hidden items are restored and ellipses are removed.
95+ * - These ranges are hidden when necessary and replaced with an ellipsis element.
96+ * - The ellipsis element serves as an interactive control, revealing hidden breadcrumbs in a popover.
97+ * - If more space becomes available, hidden items are restored, and unnecessary ellipses are removed.
9498 * - The first breadcrumb remains fully visible and does not shrink.
95- */
99+ */
96100 _updateBreadcrumbs ( ) {
97101 // Get all breadcrumbs elements
98102 const breadcrumbs = Array . from ( this . children ) as HTMLElement [ ] ;
@@ -136,22 +140,26 @@ export class VcfBreadcrumbs extends ResizeMixin(ElementMixin(ThemableMixin(Polyl
136140 if ( totalWidth > containerWidth ) {
137141 collapseRanges . forEach ( ( { start } ) => {
138142 const collapseItem = breadcrumbs [ start ] ;
143+
144+ // save the collapsed items
145+ let hiddenItems = [ ] ;
139146
140147 // Hide collapsed items within this range
141148 for ( let i = start ; i <= collapseRanges . find ( r => r . start === start ) ?. end ! ; i ++ ) {
142149 breadcrumbs [ i ] . style . display = 'none' ;
150+ hiddenItems . push ( breadcrumbs [ i ] ) ;
143151 }
144152
145153 // Insert an ellipsis element if it doesn't already exist
146154 if ( collapseItem . previousElementSibling ?. getAttribute ( "part" ) != "ellipsis" ) {
147- let ellipsis = this . _createEllipsisBreadcrumb ( ) ;
155+ let ellipsis = this . _createEllipsisBreadcrumb ( hiddenItems ) ;
148156 collapseItem . insertAdjacentElement ( "beforebegin" , ellipsis ) ;
149- }
157+ }
150158 } ) ;
151159 }
152160 }
153161
154- /**
162+ /**
155163 * Finds ranges of consecutive elements that have the "collapse" attribute.
156164 */
157165 _findCollapseRanges ( breadcrumbs : HTMLElement [ ] ) {
@@ -179,21 +187,68 @@ export class VcfBreadcrumbs extends ResizeMixin(ElementMixin(ThemableMixin(Polyl
179187 /**
180188 * Creates an ellipsis breadcrumb element to represent hidden items.
181189 *
182- * - The element is a `<vcf-breadcrumb>` with an "ellipsis" part.
183- * - It displays as "…" to indicate collapsed breadcrumbs.
184- * - It does not shrink and maintains minimal width to avoid layout shifts.
185- * - This element is inserted dynamically when space constraints require hiding breadcrumbs.
190+ * - The element is a `<vcf-breadcrumb>` with a unique ID and "ellipsis" part.
191+ * - It displays "…" to indicate collapsed breadcrumbs.
192+ * - It does not shrink and maintains minimal width to prevent layout shifts.
193+ * - A `vaadin-popover` is attached to display the hidden breadcrumbs as a vertical list.
194+ * - Clicking an item inside the popover closes the popover.
195+ * - The ellipsis is dynamically inserted and removed as needed based on available space.
186196 *
187- * @returns {HTMLElement } An ellipsis breadcrumb element.
197+ * @param {HTMLElement[] } hiddenItems - The list of breadcrumbs that will be hidden and represented by the ellipsis
198+ * @returns {HTMLElement } An ellipsis breadcrumb element with an associated popover
188199 */
189- _createEllipsisBreadcrumb ( ) {
200+ _createEllipsisBreadcrumb ( hiddenItems : HTMLElement [ ] ) {
190201 let ellipsis = document . createElement ( "vcf-breadcrumb" ) ;
202+ const id = "ellipsis-" + crypto . randomUUID ( ) ;
203+ ellipsis . setAttribute ( "id" , id ) ;
191204 ellipsis . setAttribute ( "part" , "ellipsis" ) ;
205+ ellipsis . setAttribute ( "aria-label" , "Hidden breadcrumbs" ) ;
192206 ellipsis . innerText = "…" ;
193207 // Make sure the ellipsis is visible and positioned correctly
194208 ellipsis . style . display = 'inline-block' ;
195209 ellipsis . style . flexShrink = '0' ;
196210 ellipsis . style . minWidth = '0' ;
211+
212+ // Create a popover to show the hidden breadcumbs and add it to the ellipsis element
213+ let popover = document . createElement ( "vaadin-popover" ) ;
214+ popover . setAttribute ( "for" , id ) ;
215+ popover . setAttribute ( "overlay-role" , "menu" ) ;
216+ popover . setAttribute ( 'accessible-name-ref' , "hidden breadcrumbs" ) ;
217+ popover . setAttribute ( "theme" , "hidden-breadcrumbs" ) ;
218+ popover . setAttribute ( "position" , "bottom-start" ) ;
219+ popover . setAttribute ( "modal" , "true" ) ;
220+
221+ popover . renderer = ( root ) => {
222+ // Ensure content is only added once
223+ if ( ! root . firstChild ) {
224+ const verticalLayout = document . createElement ( 'vaadin-vertical-layout' ) ;
225+ verticalLayout . classList . add ( 'hidden-breadcrumbs-layout' ) ;
226+
227+ // create new anchor elements for the hidden items and add them to the vertical layout
228+ hiddenItems . forEach ( ( element ) => {
229+ const item = document . createElement ( 'a' ) ;
230+ item . textContent = element . textContent ;
231+ item . setAttribute ( "href" , element . getAttribute ( 'href' ) ?? '' ) ;
232+ item . setAttribute ( "role" , "menuitem" ) ;
233+ item . classList . add ( ) ;
234+ // Copy element class list
235+ const elementClasses = Array . from ( element . classList ) ;
236+ item . classList . add ( ...elementClasses ) ;
237+ item . classList . add ( "hidden-breadcrumb-anchor" ) ;
238+
239+ // Add click event to close popover when clicking an item
240+ item . addEventListener ( "click" , ( ) => {
241+ popover . opened = false ;
242+ } ) ;
243+
244+ verticalLayout . appendChild ( item ) ;
245+ } ) ;
246+ root . appendChild ( verticalLayout ) ;
247+ }
248+ } ;
249+
250+ // append popover to ellipsis to move it later to the anchor within the container
251+ ellipsis . appendChild ( popover ) ;
197252 return ellipsis ;
198253 }
199254
0 commit comments