lifecycle-timeline 1.2.4 → 1.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -78,6 +78,9 @@ new Timeline('timeline-root', data, { visibleCount: 3 });
78
78
  | `locale` | `string` | `auto` | UI language (`'en'`, `'fr'`). |
79
79
  | `i18n` | `object` | `{}` | Custom translations or new languages. |
80
80
  | `showTable` | `boolean` | `true` | Shows/hides the summary data table. |
81
+ | `showLegend` | `boolean` | `true` | Shows/hides the legend below the timeline. |
82
+ | `filterVersions` | `boolean` | `true` | Shows/hides the version filter input. |
83
+ | `splitSupport` | `boolean` | `false` | If `true`, Ent. starts after OSS end. If `false`, overlaps. |
81
84
 
82
85
  ### 🌐 Internationalization (i18n)
83
86
 
package/dist/timeline.css CHANGED
@@ -1 +1 @@
1
- :root{--bg-page: #f9fafb;--bg-card: #ffffff;--bg-track: #e5e7eb;--text-primary: #1f2937;--text-secondary: #6b7280;--text-inverse: #ffffff;--border-color: #e5e7eb;--line-color: #e5e7eb;--grid-line: #9ca3af;--accent-oss: #99e67d;--accent-ent: #ffe88e;--current-date: #086dc3;--font-family: "Inter", system-ui, -apple-system, sans-serif;--tooltip-bg: #ffffff;--tooltip-shadow: rgba(0, 0, 0, .1)}[data-theme=dark]{--bg-page: #111827;--bg-card: #1f2937;--bg-track: #374151;--text-primary: #f9fafb;--text-secondary: #9ca3af;--border-color: #374151;--line-color: #374151;--grid-line: #6b7280;--tooltip-bg: #1f2937;--tooltip-shadow: rgba(0, 0, 0, .5);--accent-oss: #78bd5d;--accent-ent: #e3c456}body{margin:0;padding:0;background-color:var(--bg-page);font-family:var(--font-family);color:var(--text-primary);display:flex;justify-content:center;align-items:center;min-height:100vh;transition:background-color .3s ease,color .3s ease}.page-container{width:100%;max-width:1200px;padding:2rem;background:var(--bg-card);border-radius:12px;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f;transition:background-color .3s ease}.timeline-wrapper{position:relative;padding-top:40px;padding-bottom:60px;overflow-x:auto;scrollbar-width:thin}.timeline-header{display:flex;position:sticky;left:0;margin-bottom:20px}.timeline-tracks{position:relative;padding-top:20px}.timeline-axis{display:flex;border-bottom:1px solid var(--border-color);margin-bottom:10px;padding-bottom:8px;position:relative}.timeline-year{flex:1;font-size:.875rem;font-weight:600;color:var(--text-secondary);border-left:1px dashed var(--border-color);padding-left:0;text-align:center;box-sizing:border-box}.timeline-year:first-child{border-left:none}.timeline-row{display:flex;align-items:center;margin-bottom:1.5rem;position:relative;height:36px;z-index:1;opacity:0;transform:translateY(10px);transition:opacity .4s ease,transform .4s ease}.timeline-row.row-visible{opacity:1;transform:translateY(0)}.version-label{width:80px;min-width:80px;box-sizing:border-box;padding:4px;border-radius:12px;flex-shrink:0;font-weight:600;font-size:.85rem;color:var(--text-primary);margin-right:16px;text-align:center;background-color:transparent;cursor:pointer;transition:all .3s ease;position:sticky;left:0;z-index:10}.version-label:hover{background-color:var(--bg-track);transform:scale(1.05);box-shadow:0 4px 12px #0000001a}.version-label a.version-link{text-decoration:none;color:inherit;display:block;width:100%;height:100%}.version-label a.version-link:hover{text-decoration:underline;opacity:.8}.status-oss{background-color:var(--accent-oss);color:#000}.status-ent{background-color:var(--accent-ent);color:#000}.status-expired{background-color:var(--bg-track);color:var(--text-secondary)}.track-container{flex-grow:1;position:relative;height:100%;background:var(--bg-track);border-radius:0;overflow:hidden;transition:background-color .3s ease}.bar-segment{position:absolute;height:100%;top:0;transition:opacity .3s ease;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.75rem;font-weight:500;overflow:hidden;white-space:nowrap;box-shadow:0 1px 2px #0000001a}.bar-oss{background:linear-gradient(to bottom,#dcfce7 0%,var(--accent-oss) 100%);z-index:2;border-radius:0;height:66%;top:auto;bottom:0}[data-theme=dark] .bar-oss{background:linear-gradient(to bottom,#a7f3d0 0%,var(--accent-oss) 100%)}.bar-ent{background:linear-gradient(to bottom,#fef9c3 0%,var(--accent-ent) 100%);z-index:1;border-radius:0;opacity:.9}.grid-lines-container{position:absolute;inset:0 0 0 96px;pointer-events:none;z-index:5}.indicators-container{position:absolute;inset:0 0 0 96px;pointer-events:none;z-index:20}.current-date-indicator{position:absolute;top:-30px;bottom:0;width:3px;background-image:linear-gradient(to bottom,var(--current-date) 60%,transparent 60%);background-size:3px 20px;background-repeat:repeat-y;border-left:none;z-index:20;pointer-events:none}@keyframes pulse-blue{0%{box-shadow:0 0 #086dc3b3}70%{box-shadow:0 0 0 6px #086dc300}to{box-shadow:0 0 #086dc300}}.current-date-badge{position:absolute;top:100%;margin-top:8px;left:50%;transform:translate(-50%);background-color:var(--current-date);color:#fff;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;white-space:nowrap;box-shadow:0 2px 4px #086dc333;z-index:25;animation:pulse-blue 2s infinite}.year-grid-line{position:absolute;top:0;bottom:0;border-left:2px solid var(--grid-line);opacity:1;z-index:5;pointer-events:none}.timeline-tooltip-overlay{position:absolute;background:var(--tooltip-bg);color:var(--text-primary);padding:10px 14px;border-radius:8px;font-size:.85rem;box-shadow:0 10px 15px -3px var(--tooltip-shadow),0 4px 6px -4px var(--tooltip-shadow);border:1px solid var(--border-color);pointer-events:none;z-index:10000;transition:opacity .15s ease-out;line-height:1.4}.tooltip-header{font-weight:700;margin-bottom:4px;color:var(--current-date);text-transform:uppercase;font-size:.75rem;letter-spacing:.05em}.tooltip-date strong{color:var(--text-secondary)}.timeline-row{transition:opacity .3s ease,transform .3s ease,height .3s ease}.timeline-row[style*="display: none"]{opacity:0;transform:translateY(10px)}.bar-segment{transition:transform .2s ease,box-shadow .2s ease}.bar-segment:hover{box-shadow:0 4px 12px #0000001a}.bar-segment:focus-visible{outline:2px solid var(--text-primary);outline-offset:-2px;z-index:100}.track-container{transition:background-color .3s ease}.release-legend{margin:40px auto 0;padding:24px;background-color:var(--bg-card);border:1px solid var(--border-color);border-radius:12px;display:flex;flex-direction:column;gap:16px;max-width:800px}.legend-block{display:flex;align-items:flex-start;gap:12px}.legend-icon{width:20px;height:20px;border-radius:4px;flex-shrink:0;margin-top:2px}.legend-block h3{margin:0 0 4px;font-size:1rem;font-weight:700;color:var(--text-primary)}.legend-block p{margin:0;font-size:.9rem;color:var(--text-secondary);line-height:1.5}.legend-block.oss .legend-icon{background-color:var(--accent-oss)}.legend-block.commercial .legend-icon{background-color:var(--accent-ent)}.legend-block.eol .legend-icon{background-color:var(--bg-track)}.timeline-toolbar{display:flex;justify-content:flex-start;align-items:center;margin-bottom:24px;gap:16px}.timeline-filter-container{display:flex;align-items:center;gap:12px;background:var(--bg-card);border:1px solid var(--border-color);padding:8px 16px;border-radius:24px;max-width:400px;flex-grow:1;transition:all .3s ease;box-shadow:0 2px 4px #0000000d}.timeline-filter-container:focus-within{border-color:var(--current-date);box-shadow:0 8px 20px #086dc31a}.timeline-filter-input{border:none;outline:none;font-size:.95rem;color:var(--text-primary);background:transparent;width:100%}.highlight-match{background-color:#fde68a;color:#92400e;padding:0 2px;border-radius:2px;font-weight:700}.release-legend{display:flex;flex-wrap:wrap;gap:1.5rem;margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--border-color)}.legend-item-reactive{cursor:pointer;transition:all .2s ease;padding:8px;border-radius:8px;border:1px solid transparent}.legend-item-reactive:hover{background-color:var(--bg-track);transform:translateY(-2px)}.legend-item-reactive.active-highlight{background-color:var(--bg-track);border-color:var(--grid-line);box-shadow:0 2px 4px #0000001a}.bar-segment,.version-label{transition:opacity .3s ease,filter .3s ease}[class*=highlight-oss] .bar-segment:not(.segment-oss),[class*=highlight-oss] .version-label:not(.status-oss){opacity:.3;filter:grayscale(.5)}[class*=highlight-ent] .bar-segment:not(.segment-ent),[class*=highlight-ent] .version-label:not(.status-ent){opacity:.3;filter:grayscale(.5)}[class*=highlight-eol] .bar-segment,[class*=highlight-eol] .version-label:not(.status-expired){opacity:.3;filter:grayscale(.5)}.timeline-toggle-btn{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border-color);padding:8px 16px;border-radius:20px;cursor:pointer;font-size:.875rem;font-weight:500;color:var(--text-primary);box-shadow:0 1px 2px #0000000d;transition:all .2s ease}.timeline-toggle-btn:hover{background:var(--bg-page);border-color:var(--text-secondary);transform:translateY(-1px)}.timeline-toggle-btn:focus-visible{outline:2px solid var(--current-date);outline-offset:2px}.theme-toggle-btn{position:absolute;top:0;right:0;background:var(--bg-card);border:1px solid var(--border-color);color:var(--text-primary);width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s ease;z-index:1000;box-shadow:0 4px 6px -1px #0000001a}.theme-toggle-btn:focus-visible{outline:2px solid var(--current-date);outline-offset:2px}#timeline-root{position:relative}.theme-toggle-btn:hover{background-color:var(--bg-track);transform:scale(1.1)}@media(max-width:1024px){.timeline-wrapper{padding-left:0}}@media(max-width:768px){.page-container{padding:1rem}.timeline-toolbar{flex-direction:column;align-items:stretch}.timeline-filter-container{max-width:none}}.timeline-row,.timeline-table tr{transition:opacity .4s ease,transform .4s cubic-bezier(.4,0,.2,1)}.row-hidden{display:none!important;opacity:0;transform:translateY(-10px)}.row-visible{display:flex!important;opacity:1;transform:translateY(0)}.timeline-table tr.row-visible{display:table-row!important}.timeline-tracks,.timeline-table tbody{transition:max-height .5s ease-in-out}.timeline-table-container{margin-top:1rem;margin-bottom:2rem;overflow-x:auto;border-radius:12px;border:1px solid var(--border-color);background-color:var(--bg-card);transition:all .3s ease}.timeline-table{width:100%;border-collapse:collapse;font-size:.85rem;text-align:left}.timeline-table th{background-color:var(--bg-page);color:var(--text-secondary);font-weight:700;padding:14px 16px;border-bottom:1px solid var(--border-color);text-transform:uppercase;font-size:.75rem;letter-spacing:.05em}.timeline-table td{padding:12px 16px;border-bottom:1px solid var(--border-color);color:var(--text-primary);vertical-align:middle}.timeline-table tr:hover td{background-color:var(--bg-page)}.timeline-table tr:last-child td{border-bottom:none}.table-badge{display:inline-block;padding:4px 12px;border-radius:12px;font-size:.85rem;font-weight:600;font-family:var(--font-family);text-align:center;min-width:64px;transition:all .3s ease}.table-version-link{text-decoration:none;color:inherit;display:inline-block}.table-version-link:hover .table-badge{transform:translateY(-1px);box-shadow:0 4px 8px #0000001a}.table-badge.status-oss{background-color:var(--accent-oss);color:#000}.table-badge.status-ent{background-color:var(--accent-ent);color:#000}.table-badge.status-expired{background-color:var(--bg-track);color:var(--text-secondary)}.past-date{color:var(--text-secondary);opacity:.7;font-style:italic}.timeline-table-toggle{display:flex;justify-content:center;padding:10px;background-color:var(--bg-card);border-top:1px solid var(--border-color);border-bottom-left-radius:12px;margin-top:40px}.risk-high{color:#ef4444}.risk-medium{color:#f59e0b}.risk-low{color:#10b981}[data-theme=dark] .details-header{background-color:#0f172a}
1
+ .lt-root{--bg-page: #f9fafb;--bg-card: #ffffff;--bg-track: #e5e7eb;--text-primary: #1f2937;--text-secondary: #6b7280;--text-inverse: #ffffff;--border-color: #e5e7eb;--line-color: #e5e7eb;--grid-line: #9ca3af;--accent-oss: #99e67d;--accent-ent: #ffe88e;--current-date: #086dc3;--accent-eol: #ef4444;--font-family: "Inter", system-ui, -apple-system, sans-serif;--tooltip-bg: #ffffff;--tooltip-shadow: rgba(0, 0, 0, .1);transition:background-color .3s ease,color .3s ease;background-color:var(--bg-card);color:var(--text-primary);font-family:var(--font-family);padding:24px;border-radius:16px;overflow:hidden;border:1px solid var(--border-color)}.lt-root[data-theme=dark]{--bg-page: #111827;--bg-card: #1f2937;--bg-track: #374151;--text-primary: #f9fafb;--text-secondary: #9ca3af;--border-color: #374151;--line-color: #374151;--grid-line: #6b7280;--tooltip-bg: #1f2937;--tooltip-shadow: rgba(0, 0, 0, .5);--accent-oss: #78bd5d;--accent-ent: #e3c456;--accent-eol: #f87171}.lt-wrapper{position:relative;padding-top:40px;padding-bottom:60px;overflow-x:auto;scrollbar-width:thin}.lt-header{display:flex;position:sticky;left:0;margin-bottom:20px}.lt-tracks{position:relative;padding-top:20px}.lt-axis{display:flex;border-bottom:1px solid var(--border-color);margin-bottom:10px;padding-bottom:8px;position:relative}.lt-year{flex:1;font-size:.875rem;font-weight:600;color:var(--text-secondary);border-left:1px dashed var(--border-color);padding-left:0;text-align:center;box-sizing:border-box}.lt-year:first-child{border-left:none}.lt-row{display:flex;align-items:center;margin-bottom:1.5rem;position:relative;height:36px;z-index:1;opacity:0;transform:translateY(10px);transition:opacity .4s ease,transform .4s ease}.lt-row.lt-row-visible{opacity:1;transform:translateY(0)}.lt-version-label{width:80px;min-width:80px;box-sizing:border-box;padding:4px;border-radius:12px;flex-shrink:0;font-weight:600;font-size:.85rem;color:var(--text-primary);margin-right:16px;text-align:center;background-color:transparent;cursor:pointer;transition:all .3s ease;position:sticky;left:0;z-index:10}.lt-version-label:hover{background-color:var(--bg-track);transform:scale(1.05);box-shadow:0 4px 12px #0000001a}.lt-version-label a.lt-version-link{text-decoration:none;color:inherit;display:block;width:100%;height:100%}.lt-version-label a.lt-version-link:hover{text-decoration:underline;opacity:.8}.lt-status-oss{background-color:var(--accent-oss);color:#000}.lt-status-ent{background-color:var(--accent-ent);color:#000}.lt-status-expired{background-color:var(--accent-eol);color:#fff}.lt-track-container{flex-grow:1;position:relative;height:100%;background:var(--bg-track);border-radius:0;overflow:hidden;transition:background-color .3s ease}.lt-bar-segment{position:absolute;height:100%;top:0;transition:opacity .3s ease;display:flex;align-items:center;justify-content:center;color:#fff;font-size:.75rem;font-weight:500;overflow:hidden;white-space:nowrap;box-shadow:0 1px 2px #0000001a}.lt-bar-oss{background:linear-gradient(to bottom,#dcfce7 0%,var(--accent-oss) 100%);z-index:2;border-radius:0;height:100%;top:auto;bottom:0}[data-theme=dark] .lt-bar-oss{background:linear-gradient(to bottom,#a7f3d0 0%,var(--accent-oss) 100%)}.lt-bar-ent{background:linear-gradient(to bottom,#fef9c3 0%,var(--accent-ent) 100%);z-index:1;border-radius:0;opacity:.9;height:100%}.lt-bar-eol{background:linear-gradient(to bottom,#fee2e2 0%,var(--accent-eol) 100%);z-index:0;border-radius:0;opacity:.8}.lt-grid-lines-container{position:absolute;inset:0 0 0 96px;pointer-events:none;z-index:5}.lt-indicators-container{position:absolute;inset:0 0 0 96px;pointer-events:none;z-index:20}.lt-current-date-indicator{position:absolute;top:-30px;bottom:0;width:3px;background-image:linear-gradient(to bottom,var(--current-date) 60%,transparent 60%);background-size:3px 20px;background-repeat:repeat-y;border-left:none;z-index:20;pointer-events:none}@keyframes pulse-blue{0%{box-shadow:0 0 #086dc3b3}70%{box-shadow:0 0 0 6px #086dc300}to{box-shadow:0 0 #086dc300}}.lt-current-date-badge{position:absolute;top:100%;margin-top:8px;left:50%;transform:translate(-50%);background-color:var(--current-date);color:#fff;padding:4px 12px;border-radius:12px;font-size:.75rem;font-weight:600;white-space:nowrap;box-shadow:0 2px 4px #086dc333;z-index:25;animation:pulse-blue 2s infinite}.lt-year-grid-line{position:absolute;top:0;bottom:0;border-left:2px solid var(--grid-line);opacity:1;z-index:5;pointer-events:none}.lt-tooltip{position:absolute;background:var(--tooltip-bg);color:var(--text-primary);padding:10px 14px;border-radius:8px;font-size:.85rem;box-shadow:0 10px 15px -3px var(--tooltip-shadow),0 4px 6px -4px var(--tooltip-shadow);border:1px solid var(--border-color);pointer-events:none;z-index:10000;transition:opacity .15s ease-out;line-height:1.4}.lt-tooltip-header{font-weight:700;margin-bottom:4px;color:var(--current-date);text-transform:uppercase;font-size:.75rem;letter-spacing:.05em}.lt-tooltip-date strong{color:var(--text-secondary)}.lt-row{transition:opacity .3s ease,transform .3s ease,height .3s ease}.lt-row[style*="display: none"]{opacity:0;transform:translateY(10px)}.lt-bar-segment{transition:transform .2s ease,box-shadow .2s ease}.lt-bar-segment:hover{box-shadow:0 4px 12px #0000001a}.lt-bar-segment:focus-visible{outline:2px solid var(--text-primary);outline-offset:-2px;z-index:100}.lt-track-container{transition:background-color .3s ease}.lt-legend{margin:40px auto 0;padding:24px;background-color:var(--bg-card);border:1px solid var(--border-color);border-radius:12px;display:flex;flex-direction:column;gap:16px;max-width:800px}.lt-legend-block{display:flex;align-items:flex-start;gap:12px}.lt-legend-icon{width:20px;height:20px;border-radius:4px;flex-shrink:0;margin-top:2px}.lt-legend-block h3{margin:0 0 4px;font-size:1rem;font-weight:700;color:var(--text-primary)}.lt-legend-block p{margin:0;font-size:.9rem;color:var(--text-secondary);line-height:1.5}.lt-legend-block.oss .lt-legend-icon{background-color:var(--accent-oss)}.lt-legend-block.commercial .lt-legend-icon{background-color:var(--accent-ent)}.lt-legend-block.eol .lt-legend-icon{background-color:var(--bg-track)}.lt-toolbar{display:flex;justify-content:flex-start;align-items:center;margin-bottom:24px;gap:16px}.lt-filter-container{display:flex;align-items:center;gap:12px;background:var(--bg-card);border:1px solid var(--border-color);padding:8px 16px;border-radius:24px;max-width:400px;flex-grow:1;transition:all .3s ease;box-shadow:0 2px 4px #0000000d}.lt-filter-container:focus-within{border-color:var(--current-date);box-shadow:0 8px 20px #086dc31a}.lt-filter-input{border:none;outline:none;font-size:.95rem;color:var(--text-primary);background:transparent;width:100%}.lt-highlight-match{background-color:#fde68a;color:#92400e;padding:0 2px;border-radius:2px;font-weight:700}.lt-legend{display:flex;flex-wrap:wrap;gap:1.5rem;margin-top:2rem;padding-top:1.5rem;border-top:1px solid var(--border-color)}.lt-legend-item-reactive{cursor:pointer;transition:all .2s ease;padding:8px;border-radius:8px;border:1px solid transparent}.lt-legend-item-reactive:hover{background-color:var(--bg-track);transform:translateY(-2px)}.lt-legend-item-reactive.lt-active-highlight{background-color:var(--bg-track);border-color:var(--grid-line);box-shadow:0 2px 4px #0000001a}.lt-bar-segment,.lt-version-label{transition:opacity .3s ease,filter .3s ease}[class*=lt-highlight-oss] .lt-bar-segment:not(.lt-segment-oss),[class*=lt-highlight-oss] .lt-version-label:not(.lt-status-oss){opacity:.3;filter:grayscale(.5)}[class*=lt-highlight-ent] .lt-bar-segment:not(.lt-segment-ent),[class*=lt-highlight-ent] .lt-version-label:not(.lt-status-ent){opacity:.3;filter:grayscale(.5)}[class*=lt-highlight-eol] .lt-bar-segment,[class*=lt-highlight-eol] .lt-version-label:not(.lt-status-expired){opacity:.3;filter:grayscale(.5)}.lt-toggle-btn{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border-color);padding:8px 16px;border-radius:20px;cursor:pointer;font-size:.875rem;font-weight:500;color:var(--text-primary);box-shadow:0 1px 2px #0000000d;transition:all .2s ease}.lt-toggle-btn:hover{background:var(--bg-page);border-color:var(--text-secondary);transform:translateY(-1px)}.lt-toggle-btn:focus-visible{outline:2px solid var(--current-date);outline-offset:2px}.lt-theme-toggle-btn{position:absolute;top:24px;right:24px;background:var(--bg-card);border:1px solid var(--border-color);color:var(--text-primary);width:40px;height:40px;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s ease;z-index:1000;box-shadow:0 4px 6px -1px #0000001a}.lt-theme-toggle-btn:focus-visible{outline:2px solid var(--current-date);outline-offset:2px}.lt-root{position:relative}.lt-theme-toggle-btn:hover{background-color:var(--bg-track);transform:scale(1.1)}@media(max-width:1024px){.lt-wrapper{padding-left:0}}@media(max-width:768px){.page-container{padding:1rem}.lt-toolbar{flex-direction:column;align-items:stretch}.lt-filter-container{max-width:none}}.lt-row,.lt-table tr{transition:opacity .4s ease,transform .4s cubic-bezier(.4,0,.2,1)}.lt-row-hidden{display:none!important;opacity:0;transform:translateY(-10px)}.lt-row-visible{display:flex!important;opacity:1;transform:translateY(0)}.lt-table tr.lt-row-visible{display:table-row!important}.lt-tracks,.lt-table tbody{transition:max-height .5s ease-in-out}.lt-table-container{margin-top:1rem;margin-bottom:2rem;overflow-x:auto;border-radius:12px;border:1px solid var(--border-color);background-color:var(--bg-card);transition:all .3s ease}.lt-table{width:100%;border-collapse:collapse;font-size:.85rem;text-align:left}.lt-table th{background-color:var(--bg-page);color:var(--text-secondary);font-weight:700;padding:14px 16px;border-bottom:1px solid var(--border-color);text-transform:uppercase;font-size:.75rem;letter-spacing:.05em}.lt-table td{padding:12px 16px;border-bottom:1px solid var(--border-color);color:var(--text-primary);vertical-align:middle}.lt-table tr:hover td{background-color:var(--bg-page)}.lt-table tr:last-child td{border-bottom:none}.lt-table-badge{display:inline-block;padding:4px 12px;border-radius:12px;font-size:.85rem;font-weight:600;font-family:var(--font-family);text-align:center;min-width:64px;transition:all .3s ease}.lt-table-version-link{text-decoration:none;color:inherit;display:inline-block}.lt-table-version-link:hover .lt-table-badge{transform:translateY(-1px);box-shadow:0 4px 8px #0000001a}.lt-table-badge.status-oss{background-color:var(--accent-oss);color:#000}.lt-table-badge.status-ent{background-color:var(--accent-ent);color:#000}.lt-table-badge.status-expired{background-color:var(--accent-eol);color:#fff}.lt-past-date{color:var(--text-secondary);opacity:.7;font-style:italic}.lt-table-toggle{display:flex;justify-content:center;padding:10px;background-color:var(--bg-card);border-top:1px solid var(--border-color);border-bottom-left-radius:12px;margin-top:40px}.risk-high{color:#ef4444}.risk-medium{color:#f59e0b}.risk-low{color:#10b981}[data-theme=dark] .details-header{background-color:#0f172a}
package/dist/timeline.js CHANGED
@@ -46,8 +46,8 @@ class v {
46
46
  * @param {string} [options.locale] - Language code (e.g., 'en', 'fr').
47
47
  * @param {Object} [options.i18n] - Custom translations to merge or override.
48
48
  */
49
- constructor(t, e, s = {}) {
50
- this.root = document.getElementById(t), this.root && (this.options = s, this.visibleCount = s.visibleCount || 3, this.showTable = s.showTable !== !1, this.isExpanded = !1, this.theme = "light", this.filterText = "", this.activeHighlight = null, this.translations = { ...u, ...s.i18n || {} }, this.locale = s.locale || this.detectLanguage(), this.setupBaseLayout(), this.updateData(e));
49
+ constructor(t, e, i = {}) {
50
+ this.root = document.getElementById(t), this.root && (this.options = i, this.visibleCount = i.visibleCount || 3, this.showTable = i.showTable !== !1, this.showThemeToggle = i.showThemeToggle !== !1, this.showLegend = i.showLegend !== !1, this.filterVersions = i.filterVersions !== !1, this.splitSupport = i.splitSupport === !0, this.isExpanded = !1, this.theme = "light", this.filterText = "", this.activeHighlight = null, this.root.classList.add("lt-root"), this.root.classList.toggle("lt-mode-split", this.splitSupport), this.root.classList.toggle("lt-mode-overlay", !this.splitSupport), this.root.setAttribute("data-theme", this.theme), this.translations = { ...u, ...i.i18n || {} }, this.locale = i.locale || this.detectLanguage(), this.setupBaseLayout(), this.updateData(e));
51
51
  }
52
52
  /**
53
53
  * Detects the browser language.
@@ -64,14 +64,14 @@ class v {
64
64
  * @returns {string} The translated string.
65
65
  */
66
66
  t(t, e = {}) {
67
- let s = (this.translations[this.locale] || this.translations.en)[t] || t;
68
- return Object.keys(e).forEach((i) => s = s.replace(`{${i}}`, e[i])), s;
67
+ let i = (this.translations[this.locale] || this.translations.en)[t] || t;
68
+ return Object.keys(e).forEach((s) => i = i.replace(`{${s}}`, e[s])), i;
69
69
  }
70
70
  /**
71
71
  * Sets up the initial layout of the timeline.
72
72
  */
73
73
  setupBaseLayout() {
74
- this.root.innerHTML = "", this.root.setAttribute("role", "application"), this.root.setAttribute("aria-label", "Product Lifecycle Timeline"), this.renderToolbar(), this.renderThemeToggle(), this.showTable && (this.tableContainer = this.el("div", "timeline-table-container", this.root)), this.wrapper = this.el("div", "timeline-wrapper", this.root), this.wrapper.setAttribute("role", "grid"), this.wrapper.setAttribute("aria-readonly", "true"), this.axis = this.el("div", "timeline-axis", this.wrapper), this.axis.setAttribute("role", "row"), this.tracks = this.el("div", "timeline-tracks", this.wrapper), this.legendContainer = this.el("div", "timeline-legend-container", this.wrapper);
74
+ this.root.innerHTML = "", this.root.setAttribute("role", "application"), this.root.setAttribute("aria-label", "Product Lifecycle Timeline"), this.renderToolbar(), this.showThemeToggle && this.renderThemeToggle(), this.showTable && (this.tableContainer = this.el("div", "lt-table-container", this.root)), this.wrapper = this.el("div", "lt-wrapper", this.root), this.wrapper.setAttribute("role", "grid"), this.wrapper.setAttribute("aria-readonly", "true"), this.axis = this.el("div", "lt-axis", this.wrapper), this.axis.setAttribute("role", "row"), this.tracks = this.el("div", "lt-tracks", this.wrapper), this.showLegend && (this.legendContainer = this.el("div", "lt-legend-container", this.wrapper));
75
75
  }
76
76
  /**
77
77
  * Updates the timeline data and re-renders.
@@ -86,11 +86,11 @@ class v {
86
86
  * @returns {Array<Object>} The validated and filtered data array.
87
87
  */
88
88
  validateData(t) {
89
- return t.filter((e, s) => {
90
- const n = ["version", "ossStart", "ossEnd"].filter((l) => !e[l]);
89
+ return t.filter((e, i) => {
90
+ const n = ["version", "ossStart", "ossEnd"].filter((o) => !e[o]);
91
91
  if (n.length > 0)
92
- return console.warn(`[Timeline] Missing fields for item at index ${s}: ${n.join(", ")}`), !1;
93
- const a = ["ossStart", "ossEnd", "enterpriseEnd"].filter((l) => e[l]).filter((l) => isNaN(new Date(e[l]).getTime()));
92
+ return console.warn(`[Timeline] Missing fields for item at index ${i}: ${n.join(", ")}`), !1;
93
+ const a = ["ossStart", "ossEnd", "enterpriseEnd"].filter((o) => e[o]).filter((o) => isNaN(new Date(e[o]).getTime()));
94
94
  return a.length > 0 ? (console.warn(`[Timeline] Invalid date format for item "${e.version}": ${a.join(", ")}`), !1) : !0;
95
95
  });
96
96
  }
@@ -112,43 +112,43 @@ class v {
112
112
  * Renders the data table.
113
113
  */
114
114
  renderTable() {
115
- const t = this.el("table", "timeline-table", this.tableContainer);
115
+ const t = this.el("table", "lt-table", this.tableContainer);
116
116
  t.setAttribute("aria-label", "Project support");
117
- const e = this.el("thead", "", t), s = this.el("tr", "", e);
118
- [this.t("branch"), this.t("initial"), this.t("ossEnd"), this.t("entEnd")].forEach((i) => {
119
- this.el("th", "", s).textContent = i;
120
- }), this.tableBody = this.el("tbody", "", t), this.tableRows = this.data.map((i) => this.createTableRow(i)), this.tableToggleContainer = this.el("div", "timeline-table-toggle", this.tableContainer), this.updateTableVisibility();
117
+ const e = this.el("thead", "", t), i = this.el("tr", "", e);
118
+ [this.t("branch"), this.t("initial"), this.t("ossEnd"), this.t("entEnd")].forEach((s) => {
119
+ this.el("th", "", i).textContent = s;
120
+ }), this.tableBody = this.el("tbody", "", t), this.tableRows = this.data.map((s) => this.createTableRow(s)), this.tableToggleContainer = this.el("div", "lt-table-toggle", this.tableContainer), this.updateTableVisibility();
121
121
  }
122
122
  /**
123
123
  * Creates a row for the data table.
124
124
  * @param {Object} item - Version data.
125
125
  */
126
126
  createTableRow(t) {
127
- const e = this.el("tr", "", this.tableBody), s = Date.now(), i = new Date(t.ossStart).getTime(), n = new Date(t.ossEnd).getTime(), o = t.enterpriseEnd ? new Date(t.enterpriseEnd).getTime() : n, a = s >= i && s <= n ? "status-oss" : s > n && s <= o ? "status-ent" : s > o ? "status-expired" : "", l = this.el("td", "", e);
128
- let d = `<span class="table-badge ${a}">${t.versionOriginal || t.version}</span>`;
129
- t.releaseNotesUrl ? l.innerHTML = `<a href="${t.releaseNotesUrl}" target="_blank" class="table-version-link">${d}</a>` : l.innerHTML = d;
130
- const r = this.el("td", "", e);
131
- r.textContent = t.ossStart, s > i && (r.className = "past-date");
132
- const h = this.el("td", "", e);
133
- h.textContent = t.ossEnd, s > n && (h.className = "past-date");
127
+ const e = this.el("tr", "", this.tableBody), i = Date.now(), s = new Date(t.ossStart).getTime(), n = new Date(t.ossEnd).getTime(), r = t.enterpriseEnd ? new Date(t.enterpriseEnd).getTime() : n, a = i >= s && i <= n ? "lt-status-oss" : i > n && i <= r ? "lt-status-ent" : i > r ? "lt-status-expired" : "", o = this.el("td", "", e);
128
+ let h = `<span class="lt-table-badge ${a}">${t.versionOriginal || t.version}</span>`;
129
+ t.releaseNotesUrl ? o.innerHTML = `<a href="${t.releaseNotesUrl}" target="_blank" class="lt-table-version-link">${h}</a>` : o.innerHTML = h;
130
+ const l = this.el("td", "", e);
131
+ l.textContent = t.ossStart, i > s && (l.className = "lt-past-date");
132
+ const g = this.el("td", "", e);
133
+ g.textContent = t.ossEnd, i > n && (g.className = "lt-past-date");
134
134
  const c = this.el("td", "", e);
135
- return c.textContent = t.enterpriseEnd || t.ossEnd, s > o && (c.className = "past-date"), { el: e, version: t.version.toLowerCase(), versionOriginal: t.versionOriginal || t.version };
135
+ return c.textContent = t.enterpriseEnd || t.ossEnd, i > r && (c.className = "lt-past-date"), { el: e, version: t.version.toLowerCase(), versionOriginal: t.versionOriginal || t.version };
136
136
  }
137
137
  /**
138
138
  * Updates table visibility based on filter and visibleCount.
139
139
  */
140
140
  updateTableVisibility() {
141
- const t = this.tableRows.filter((i) => i.version.includes(this.filterText));
142
- this.tableRows.forEach((i) => {
143
- const n = i.el.querySelector(".table-badge");
144
- n.innerHTML = this.highlight(i.versionOriginal || i.version), i.el.classList.remove("row-visible"), i.el.classList.add("row-hidden");
141
+ const t = this.tableRows.filter((s) => s.version.includes(this.filterText));
142
+ this.tableRows.forEach((s) => {
143
+ const n = s.el.querySelector(".lt-table-badge");
144
+ n.innerHTML = this.highlight(s.versionOriginal || s.version), s.el.classList.remove("lt-row-visible"), s.el.classList.add("lt-row-hidden");
145
145
  });
146
146
  const e = this.filterText === "" && t.length > this.visibleCount;
147
- if ((e && !this.isExpanded ? t.slice(0, this.visibleCount) : t).forEach((i) => {
148
- i.el.classList.remove("row-hidden"), i.el.classList.add("row-visible");
147
+ if ((e && !this.isExpanded ? t.slice(0, this.visibleCount) : t).forEach((s) => {
148
+ s.el.classList.remove("lt-row-hidden"), s.el.classList.add("lt-row-visible");
149
149
  }), this.tableToggleContainer.innerHTML = "", e) {
150
- const i = this.el("button", "timeline-toggle-btn table-toggle", this.tableToggleContainer), n = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="${this.isExpanded ? "18 15 12 9 6 15" : "6 9 12 15 18 9"}"></polyline></svg>`;
151
- i.innerHTML = this.isExpanded ? `${this.t("less")} ${n}` : `${this.t("more", { n: t.length - this.visibleCount })} ${n}`, i.onclick = () => {
150
+ const s = this.el("button", "lt-toggle-btn lt-table-toggle", this.tableToggleContainer), n = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="${this.isExpanded ? "18 15 12 9 6 15" : "6 9 12 15 18 9"}"></polyline></svg>`;
151
+ s.innerHTML = this.isExpanded ? `${this.t("less")} ${n}` : `${this.t("more", { n: t.length - this.visibleCount })} ${n}`, s.onclick = () => {
152
152
  this.isExpanded = !this.isExpanded, this.updateVisibility();
153
153
  };
154
154
  }
@@ -157,19 +157,19 @@ class v {
157
157
  * Renders the entire timeline.
158
158
  */
159
159
  render() {
160
- this.showTable && (this.tableContainer.innerHTML = ""), this.axis.innerHTML = "", this.tracks.innerHTML = "", this.legendContainer.innerHTML = "", this.showTable && this.renderTable(), this.renderAxis(), this.grid = this.el("div", "grid-lines-container", this.tracks), this.indicators = this.el("div", "indicators-container", this.tracks), this.renderGrid(), this.renderCurrentDateLine(), this.rows = this.data.map((t, e) => this.createRow(t, e)), this.toggleContainer = this.el("div", "timeline-more-toggle", this.tracks, {
160
+ this.showTable && (this.tableContainer.innerHTML = ""), this.axis.innerHTML = "", this.tracks.innerHTML = "", this.showLegend && (this.legendContainer.innerHTML = ""), this.showTable && this.renderTable(), this.renderAxis(), this.grid = this.el("div", "lt-grid-lines-container", this.tracks), this.indicators = this.el("div", "lt-indicators-container", this.tracks), this.renderGrid(), this.renderCurrentDateLine(), this.rows = this.data.map((t, e) => this.createRow(t, e)), this.toggleContainer = this.el("div", "lt-more-toggle", this.tracks, {
161
161
  paddingTop: "10px",
162
162
  display: "flex",
163
163
  justifyContent: "center",
164
164
  position: "relative",
165
165
  zIndex: "10"
166
- }), this.updateVisibility(), this.renderLegend(), this.setupTooltip();
166
+ }), this.updateVisibility(), this.showLegend && this.renderLegend(), this.setupTooltip();
167
167
  }
168
168
  /**
169
169
  * Sets up the tooltip overlay.
170
170
  */
171
171
  setupTooltip() {
172
- this.tooltip && this.tooltip.remove(), this.tooltip = this.el("div", "timeline-tooltip-overlay", document.body), this.tooltip.style.display = "none", this.tooltip.setAttribute("role", "tooltip");
172
+ this.tooltip && this.tooltip.remove(), this.tooltip = this.el("div", "lt-tooltip", this.root), this.tooltip.style.display = "none", this.tooltip.setAttribute("role", "tooltip");
173
173
  }
174
174
  /**
175
175
  * Shows the tooltip with provided content.
@@ -184,9 +184,10 @@ class v {
184
184
  * @param {MouseEvent} e - The mouse event.
185
185
  */
186
186
  updateTooltipPos(t) {
187
- let s = t.pageX + 12, i = t.pageY + 12;
188
- const n = this.tooltip.offsetWidth, o = this.tooltip.offsetHeight;
189
- s + n > window.innerWidth && (s = t.pageX - n - 12), i + o > window.innerHeight && (i = t.pageY - o - 12), this.tooltip.style.left = `${s}px`, this.tooltip.style.top = `${i}px`;
187
+ const e = this.root.getBoundingClientRect(), i = 12;
188
+ let s = t.clientX - e.left + i, n = t.clientY - e.top + i;
189
+ const r = this.tooltip.offsetWidth, a = this.tooltip.offsetHeight;
190
+ s + r > this.root.offsetWidth && (s = t.clientX - e.left - r - i), n + a > this.root.offsetHeight && (n = t.clientY - e.top - a - i), this.tooltip.style.left = `${s}px`, this.tooltip.style.top = `${n}px`;
190
191
  }
191
192
  /**
192
193
  * Hides the tooltip.
@@ -203,7 +204,7 @@ class v {
203
204
  if (!this.filterText) return t;
204
205
  try {
205
206
  const e = new RegExp(`(${this.filterText})`, "gi");
206
- return t.replace(e, '<mark class="highlight-match">$1</mark>');
207
+ return t.replace(e, '<mark class="lt-highlight-match">$1</mark>');
207
208
  } catch {
208
209
  return t;
209
210
  }
@@ -216,33 +217,36 @@ class v {
216
217
  * @param {Object} [styles={}] - Inline styles.
217
218
  * @returns {HTMLElement} The created element.
218
219
  */
219
- el(t, e, s, i = {}) {
220
+ el(t, e, i, s = {}) {
220
221
  const n = document.createElement(t);
221
- return e && (n.className = e), Object.assign(n.style, i), s && s.appendChild(n), n;
222
+ return e && (n.className = e), Object.assign(n.style, s), i && i.appendChild(n), n;
222
223
  }
223
224
  /**
224
225
  * Renders the toolbar with the filter input.
225
226
  */
226
227
  renderToolbar() {
227
- const t = this.el("div", "timeline-toolbar", this.root), e = this.el("div", "timeline-filter-container", t);
228
- e.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
229
- const s = this.el("input", "timeline-filter-input", e);
230
- s.placeholder = this.t("filter"), s.value = this.filterText, s.setAttribute("aria-label", this.t("filter")), s.oninput = (i) => {
231
- this.filterText = i.target.value.toLowerCase().trim(), this.updateVisibility();
232
- };
228
+ const t = this.el("div", "lt-toolbar", this.root);
229
+ if (this.filterVersions) {
230
+ const e = this.el("div", "lt-filter-container", t);
231
+ e.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';
232
+ const i = this.el("input", "lt-filter-input", e);
233
+ i.placeholder = this.t("filter"), i.value = this.filterText, i.setAttribute("aria-label", this.t("filter")), i.oninput = (s) => {
234
+ this.filterText = s.target.value.toLowerCase().trim(), this.updateVisibility();
235
+ };
236
+ }
233
237
  }
234
238
  /**
235
239
  * Renders the theme toggle button.
236
240
  */
237
241
  renderThemeToggle() {
238
- const t = this.el("button", "theme-toggle-btn", this.root);
242
+ const t = this.el("button", "lt-theme-toggle-btn", this.root);
239
243
  t.title = this.t("dark"), t.setAttribute("aria-label", this.t("dark"));
240
244
  const e = {
241
245
  moon: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>',
242
246
  sun: '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>'
243
247
  };
244
248
  t.innerHTML = this.theme === "dark" ? e.sun : e.moon, t.onclick = () => {
245
- this.theme = this.theme === "light" ? "dark" : "light", document.documentElement.setAttribute("data-theme", this.theme), t.innerHTML = this.theme === "dark" ? e.sun : e.moon, t.setAttribute("aria-pressed", this.theme === "dark");
249
+ this.theme = this.theme === "light" ? "dark" : "light", this.root.setAttribute("data-theme", this.theme), t.innerHTML = this.theme === "dark" ? e.sun : e.moon, t.setAttribute("aria-pressed", this.theme === "dark");
246
250
  };
247
251
  }
248
252
  /**
@@ -250,13 +254,13 @@ class v {
250
254
  */
251
255
  renderLegend() {
252
256
  this.legendContainer.innerHTML = "";
253
- const t = this.el("div", "release-legend", this.legendContainer);
257
+ const t = this.el("div", "lt-legend", this.legendContainer);
254
258
  t.setAttribute("role", "complementary"), t.setAttribute("aria-label", "Support Legend"), ["oss", "ent", "eol"].forEach((e) => {
255
- const s = this.el("div", `legend-block ${e === "ent" ? "commercial" : e}`, t);
256
- s.classList.add("legend-item-reactive"), this.activeHighlight === e && s.classList.add("active-highlight"), s.innerHTML = `
257
- <div class="legend-icon" aria-hidden="true"></div>
259
+ const i = this.el("div", `lt-legend-block ${e === "ent" ? "commercial" : e}`, t);
260
+ i.classList.add("lt-legend-item-reactive"), this.activeHighlight === e && i.classList.add("lt-active-highlight"), i.innerHTML = `
261
+ <div class="lt-legend-icon" aria-hidden="true"></div>
258
262
  <div><h3>${this.t(e)}</h3><p>${this.t(e + "Desc")}</p></div>
259
- `, s.onclick = () => this.highlightSegment(e);
263
+ `, i.onclick = () => this.highlightSegment(e);
260
264
  });
261
265
  }
262
266
  /**
@@ -264,7 +268,7 @@ class v {
264
268
  * @param {string} type - 'oss', 'ent', or 'eol'.
265
269
  */
266
270
  highlightSegment(t) {
267
- this.activeHighlight = this.activeHighlight === t ? null : t, this.root.classList.remove("highlight-oss", "highlight-ent", "highlight-eol"), this.activeHighlight && this.root.classList.add(`highlight-${this.activeHighlight}`), this.renderLegend();
271
+ this.activeHighlight = this.activeHighlight === t ? null : t, this.root.classList.remove("lt-highlight-oss", "lt-highlight-ent", "lt-highlight-eol"), this.activeHighlight && this.root.classList.add(`lt-highlight-${this.activeHighlight}`), this.renderLegend();
268
272
  }
269
273
  /**
270
274
  * Renders the year axis.
@@ -272,8 +276,8 @@ class v {
272
276
  renderAxis() {
273
277
  this.el("div", "", this.axis, { width: "96px", flexShrink: "0" }).setAttribute("role", "presentation");
274
278
  for (let e = this.minYear; e <= this.maxYear; e++) {
275
- const s = this.el("div", "timeline-year", this.axis);
276
- s.textContent = e, s.setAttribute("role", "columnheader");
279
+ const i = this.el("div", "lt-year", this.axis);
280
+ i.textContent = e, i.setAttribute("role", "columnheader");
277
281
  }
278
282
  }
279
283
  /**
@@ -281,9 +285,9 @@ class v {
281
285
  */
282
286
  renderGrid() {
283
287
  const t = new Date(this.minYear, 0, 1).getTime(), e = new Date(this.maxYear, 11, 31).getTime() - t;
284
- for (let s = this.minYear; s <= this.maxYear; s++) {
285
- const i = this.el("div", "year-grid-line", this.grid);
286
- i.style.left = `${(new Date(s, 0, 1).getTime() - t) / e * 100}%`, i.setAttribute("role", "presentation");
288
+ for (let i = this.minYear; i <= this.maxYear; i++) {
289
+ const s = this.el("div", "lt-year-grid-line", this.grid);
290
+ s.style.left = `${(new Date(i, 0, 1).getTime() - t) / e * 100}%`, s.setAttribute("role", "presentation");
287
291
  }
288
292
  }
289
293
  /**
@@ -292,18 +296,20 @@ class v {
292
296
  * @returns {Object} Metadata about the created row.
293
297
  */
294
298
  createRow(t, e) {
295
- const s = this.el("div", "timeline-row row-entrance", this.tracks);
296
- s.setAttribute("role", "row"), s.style.transitionDelay = `${e * 0.05}s`;
297
- const i = this.el("div", "version-label", s);
298
- i.setAttribute("role", "rowheader");
299
- const n = Date.now(), o = new Date(t.ossStart).getTime(), a = new Date(t.ossEnd).getTime(), l = t.enterpriseEnd ? new Date(t.enterpriseEnd).getTime() : a, d = n >= o && n <= a ? "status-oss" : n > a && n <= l ? "status-ent" : n > l ? "status-expired" : "";
300
- if (d && i.classList.add(d), t.releaseNotesUrl) {
301
- const h = this.el("a", "version-link", i);
302
- h.href = t.releaseNotesUrl, h.target = "_blank", h.innerHTML = this.highlight(t.version), h.title = this.t("notes", { v: t.version }), h.setAttribute("aria-label", this.t("notes", { v: t.version }));
299
+ const i = this.el("div", "lt-row row-entrance", this.tracks);
300
+ i.setAttribute("role", "row"), i.style.transitionDelay = `${e * 0.05}s`;
301
+ const s = this.el("div", "lt-version-label", i);
302
+ s.setAttribute("role", "rowheader");
303
+ const n = Date.now(), r = new Date(t.ossStart).getTime(), a = new Date(t.ossEnd).getTime(), o = t.enterpriseEnd ? new Date(t.enterpriseEnd).getTime() : a, h = n >= r && n <= a ? "lt-status-oss" : n > a && n <= o ? "lt-status-ent" : n > o ? "lt-status-expired" : "";
304
+ if (h && s.classList.add(h), t.releaseNotesUrl) {
305
+ const d = this.el("a", "lt-version-link", s);
306
+ d.href = t.releaseNotesUrl, d.target = "_blank", d.innerHTML = this.highlight(t.version), d.title = this.t("notes", { v: t.version }), d.setAttribute("aria-label", this.t("notes", { v: t.version }));
303
307
  } else
304
- i.innerHTML = this.highlight(t.version);
305
- const r = this.el("div", "track-container", s);
306
- return r.setAttribute("role", "gridcell"), r.appendChild(this.createBar(t, t.ossStart, t.enterpriseEnd || t.ossEnd, "bar-ent", this.t("ent"))), r.appendChild(this.createBar(t, t.ossStart, t.ossEnd, "bar-oss", this.t("oss"))), { el: s, version: t.version.toLowerCase(), versionOriginal: t.version };
308
+ s.innerHTML = this.highlight(t.version);
309
+ const l = this.el("div", "lt-track-container", i);
310
+ l.setAttribute("role", "gridcell");
311
+ const g = this.splitSupport ? t.ossEnd : t.ossStart, c = t.enterpriseEnd || t.ossEnd, p = new Date(this.maxYear, 11, 31).toISOString().split("T")[0];
312
+ return l.appendChild(this.createBar(t, c, p, "lt-bar-eol", this.t("eol"))), l.appendChild(this.createBar(t, g, t.enterpriseEnd || t.ossEnd, "lt-bar-ent", this.t("ent"))), l.appendChild(this.createBar(t, t.ossStart, t.ossEnd, "lt-bar-oss", this.t("oss"))), { el: i, version: t.version.toLowerCase(), versionOriginal: t.version };
307
313
  }
308
314
  /**
309
315
  * Creates a colored bar segment for the timeline.
@@ -314,34 +320,34 @@ class v {
314
320
  * @param {string} label - Support type label for tooltip.
315
321
  * @returns {HTMLElement} The created bar element.
316
322
  */
317
- createBar(t, e, s, i, n) {
318
- const o = new Date(e).getTime(), a = new Date(s).getTime(), l = new Date(this.minYear, 0, 1).getTime(), d = new Date(this.maxYear, 11, 31).getTime() - l, r = this.el("div", `bar-segment ${i}`), h = i === "bar-oss" ? "segment-oss" : "segment-ent";
319
- r.classList.add(h), r.style.left = `${(o - l) / d * 100}%`, r.style.width = `${(a - o) / d * 100}%`, r.setAttribute("role", "img"), r.setAttribute("aria-label", `${n}: ${t.version} (${e} to ${s})`), r.tabIndex = 0;
323
+ createBar(t, e, i, s, n) {
324
+ const r = new Date(e).getTime(), a = new Date(i).getTime(), o = new Date(this.minYear, 0, 1).getTime(), h = new Date(this.maxYear, 11, 31).getTime() - o, l = this.el("div", `lt-bar-segment ${s}`), g = s === "lt-bar-oss" ? "lt-segment-oss" : s === "lt-bar-ent" ? "lt-segment-ent" : "lt-segment-eol";
325
+ l.classList.add(g), l.style.left = `${(r - o) / h * 100}%`, l.style.width = `${(a - r) / h * 100}%`, l.setAttribute("role", "img"), l.setAttribute("aria-label", `${n}: ${t.version} (${e} to ${i})`), l.tabIndex = 0;
320
326
  const c = `
321
- <div class="tooltip-header">${n} - ${t.version}</div>
322
- <div class="tooltip-date"><strong>Du:</strong> ${e}</div>
323
- <div class="tooltip-date"><strong>Au:</strong> ${s}</div>
327
+ <div class="lt-tooltip-header">${n} - ${t.version}</div>
328
+ <div class="lt-tooltip-date"><strong>Du:</strong> ${e}</div>
329
+ <div class="lt-tooltip-date"><strong>Au:</strong> ${i}</div>
324
330
  `;
325
- return r.onmouseenter = (g) => this.showTooltip(g, c), r.onmousemove = (g) => this.updateTooltipPos(g), r.onmouseleave = () => this.hideTooltip(), r.onfocus = (g) => {
326
- const p = r.getBoundingClientRect();
327
- this.showTooltip({ pageX: p.left + window.scrollX, pageY: p.top + window.scrollY - 40 }, c);
328
- }, r.onblur = () => this.hideTooltip(), r;
331
+ return l.onmouseenter = (p) => this.showTooltip(p, c), l.onmousemove = (p) => this.updateTooltipPos(p), l.onmouseleave = () => this.hideTooltip(), l.onfocus = (p) => {
332
+ const d = l.getBoundingClientRect();
333
+ this.showTooltip({ pageX: d.left + window.scrollX, pageY: d.top + window.scrollY - 40 }, c);
334
+ }, l.onblur = () => this.hideTooltip(), l;
329
335
  }
330
336
  /**
331
337
  * Updates visibility of rows based on filtering and expansion state.
332
338
  */
333
339
  updateVisibility() {
334
- const t = this.rows.filter((i) => i.version.includes(this.filterText));
335
- this.rows.forEach((i) => {
336
- const n = i.el.querySelector(".version-label"), o = n.querySelector(".version-link");
337
- o ? o.innerHTML = this.highlight(i.versionOriginal || i.version) : n.innerHTML = this.highlight(i.versionOriginal || i.version), i.el.classList.remove("row-visible"), i.el.classList.add("row-hidden");
340
+ const t = this.rows.filter((s) => s.version.includes(this.filterText));
341
+ this.rows.forEach((s) => {
342
+ const n = s.el.querySelector(".lt-version-label"), r = n.querySelector(".lt-version-link");
343
+ r ? r.innerHTML = this.highlight(s.versionOriginal || s.version) : n.innerHTML = this.highlight(s.versionOriginal || s.version), s.el.classList.remove("lt-row-visible"), s.el.classList.add("lt-row-hidden");
338
344
  });
339
345
  const e = this.filterText === "" && t.length > this.visibleCount;
340
- if ((e && !this.isExpanded ? t.slice(0, this.visibleCount) : t).forEach((i) => {
341
- i.el.classList.remove("row-hidden"), i.el.classList.add("row-visible");
346
+ if ((e && !this.isExpanded ? t.slice(0, this.visibleCount) : t).forEach((s) => {
347
+ s.el.classList.remove("lt-row-hidden"), s.el.classList.add("lt-row-visible");
342
348
  }), this.showTable && this.updateTableVisibility(), this.toggleContainer.innerHTML = "", e) {
343
- const i = this.el("button", "timeline-toggle-btn", this.toggleContainer), n = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="${this.isExpanded ? "18 15 12 9 6 15" : "6 9 12 15 18 9"}"></polyline></svg>`;
344
- i.innerHTML = this.isExpanded ? `${this.t("less")} ${n}` : `${this.t("more", { n: t.length - this.visibleCount })} ${n}`, i.setAttribute("aria-expanded", this.isExpanded), i.onclick = () => {
349
+ const s = this.el("button", "lt-toggle-btn", this.toggleContainer), n = `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="${this.isExpanded ? "18 15 12 9 6 15" : "6 9 12 15 18 9"}"></polyline></svg>`;
350
+ s.innerHTML = this.isExpanded ? `${this.t("less")} ${n}` : `${this.t("more", { n: t.length - this.visibleCount })} ${n}`, s.setAttribute("aria-expanded", this.isExpanded), s.onclick = () => {
345
351
  this.isExpanded = !this.isExpanded, this.updateVisibility();
346
352
  };
347
353
  }
@@ -350,12 +356,12 @@ class v {
350
356
  * Renders the vertical line indicating current date.
351
357
  */
352
358
  renderCurrentDateLine() {
353
- const t = new Date(this.minYear, 0, 1).getTime(), e = new Date(this.maxYear, 11, 31).getTime() - t, s = Date.now() - t;
354
- if (s < 0 || s > e) return;
355
- const i = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], n = this.el("div", "current-date-indicator", this.indicators);
356
- n.style.left = `${s / e * 100}%`, n.setAttribute("role", "separator"), n.setAttribute("aria-label", this.t("today", { date: i }));
357
- const o = this.el("div", "current-date-badge", n);
358
- o.textContent = i, o.setAttribute("aria-hidden", "true");
359
+ const t = new Date(this.minYear, 0, 1).getTime(), e = new Date(this.maxYear, 11, 31).getTime() - t, i = Date.now() - t;
360
+ if (i < 0 || i > e) return;
361
+ const s = (/* @__PURE__ */ new Date()).toISOString().split("T")[0], n = this.el("div", "lt-current-date-indicator", this.indicators);
362
+ n.style.left = `${i / e * 100}%`, n.setAttribute("role", "separator"), n.setAttribute("aria-label", this.t("today", { date: s }));
363
+ const r = this.el("div", "lt-current-date-badge", n);
364
+ r.textContent = s, r.setAttribute("aria-hidden", "true");
359
365
  }
360
366
  }
361
367
  export {
@@ -1,8 +1,8 @@
1
- (function(c,g){typeof exports=="object"&&typeof module<"u"?module.exports=g():typeof define=="function"&&define.amd?define(g):(c=typeof globalThis<"u"?globalThis:c||self,c.Timeline=g())})(this,(function(){"use strict";const c={en:{filter:"Filter versions...",oss:"OSS Support",ent:"Enterprise Support",eol:"Out of Support",less:"Show Less",more:"Show {n} more versions",notes:"View Release Notes for {v}",dark:"Toggle Dark Mode",ossDesc:"Free security updates and bugfixes.",entDesc:"Expert support during OSS plus extended support after EOL.",eolDesc:"End of life. No further updates are provided.",today:"Today's date: {date}",branch:"Branch",initial:"Initial Release",ossEnd:"End OSS",entEnd:"End Enterprise *"},fr:{filter:"Filtrer les versions...",oss:"Support OSS",ent:"Support Entreprise",eol:"Fin de support",less:"Voir moins",more:"Voir {n} versions supplémentaires",notes:"Voir les notes de version pour {v}",dark:"Changer le mode sombre",ossDesc:"Mises à jour de sécurité et corrections de bugs gratuites.",entDesc:"Support pendant la période OSS plus support étendu après.",eolDesc:"Version en fin de vie. Plus de mises à jour.",today:"Date du jour : {date}",branch:"Version",initial:"Sortie initiale",ossEnd:"Fin OSS",entEnd:"Fin Entreprise *"}};class g{constructor(t,e,s={}){this.root=document.getElementById(t),this.root&&(this.options=s,this.visibleCount=s.visibleCount||3,this.showTable=s.showTable!==!1,this.isExpanded=!1,this.theme="light",this.filterText="",this.activeHighlight=null,this.translations={...c,...s.i18n||{}},this.locale=s.locale||this.detectLanguage(),this.setupBaseLayout(),this.updateData(e))}detectLanguage(){const t=(navigator.language||"en").split("-")[0];return this.translations[t]?t:"en"}t(t,e={}){let s=(this.translations[this.locale]||this.translations.en)[t]||t;return Object.keys(e).forEach(i=>s=s.replace(`{${i}}`,e[i])),s}setupBaseLayout(){this.root.innerHTML="",this.root.setAttribute("role","application"),this.root.setAttribute("aria-label","Product Lifecycle Timeline"),this.renderToolbar(),this.renderThemeToggle(),this.showTable&&(this.tableContainer=this.el("div","timeline-table-container",this.root)),this.wrapper=this.el("div","timeline-wrapper",this.root),this.wrapper.setAttribute("role","grid"),this.wrapper.setAttribute("aria-readonly","true"),this.axis=this.el("div","timeline-axis",this.wrapper),this.axis.setAttribute("role","row"),this.tracks=this.el("div","timeline-tracks",this.wrapper),this.legendContainer=this.el("div","timeline-legend-container",this.wrapper)}updateData(t){this.data=this.validateData(t||[]),this.calculateTimeRange(),this.render()}validateData(t){return t.filter((e,s)=>{const n=["version","ossStart","ossEnd"].filter(l=>!e[l]);if(n.length>0)return console.warn(`[Timeline] Missing fields for item at index ${s}: ${n.join(", ")}`),!1;const a=["ossStart","ossEnd","enterpriseEnd"].filter(l=>e[l]).filter(l=>isNaN(new Date(e[l]).getTime()));return a.length>0?(console.warn(`[Timeline] Invalid date format for item "${e.version}": ${a.join(", ")}`),!1):!0})}calculateTimeRange(){if(this.currentDate=new Date,!this.data.length){this.minYear=this.currentDate.getFullYear()-1,this.maxYear=this.currentDate.getFullYear()+3;return}const t=this.data.flatMap(e=>[new Date(e.ossStart),new Date(e.enterpriseEnd||e.ossEnd)]);this.minYear=new Date(Math.min(...t)).getFullYear(),this.maxYear=new Date(Math.max(...t)).getFullYear()}renderTable(){const t=this.el("table","timeline-table",this.tableContainer);t.setAttribute("aria-label","Project support");const e=this.el("thead","",t),s=this.el("tr","",e);[this.t("branch"),this.t("initial"),this.t("ossEnd"),this.t("entEnd")].forEach(i=>{this.el("th","",s).textContent=i}),this.tableBody=this.el("tbody","",t),this.tableRows=this.data.map(i=>this.createTableRow(i)),this.tableToggleContainer=this.el("div","timeline-table-toggle",this.tableContainer),this.updateTableVisibility()}createTableRow(t){const e=this.el("tr","",this.tableBody),s=Date.now(),i=new Date(t.ossStart).getTime(),n=new Date(t.ossEnd).getTime(),o=t.enterpriseEnd?new Date(t.enterpriseEnd).getTime():n,a=s>=i&&s<=n?"status-oss":s>n&&s<=o?"status-ent":s>o?"status-expired":"",l=this.el("td","",e);let d=`<span class="table-badge ${a}">${t.versionOriginal||t.version}</span>`;t.releaseNotesUrl?l.innerHTML=`<a href="${t.releaseNotesUrl}" target="_blank" class="table-version-link">${d}</a>`:l.innerHTML=d;const r=this.el("td","",e);r.textContent=t.ossStart,s>i&&(r.className="past-date");const h=this.el("td","",e);h.textContent=t.ossEnd,s>n&&(h.className="past-date");const u=this.el("td","",e);return u.textContent=t.enterpriseEnd||t.ossEnd,s>o&&(u.className="past-date"),{el:e,version:t.version.toLowerCase(),versionOriginal:t.versionOriginal||t.version}}updateTableVisibility(){const t=this.tableRows.filter(i=>i.version.includes(this.filterText));this.tableRows.forEach(i=>{const n=i.el.querySelector(".table-badge");n.innerHTML=this.highlight(i.versionOriginal||i.version),i.el.classList.remove("row-visible"),i.el.classList.add("row-hidden")});const e=this.filterText===""&&t.length>this.visibleCount;if((e&&!this.isExpanded?t.slice(0,this.visibleCount):t).forEach(i=>{i.el.classList.remove("row-hidden"),i.el.classList.add("row-visible")}),this.tableToggleContainer.innerHTML="",e){const i=this.el("button","timeline-toggle-btn table-toggle",this.tableToggleContainer),n=`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="${this.isExpanded?"18 15 12 9 6 15":"6 9 12 15 18 9"}"></polyline></svg>`;i.innerHTML=this.isExpanded?`${this.t("less")} ${n}`:`${this.t("more",{n:t.length-this.visibleCount})} ${n}`,i.onclick=()=>{this.isExpanded=!this.isExpanded,this.updateVisibility()}}}render(){this.showTable&&(this.tableContainer.innerHTML=""),this.axis.innerHTML="",this.tracks.innerHTML="",this.legendContainer.innerHTML="",this.showTable&&this.renderTable(),this.renderAxis(),this.grid=this.el("div","grid-lines-container",this.tracks),this.indicators=this.el("div","indicators-container",this.tracks),this.renderGrid(),this.renderCurrentDateLine(),this.rows=this.data.map((t,e)=>this.createRow(t,e)),this.toggleContainer=this.el("div","timeline-more-toggle",this.tracks,{paddingTop:"10px",display:"flex",justifyContent:"center",position:"relative",zIndex:"10"}),this.updateVisibility(),this.renderLegend(),this.setupTooltip()}setupTooltip(){this.tooltip&&this.tooltip.remove(),this.tooltip=this.el("div","timeline-tooltip-overlay",document.body),this.tooltip.style.display="none",this.tooltip.setAttribute("role","tooltip")}showTooltip(t,e){this.tooltip.innerHTML=e,this.tooltip.style.display="block",this.updateTooltipPos(t)}updateTooltipPos(t){let s=t.pageX+12,i=t.pageY+12;const n=this.tooltip.offsetWidth,o=this.tooltip.offsetHeight;s+n>window.innerWidth&&(s=t.pageX-n-12),i+o>window.innerHeight&&(i=t.pageY-o-12),this.tooltip.style.left=`${s}px`,this.tooltip.style.top=`${i}px`}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none")}highlight(t){if(!this.filterText)return t;try{const e=new RegExp(`(${this.filterText})`,"gi");return t.replace(e,'<mark class="highlight-match">$1</mark>')}catch{return t}}el(t,e,s,i={}){const n=document.createElement(t);return e&&(n.className=e),Object.assign(n.style,i),s&&s.appendChild(n),n}renderToolbar(){const t=this.el("div","timeline-toolbar",this.root),e=this.el("div","timeline-filter-container",t);e.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';const s=this.el("input","timeline-filter-input",e);s.placeholder=this.t("filter"),s.value=this.filterText,s.setAttribute("aria-label",this.t("filter")),s.oninput=i=>{this.filterText=i.target.value.toLowerCase().trim(),this.updateVisibility()}}renderThemeToggle(){const t=this.el("button","theme-toggle-btn",this.root);t.title=this.t("dark"),t.setAttribute("aria-label",this.t("dark"));const e={moon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>',sun:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>'};t.innerHTML=this.theme==="dark"?e.sun:e.moon,t.onclick=()=>{this.theme=this.theme==="light"?"dark":"light",document.documentElement.setAttribute("data-theme",this.theme),t.innerHTML=this.theme==="dark"?e.sun:e.moon,t.setAttribute("aria-pressed",this.theme==="dark")}}renderLegend(){this.legendContainer.innerHTML="";const t=this.el("div","release-legend",this.legendContainer);t.setAttribute("role","complementary"),t.setAttribute("aria-label","Support Legend"),["oss","ent","eol"].forEach(e=>{const s=this.el("div",`legend-block ${e==="ent"?"commercial":e}`,t);s.classList.add("legend-item-reactive"),this.activeHighlight===e&&s.classList.add("active-highlight"),s.innerHTML=`
2
- <div class="legend-icon" aria-hidden="true"></div>
1
+ (function(u,b){typeof exports=="object"&&typeof module<"u"?module.exports=b():typeof define=="function"&&define.amd?define(b):(u=typeof globalThis<"u"?globalThis:u||self,u.Timeline=b())})(this,(function(){"use strict";const u={en:{filter:"Filter versions...",oss:"OSS Support",ent:"Enterprise Support",eol:"Out of Support",less:"Show Less",more:"Show {n} more versions",notes:"View Release Notes for {v}",dark:"Toggle Dark Mode",ossDesc:"Free security updates and bugfixes.",entDesc:"Expert support during OSS plus extended support after EOL.",eolDesc:"End of life. No further updates are provided.",today:"Today's date: {date}",branch:"Branch",initial:"Initial Release",ossEnd:"End OSS",entEnd:"End Enterprise *"},fr:{filter:"Filtrer les versions...",oss:"Support OSS",ent:"Support Entreprise",eol:"Fin de support",less:"Voir moins",more:"Voir {n} versions supplémentaires",notes:"Voir les notes de version pour {v}",dark:"Changer le mode sombre",ossDesc:"Mises à jour de sécurité et corrections de bugs gratuites.",entDesc:"Support pendant la période OSS plus support étendu après.",eolDesc:"Version en fin de vie. Plus de mises à jour.",today:"Date du jour : {date}",branch:"Version",initial:"Sortie initiale",ossEnd:"Fin OSS",entEnd:"Fin Entreprise *"}};class b{constructor(t,e,i={}){this.root=document.getElementById(t),this.root&&(this.options=i,this.visibleCount=i.visibleCount||3,this.showTable=i.showTable!==!1,this.showThemeToggle=i.showThemeToggle!==!1,this.showLegend=i.showLegend!==!1,this.filterVersions=i.filterVersions!==!1,this.splitSupport=i.splitSupport===!0,this.isExpanded=!1,this.theme="light",this.filterText="",this.activeHighlight=null,this.root.classList.add("lt-root"),this.root.classList.toggle("lt-mode-split",this.splitSupport),this.root.classList.toggle("lt-mode-overlay",!this.splitSupport),this.root.setAttribute("data-theme",this.theme),this.translations={...u,...i.i18n||{}},this.locale=i.locale||this.detectLanguage(),this.setupBaseLayout(),this.updateData(e))}detectLanguage(){const t=(navigator.language||"en").split("-")[0];return this.translations[t]?t:"en"}t(t,e={}){let i=(this.translations[this.locale]||this.translations.en)[t]||t;return Object.keys(e).forEach(s=>i=i.replace(`{${s}}`,e[s])),i}setupBaseLayout(){this.root.innerHTML="",this.root.setAttribute("role","application"),this.root.setAttribute("aria-label","Product Lifecycle Timeline"),this.renderToolbar(),this.showThemeToggle&&this.renderThemeToggle(),this.showTable&&(this.tableContainer=this.el("div","lt-table-container",this.root)),this.wrapper=this.el("div","lt-wrapper",this.root),this.wrapper.setAttribute("role","grid"),this.wrapper.setAttribute("aria-readonly","true"),this.axis=this.el("div","lt-axis",this.wrapper),this.axis.setAttribute("role","row"),this.tracks=this.el("div","lt-tracks",this.wrapper),this.showLegend&&(this.legendContainer=this.el("div","lt-legend-container",this.wrapper))}updateData(t){this.data=this.validateData(t||[]),this.calculateTimeRange(),this.render()}validateData(t){return t.filter((e,i)=>{const n=["version","ossStart","ossEnd"].filter(r=>!e[r]);if(n.length>0)return console.warn(`[Timeline] Missing fields for item at index ${i}: ${n.join(", ")}`),!1;const a=["ossStart","ossEnd","enterpriseEnd"].filter(r=>e[r]).filter(r=>isNaN(new Date(e[r]).getTime()));return a.length>0?(console.warn(`[Timeline] Invalid date format for item "${e.version}": ${a.join(", ")}`),!1):!0})}calculateTimeRange(){if(this.currentDate=new Date,!this.data.length){this.minYear=this.currentDate.getFullYear()-1,this.maxYear=this.currentDate.getFullYear()+3;return}const t=this.data.flatMap(e=>[new Date(e.ossStart),new Date(e.enterpriseEnd||e.ossEnd)]);this.minYear=new Date(Math.min(...t)).getFullYear(),this.maxYear=new Date(Math.max(...t)).getFullYear()}renderTable(){const t=this.el("table","lt-table",this.tableContainer);t.setAttribute("aria-label","Project support");const e=this.el("thead","",t),i=this.el("tr","",e);[this.t("branch"),this.t("initial"),this.t("ossEnd"),this.t("entEnd")].forEach(s=>{this.el("th","",i).textContent=s}),this.tableBody=this.el("tbody","",t),this.tableRows=this.data.map(s=>this.createTableRow(s)),this.tableToggleContainer=this.el("div","lt-table-toggle",this.tableContainer),this.updateTableVisibility()}createTableRow(t){const e=this.el("tr","",this.tableBody),i=Date.now(),s=new Date(t.ossStart).getTime(),n=new Date(t.ossEnd).getTime(),l=t.enterpriseEnd?new Date(t.enterpriseEnd).getTime():n,a=i>=s&&i<=n?"lt-status-oss":i>n&&i<=l?"lt-status-ent":i>l?"lt-status-expired":"",r=this.el("td","",e);let h=`<span class="lt-table-badge ${a}">${t.versionOriginal||t.version}</span>`;t.releaseNotesUrl?r.innerHTML=`<a href="${t.releaseNotesUrl}" target="_blank" class="lt-table-version-link">${h}</a>`:r.innerHTML=h;const o=this.el("td","",e);o.textContent=t.ossStart,i>s&&(o.className="lt-past-date");const g=this.el("td","",e);g.textContent=t.ossEnd,i>n&&(g.className="lt-past-date");const c=this.el("td","",e);return c.textContent=t.enterpriseEnd||t.ossEnd,i>l&&(c.className="lt-past-date"),{el:e,version:t.version.toLowerCase(),versionOriginal:t.versionOriginal||t.version}}updateTableVisibility(){const t=this.tableRows.filter(s=>s.version.includes(this.filterText));this.tableRows.forEach(s=>{const n=s.el.querySelector(".lt-table-badge");n.innerHTML=this.highlight(s.versionOriginal||s.version),s.el.classList.remove("lt-row-visible"),s.el.classList.add("lt-row-hidden")});const e=this.filterText===""&&t.length>this.visibleCount;if((e&&!this.isExpanded?t.slice(0,this.visibleCount):t).forEach(s=>{s.el.classList.remove("lt-row-hidden"),s.el.classList.add("lt-row-visible")}),this.tableToggleContainer.innerHTML="",e){const s=this.el("button","lt-toggle-btn lt-table-toggle",this.tableToggleContainer),n=`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="${this.isExpanded?"18 15 12 9 6 15":"6 9 12 15 18 9"}"></polyline></svg>`;s.innerHTML=this.isExpanded?`${this.t("less")} ${n}`:`${this.t("more",{n:t.length-this.visibleCount})} ${n}`,s.onclick=()=>{this.isExpanded=!this.isExpanded,this.updateVisibility()}}}render(){this.showTable&&(this.tableContainer.innerHTML=""),this.axis.innerHTML="",this.tracks.innerHTML="",this.showLegend&&(this.legendContainer.innerHTML=""),this.showTable&&this.renderTable(),this.renderAxis(),this.grid=this.el("div","lt-grid-lines-container",this.tracks),this.indicators=this.el("div","lt-indicators-container",this.tracks),this.renderGrid(),this.renderCurrentDateLine(),this.rows=this.data.map((t,e)=>this.createRow(t,e)),this.toggleContainer=this.el("div","lt-more-toggle",this.tracks,{paddingTop:"10px",display:"flex",justifyContent:"center",position:"relative",zIndex:"10"}),this.updateVisibility(),this.showLegend&&this.renderLegend(),this.setupTooltip()}setupTooltip(){this.tooltip&&this.tooltip.remove(),this.tooltip=this.el("div","lt-tooltip",this.root),this.tooltip.style.display="none",this.tooltip.setAttribute("role","tooltip")}showTooltip(t,e){this.tooltip.innerHTML=e,this.tooltip.style.display="block",this.updateTooltipPos(t)}updateTooltipPos(t){const e=this.root.getBoundingClientRect(),i=12;let s=t.clientX-e.left+i,n=t.clientY-e.top+i;const l=this.tooltip.offsetWidth,a=this.tooltip.offsetHeight;s+l>this.root.offsetWidth&&(s=t.clientX-e.left-l-i),n+a>this.root.offsetHeight&&(n=t.clientY-e.top-a-i),this.tooltip.style.left=`${s}px`,this.tooltip.style.top=`${n}px`}hideTooltip(){this.tooltip&&(this.tooltip.style.display="none")}highlight(t){if(!this.filterText)return t;try{const e=new RegExp(`(${this.filterText})`,"gi");return t.replace(e,'<mark class="lt-highlight-match">$1</mark>')}catch{return t}}el(t,e,i,s={}){const n=document.createElement(t);return e&&(n.className=e),Object.assign(n.style,s),i&&i.appendChild(n),n}renderToolbar(){const t=this.el("div","lt-toolbar",this.root);if(this.filterVersions){const e=this.el("div","lt-filter-container",t);e.innerHTML='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>';const i=this.el("input","lt-filter-input",e);i.placeholder=this.t("filter"),i.value=this.filterText,i.setAttribute("aria-label",this.t("filter")),i.oninput=s=>{this.filterText=s.target.value.toLowerCase().trim(),this.updateVisibility()}}}renderThemeToggle(){const t=this.el("button","lt-theme-toggle-btn",this.root);t.title=this.t("dark"),t.setAttribute("aria-label",this.t("dark"));const e={moon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>',sun:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="5"></circle><line x1="12" y1="1" x2="12" y2="3"></line><line x1="12" y1="21" x2="12" y2="23"></line><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line><line x1="1" y1="12" x2="3" y2="12"></line><line x1="21" y1="12" x2="23" y2="12"></line><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line></svg>'};t.innerHTML=this.theme==="dark"?e.sun:e.moon,t.onclick=()=>{this.theme=this.theme==="light"?"dark":"light",this.root.setAttribute("data-theme",this.theme),t.innerHTML=this.theme==="dark"?e.sun:e.moon,t.setAttribute("aria-pressed",this.theme==="dark")}}renderLegend(){this.legendContainer.innerHTML="";const t=this.el("div","lt-legend",this.legendContainer);t.setAttribute("role","complementary"),t.setAttribute("aria-label","Support Legend"),["oss","ent","eol"].forEach(e=>{const i=this.el("div",`lt-legend-block ${e==="ent"?"commercial":e}`,t);i.classList.add("lt-legend-item-reactive"),this.activeHighlight===e&&i.classList.add("lt-active-highlight"),i.innerHTML=`
2
+ <div class="lt-legend-icon" aria-hidden="true"></div>
3
3
  <div><h3>${this.t(e)}</h3><p>${this.t(e+"Desc")}</p></div>
4
- `,s.onclick=()=>this.highlightSegment(e)})}highlightSegment(t){this.activeHighlight=this.activeHighlight===t?null:t,this.root.classList.remove("highlight-oss","highlight-ent","highlight-eol"),this.activeHighlight&&this.root.classList.add(`highlight-${this.activeHighlight}`),this.renderLegend()}renderAxis(){this.el("div","",this.axis,{width:"96px",flexShrink:"0"}).setAttribute("role","presentation");for(let e=this.minYear;e<=this.maxYear;e++){const s=this.el("div","timeline-year",this.axis);s.textContent=e,s.setAttribute("role","columnheader")}}renderGrid(){const t=new Date(this.minYear,0,1).getTime(),e=new Date(this.maxYear,11,31).getTime()-t;for(let s=this.minYear;s<=this.maxYear;s++){const i=this.el("div","year-grid-line",this.grid);i.style.left=`${(new Date(s,0,1).getTime()-t)/e*100}%`,i.setAttribute("role","presentation")}}createRow(t,e){const s=this.el("div","timeline-row row-entrance",this.tracks);s.setAttribute("role","row"),s.style.transitionDelay=`${e*.05}s`;const i=this.el("div","version-label",s);i.setAttribute("role","rowheader");const n=Date.now(),o=new Date(t.ossStart).getTime(),a=new Date(t.ossEnd).getTime(),l=t.enterpriseEnd?new Date(t.enterpriseEnd).getTime():a,d=n>=o&&n<=a?"status-oss":n>a&&n<=l?"status-ent":n>l?"status-expired":"";if(d&&i.classList.add(d),t.releaseNotesUrl){const h=this.el("a","version-link",i);h.href=t.releaseNotesUrl,h.target="_blank",h.innerHTML=this.highlight(t.version),h.title=this.t("notes",{v:t.version}),h.setAttribute("aria-label",this.t("notes",{v:t.version}))}else i.innerHTML=this.highlight(t.version);const r=this.el("div","track-container",s);return r.setAttribute("role","gridcell"),r.appendChild(this.createBar(t,t.ossStart,t.enterpriseEnd||t.ossEnd,"bar-ent",this.t("ent"))),r.appendChild(this.createBar(t,t.ossStart,t.ossEnd,"bar-oss",this.t("oss"))),{el:s,version:t.version.toLowerCase(),versionOriginal:t.version}}createBar(t,e,s,i,n){const o=new Date(e).getTime(),a=new Date(s).getTime(),l=new Date(this.minYear,0,1).getTime(),d=new Date(this.maxYear,11,31).getTime()-l,r=this.el("div",`bar-segment ${i}`),h=i==="bar-oss"?"segment-oss":"segment-ent";r.classList.add(h),r.style.left=`${(o-l)/d*100}%`,r.style.width=`${(a-o)/d*100}%`,r.setAttribute("role","img"),r.setAttribute("aria-label",`${n}: ${t.version} (${e} to ${s})`),r.tabIndex=0;const u=`
5
- <div class="tooltip-header">${n} - ${t.version}</div>
6
- <div class="tooltip-date"><strong>Du:</strong> ${e}</div>
7
- <div class="tooltip-date"><strong>Au:</strong> ${s}</div>
8
- `;return r.onmouseenter=p=>this.showTooltip(p,u),r.onmousemove=p=>this.updateTooltipPos(p),r.onmouseleave=()=>this.hideTooltip(),r.onfocus=p=>{const b=r.getBoundingClientRect();this.showTooltip({pageX:b.left+window.scrollX,pageY:b.top+window.scrollY-40},u)},r.onblur=()=>this.hideTooltip(),r}updateVisibility(){const t=this.rows.filter(i=>i.version.includes(this.filterText));this.rows.forEach(i=>{const n=i.el.querySelector(".version-label"),o=n.querySelector(".version-link");o?o.innerHTML=this.highlight(i.versionOriginal||i.version):n.innerHTML=this.highlight(i.versionOriginal||i.version),i.el.classList.remove("row-visible"),i.el.classList.add("row-hidden")});const e=this.filterText===""&&t.length>this.visibleCount;if((e&&!this.isExpanded?t.slice(0,this.visibleCount):t).forEach(i=>{i.el.classList.remove("row-hidden"),i.el.classList.add("row-visible")}),this.showTable&&this.updateTableVisibility(),this.toggleContainer.innerHTML="",e){const i=this.el("button","timeline-toggle-btn",this.toggleContainer),n=`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="${this.isExpanded?"18 15 12 9 6 15":"6 9 12 15 18 9"}"></polyline></svg>`;i.innerHTML=this.isExpanded?`${this.t("less")} ${n}`:`${this.t("more",{n:t.length-this.visibleCount})} ${n}`,i.setAttribute("aria-expanded",this.isExpanded),i.onclick=()=>{this.isExpanded=!this.isExpanded,this.updateVisibility()}}}renderCurrentDateLine(){const t=new Date(this.minYear,0,1).getTime(),e=new Date(this.maxYear,11,31).getTime()-t,s=Date.now()-t;if(s<0||s>e)return;const i=new Date().toISOString().split("T")[0],n=this.el("div","current-date-indicator",this.indicators);n.style.left=`${s/e*100}%`,n.setAttribute("role","separator"),n.setAttribute("aria-label",this.t("today",{date:i}));const o=this.el("div","current-date-badge",n);o.textContent=i,o.setAttribute("aria-hidden","true")}}return g}));
4
+ `,i.onclick=()=>this.highlightSegment(e)})}highlightSegment(t){this.activeHighlight=this.activeHighlight===t?null:t,this.root.classList.remove("lt-highlight-oss","lt-highlight-ent","lt-highlight-eol"),this.activeHighlight&&this.root.classList.add(`lt-highlight-${this.activeHighlight}`),this.renderLegend()}renderAxis(){this.el("div","",this.axis,{width:"96px",flexShrink:"0"}).setAttribute("role","presentation");for(let e=this.minYear;e<=this.maxYear;e++){const i=this.el("div","lt-year",this.axis);i.textContent=e,i.setAttribute("role","columnheader")}}renderGrid(){const t=new Date(this.minYear,0,1).getTime(),e=new Date(this.maxYear,11,31).getTime()-t;for(let i=this.minYear;i<=this.maxYear;i++){const s=this.el("div","lt-year-grid-line",this.grid);s.style.left=`${(new Date(i,0,1).getTime()-t)/e*100}%`,s.setAttribute("role","presentation")}}createRow(t,e){const i=this.el("div","lt-row row-entrance",this.tracks);i.setAttribute("role","row"),i.style.transitionDelay=`${e*.05}s`;const s=this.el("div","lt-version-label",i);s.setAttribute("role","rowheader");const n=Date.now(),l=new Date(t.ossStart).getTime(),a=new Date(t.ossEnd).getTime(),r=t.enterpriseEnd?new Date(t.enterpriseEnd).getTime():a,h=n>=l&&n<=a?"lt-status-oss":n>a&&n<=r?"lt-status-ent":n>r?"lt-status-expired":"";if(h&&s.classList.add(h),t.releaseNotesUrl){const d=this.el("a","lt-version-link",s);d.href=t.releaseNotesUrl,d.target="_blank",d.innerHTML=this.highlight(t.version),d.title=this.t("notes",{v:t.version}),d.setAttribute("aria-label",this.t("notes",{v:t.version}))}else s.innerHTML=this.highlight(t.version);const o=this.el("div","lt-track-container",i);o.setAttribute("role","gridcell");const g=this.splitSupport?t.ossEnd:t.ossStart,c=t.enterpriseEnd||t.ossEnd,p=new Date(this.maxYear,11,31).toISOString().split("T")[0];return o.appendChild(this.createBar(t,c,p,"lt-bar-eol",this.t("eol"))),o.appendChild(this.createBar(t,g,t.enterpriseEnd||t.ossEnd,"lt-bar-ent",this.t("ent"))),o.appendChild(this.createBar(t,t.ossStart,t.ossEnd,"lt-bar-oss",this.t("oss"))),{el:i,version:t.version.toLowerCase(),versionOriginal:t.version}}createBar(t,e,i,s,n){const l=new Date(e).getTime(),a=new Date(i).getTime(),r=new Date(this.minYear,0,1).getTime(),h=new Date(this.maxYear,11,31).getTime()-r,o=this.el("div",`lt-bar-segment ${s}`),g=s==="lt-bar-oss"?"lt-segment-oss":s==="lt-bar-ent"?"lt-segment-ent":"lt-segment-eol";o.classList.add(g),o.style.left=`${(l-r)/h*100}%`,o.style.width=`${(a-l)/h*100}%`,o.setAttribute("role","img"),o.setAttribute("aria-label",`${n}: ${t.version} (${e} to ${i})`),o.tabIndex=0;const c=`
5
+ <div class="lt-tooltip-header">${n} - ${t.version}</div>
6
+ <div class="lt-tooltip-date"><strong>Du:</strong> ${e}</div>
7
+ <div class="lt-tooltip-date"><strong>Au:</strong> ${i}</div>
8
+ `;return o.onmouseenter=p=>this.showTooltip(p,c),o.onmousemove=p=>this.updateTooltipPos(p),o.onmouseleave=()=>this.hideTooltip(),o.onfocus=p=>{const d=o.getBoundingClientRect();this.showTooltip({pageX:d.left+window.scrollX,pageY:d.top+window.scrollY-40},c)},o.onblur=()=>this.hideTooltip(),o}updateVisibility(){const t=this.rows.filter(s=>s.version.includes(this.filterText));this.rows.forEach(s=>{const n=s.el.querySelector(".lt-version-label"),l=n.querySelector(".lt-version-link");l?l.innerHTML=this.highlight(s.versionOriginal||s.version):n.innerHTML=this.highlight(s.versionOriginal||s.version),s.el.classList.remove("lt-row-visible"),s.el.classList.add("lt-row-hidden")});const e=this.filterText===""&&t.length>this.visibleCount;if((e&&!this.isExpanded?t.slice(0,this.visibleCount):t).forEach(s=>{s.el.classList.remove("lt-row-hidden"),s.el.classList.add("lt-row-visible")}),this.showTable&&this.updateTableVisibility(),this.toggleContainer.innerHTML="",e){const s=this.el("button","lt-toggle-btn",this.toggleContainer),n=`<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><polyline points="${this.isExpanded?"18 15 12 9 6 15":"6 9 12 15 18 9"}"></polyline></svg>`;s.innerHTML=this.isExpanded?`${this.t("less")} ${n}`:`${this.t("more",{n:t.length-this.visibleCount})} ${n}`,s.setAttribute("aria-expanded",this.isExpanded),s.onclick=()=>{this.isExpanded=!this.isExpanded,this.updateVisibility()}}}renderCurrentDateLine(){const t=new Date(this.minYear,0,1).getTime(),e=new Date(this.maxYear,11,31).getTime()-t,i=Date.now()-t;if(i<0||i>e)return;const s=new Date().toISOString().split("T")[0],n=this.el("div","lt-current-date-indicator",this.indicators);n.style.left=`${i/e*100}%`,n.setAttribute("role","separator"),n.setAttribute("aria-label",this.t("today",{date:s}));const l=this.el("div","lt-current-date-badge",n);l.textContent=s,l.setAttribute("aria-hidden","true")}}return b}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lifecycle-timeline",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "A beautiful, premium JS timeline component for visualizing product lifecycles, OSS support, and enterprise support dates.",
5
5
  "type": "module",
6
6
  "main": "./dist/timeline.umd.cjs",
package/src/index.d.ts CHANGED
@@ -29,6 +29,24 @@ export interface TimelineOptions {
29
29
  * @default true
30
30
  */
31
31
  showTable?: boolean;
32
+
33
+ /**
34
+ * Whether to show the legend below the timeline.
35
+ * @default true
36
+ */
37
+ showLegend?: boolean;
38
+
39
+ /**
40
+ * Whether to show the version filter input.
41
+ * @default true
42
+ */
43
+ filterVersions?: boolean;
44
+
45
+ /**
46
+ * Whether to show the enterprise bar normally (false) or split after OSS (true).
47
+ * @default false
48
+ */
49
+ splitSupport?: boolean;
32
50
  }
33
51
 
34
52
  export default class Timeline {