Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 80011e6de5 | |||
| d651cfbe2c | |||
| bdf3ad085f | |||
| 5caaf38e9a | |||
| 7630da8abd | |||
| 45b968b77d | |||
| ca2d6d225e | |||
| 8b54efef61 | |||
| e8c1197613 | |||
| b84d71dd7a | |||
| d08007cdd7 | |||
| fdcadad23b |
@@ -175,7 +175,16 @@
|
||||
--z-popover: 10012;
|
||||
--z-tooltip: 10013;
|
||||
--z-toast: 10014;
|
||||
--z-panel: 10020; /* notification / generic dropdown panels — above everything */
|
||||
--z-overlay: 9999; /* scanlines / CRT effects */
|
||||
|
||||
/* --- Corner cuts (clip-path polygon notches) --- */
|
||||
--corner-cut: 8px;
|
||||
--corner-cut-sm: 5px;
|
||||
--corner-cut-lg: 16px;
|
||||
|
||||
/* --- Syntax highlight --- */
|
||||
--color-tok-cmt: #5c8c6a;
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +227,10 @@ a:hover {
|
||||
color: var(--accent-orange);
|
||||
text-shadow: var(--glow-orange);
|
||||
}
|
||||
a:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
ul, ol { list-style: none; }
|
||||
img, svg { display: block; max-width: 100%; }
|
||||
@@ -478,6 +491,7 @@ hr {
|
||||
background: var(--accent-cyan-dim);
|
||||
}
|
||||
.lt-nav-link:hover::after { left: 0; right: 0; box-shadow: var(--glow-cyan); }
|
||||
.lt-nav-link:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; color: var(--accent-cyan); }
|
||||
|
||||
.lt-nav-link.active {
|
||||
color: var(--accent-orange);
|
||||
@@ -533,6 +547,11 @@ hr {
|
||||
text-shadow: var(--glow-orange);
|
||||
padding-left: 1.1rem;
|
||||
}
|
||||
.lt-nav-dropdown-menu li a:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: -2px;
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
|
||||
/* Header user + admin badge */
|
||||
.lt-header-user {
|
||||
@@ -763,6 +782,12 @@ hr {
|
||||
|
||||
.lt-btn:active { transform: translateY(1px); }
|
||||
|
||||
.lt-btn:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
box-shadow: var(--box-glow-cyan);
|
||||
}
|
||||
|
||||
.lt-btn:disabled,
|
||||
.lt-btn[disabled] {
|
||||
opacity: 0.30;
|
||||
@@ -859,10 +884,27 @@ hr {
|
||||
|
||||
.lt-input:focus,
|
||||
.lt-select:focus,
|
||||
.lt-textarea:focus {
|
||||
.lt-textarea:focus,
|
||||
.lt-input:focus-visible,
|
||||
.lt-select:focus-visible,
|
||||
.lt-textarea:focus-visible {
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: var(--box-glow-cyan);
|
||||
background: rgba(0,212,255,0.025);
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.lt-input:disabled,
|
||||
.lt-select:disabled,
|
||||
.lt-textarea:disabled,
|
||||
.lt-input[readonly],
|
||||
.lt-textarea[readonly] {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-muted);
|
||||
border-color: var(--border-dim);
|
||||
}
|
||||
|
||||
.lt-input::placeholder,
|
||||
@@ -949,6 +991,47 @@ select option:checked {
|
||||
text-shadow: var(--glow-cyan);
|
||||
}
|
||||
.lt-checkbox:hover { border-color: var(--accent-cyan); }
|
||||
.lt-checkbox:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.lt-checkbox:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Radio (same visual language as checkbox) */
|
||||
.lt-radio {
|
||||
appearance: none;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1px solid var(--accent-cyan-border);
|
||||
border-radius: 50%;
|
||||
background: var(--bg-terminal);
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
vertical-align: middle;
|
||||
transition: var(--transition-fast);
|
||||
position: relative;
|
||||
}
|
||||
.lt-radio:checked {
|
||||
border-color: var(--accent-cyan);
|
||||
background: var(--accent-cyan-dim);
|
||||
}
|
||||
.lt-radio:checked::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 3px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-cyan);
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
.lt-radio:hover { border-color: var(--accent-cyan); }
|
||||
.lt-radio:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.lt-radio:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@@ -985,6 +1068,7 @@ select option:checked {
|
||||
color: var(--accent-orange);
|
||||
text-shadow: var(--glow-orange);
|
||||
}
|
||||
.lt-table th[data-sort-key]:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; }
|
||||
|
||||
.lt-table td {
|
||||
padding: 0.55rem 0.85rem;
|
||||
@@ -1055,7 +1139,7 @@ select option:checked {
|
||||
.lt-status-closed { color: var(--status-closed); background: rgba(255,45,85,0.07); }
|
||||
.lt-status-online { color: var(--status-online); background: rgba(0,255,136,0.07); text-shadow: var(--glow-green); }
|
||||
.lt-status-offline { color: var(--status-offline); background: rgba(255,45,85,0.07); }
|
||||
.lt-status-running { color: var(--status-running); background: rgba(255,179,0,0.07); animation: pulse-amber 2s infinite; }
|
||||
.lt-status-running { color: var(--status-running); background: rgba(255,179,0,0.07); animation: pulse-amber 2s infinite; will-change: color; }
|
||||
.lt-status-completed { color: var(--status-completed); background: rgba(0,212,255,0.07); }
|
||||
.lt-status-failed { color: var(--status-failed); background: rgba(255,45,85,0.07); }
|
||||
|
||||
@@ -1107,6 +1191,17 @@ select option:checked {
|
||||
.lt-badge-amber { color: var(--accent-amber); }
|
||||
.lt-badge-red { color: var(--accent-red); }
|
||||
|
||||
/* Status + priority badge variants (dark-mode base) */
|
||||
.lt-badge-open { color: var(--accent-green); background: rgba(0,255,136,0.08); border-color: rgba(0,255,136,0.35); text-shadow: var(--glow-green); }
|
||||
.lt-badge-closed { color: var(--text-muted); background: rgba(74,90,112,0.10); border-color: rgba(74,90,112,0.35); }
|
||||
.lt-badge-in-progress,
|
||||
.lt-badge-progress { color: var(--accent-amber); background: rgba(255,179,0,0.08); border-color: rgba(255,179,0,0.35); }
|
||||
.lt-badge-pending { color: var(--accent-purple); background: rgba(191,95,255,0.08); border-color: rgba(191,95,255,0.35); }
|
||||
.lt-badge-p1 { color: var(--accent-red); background: rgba(255,45,85,0.09); border-color: rgba(255,45,85,0.40); text-shadow: var(--glow-red); }
|
||||
.lt-badge-p2 { color: var(--accent-orange); background: rgba(255,107,0,0.09); border-color: rgba(255,107,0,0.38); }
|
||||
.lt-badge-p3 { color: var(--accent-cyan); background: rgba(0,212,255,0.07); border-color: rgba(0,212,255,0.30); }
|
||||
.lt-badge-p4 { color: var(--accent-green); background: rgba(0,255,136,0.07); border-color: rgba(0,255,136,0.30); }
|
||||
|
||||
/* Status dots */
|
||||
.lt-dot {
|
||||
display: inline-block;
|
||||
@@ -1115,9 +1210,9 @@ select option:checked {
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.lt-dot-up { background: var(--accent-green); box-shadow: 0 0 6px var(--accent-green), 0 0 14px var(--accent-green); animation: pulse-green 2.2s ease-in-out infinite; }
|
||||
.lt-dot-up { background: var(--accent-green); box-shadow: 0 0 6px var(--accent-green), 0 0 14px var(--accent-green); animation: pulse-green 2.2s ease-in-out infinite; will-change: box-shadow; }
|
||||
.lt-dot-down { background: var(--accent-red); box-shadow: 0 0 6px var(--accent-red), 0 0 12px var(--accent-red); }
|
||||
.lt-dot-warn { background: var(--accent-amber); box-shadow: 0 0 6px var(--accent-amber), 0 0 12px var(--accent-amber); animation: pulse-amber 2.2s ease-in-out infinite; }
|
||||
.lt-dot-warn { background: var(--accent-amber); box-shadow: 0 0 6px var(--accent-amber), 0 0 12px var(--accent-amber); animation: pulse-amber 2.2s ease-in-out infinite; will-change: box-shadow; }
|
||||
.lt-dot-idle { background: var(--text-muted); box-shadow: none; }
|
||||
|
||||
|
||||
@@ -1193,7 +1288,9 @@ select option:checked {
|
||||
padding: 0.2rem 0.4rem;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.lt-modal-close:hover { color: var(--accent-red); text-shadow: var(--glow-red); }
|
||||
.lt-modal-close:hover { color: var(--accent-red); text-shadow: var(--glow-red); }
|
||||
.lt-modal-close:active { color: var(--accent-red); opacity: 0.7; }
|
||||
.lt-modal-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
|
||||
.lt-modal-body {
|
||||
padding: var(--space-lg);
|
||||
@@ -1292,6 +1389,10 @@ select option:checked {
|
||||
border-color: var(--accent-orange-border);
|
||||
border-bottom-color: var(--bg-card);
|
||||
}
|
||||
.lt-tab:focus-visible {
|
||||
outline: 2px solid var(--accent-orange);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.lt-tab-panel { display: none; }
|
||||
.lt-tab-panel.active { display: block; }
|
||||
@@ -1334,13 +1435,16 @@ select option:checked {
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.lt-sidebar-toggle:hover { color: var(--accent-cyan); text-shadow: var(--glow-cyan); }
|
||||
.lt-sidebar-toggle:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
|
||||
.lt-sidebar-body { padding: var(--space-md); }
|
||||
|
||||
.lt-filter-group {
|
||||
margin-bottom: var(--space-md);
|
||||
padding-bottom: var(--space-md);
|
||||
padding: 0 0 var(--space-md) 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border-color-dim);
|
||||
min-width: 0; /* fieldset UA reset */
|
||||
}
|
||||
.lt-filter-group:last-child { border-bottom: none; }
|
||||
|
||||
@@ -1403,11 +1507,13 @@ select option:checked {
|
||||
}
|
||||
|
||||
.lt-stat-card:hover,
|
||||
.lt-stat-card.active {
|
||||
.lt-stat-card.active,
|
||||
.lt-stat-card:focus-visible {
|
||||
background: var(--bg-tertiary);
|
||||
border-color: var(--accent-cyan-border);
|
||||
box-shadow: var(--box-glow-cyan);
|
||||
}
|
||||
.lt-stat-card:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; }
|
||||
.lt-stat-card:hover::before,
|
||||
.lt-stat-card.active::before { height: 100%; }
|
||||
|
||||
@@ -1700,6 +1806,7 @@ select option:checked {
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
box-shadow: var(--glow-cyan);
|
||||
}
|
||||
.lt-menu-btn:active { opacity: 0.7; }
|
||||
.lt-menu-btn:focus-visible { outline: 1px solid var(--accent-cyan); outline-offset: 2px; }
|
||||
.lt-menu-btn.open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
|
||||
.lt-menu-btn.open span:nth-child(2) { opacity: 0; }
|
||||
@@ -2195,7 +2302,35 @@ select option:checked {
|
||||
.lt-text-upper { text-transform: uppercase; letter-spacing: 0.1em; }
|
||||
|
||||
.lt-hidden { display: none !important; }
|
||||
.lt-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); border: 0; }
|
||||
|
||||
/* Skip navigation link — visible only on focus */
|
||||
.lt-skip-link {
|
||||
position: absolute;
|
||||
top: -100%;
|
||||
left: var(--space-sm);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
background: var(--bg-terminal);
|
||||
color: var(--accent-cyan);
|
||||
border: 1px solid var(--accent-cyan);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
z-index: var(--z-toast);
|
||||
transition: top 0.1s;
|
||||
}
|
||||
.lt-skip-link:focus, .lt-skip-link:focus-visible { top: var(--space-sm); }
|
||||
|
||||
.lt-sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%); /* replaces deprecated clip: rect(0,0,0,0) */
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Cursor blink */
|
||||
.lt-cursor::after {
|
||||
@@ -2206,6 +2341,9 @@ select option:checked {
|
||||
font-size: 0.85em;
|
||||
margin-left: 1px;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.lt-cursor::after { animation: none; }
|
||||
}
|
||||
|
||||
/* Glitch text effect */
|
||||
.lt-glitch { position: relative; }
|
||||
@@ -2221,11 +2359,13 @@ select option:checked {
|
||||
color: var(--accent-cyan);
|
||||
opacity: 0.65;
|
||||
animation: glitch-1 4s infinite;
|
||||
will-change: clip-path, transform;
|
||||
}
|
||||
.lt-glitch::after {
|
||||
color: var(--accent-orange);
|
||||
opacity: 0.65;
|
||||
animation: glitch-2 4s 0.12s infinite;
|
||||
will-change: clip-path, transform;
|
||||
}
|
||||
|
||||
|
||||
@@ -2347,7 +2487,9 @@ select option:checked {
|
||||
transform: translateX(-50%) translateY(4px);
|
||||
}
|
||||
[data-tooltip]:hover::before,
|
||||
[data-tooltip]:hover::after {
|
||||
[data-tooltip]:focus-visible::before,
|
||||
[data-tooltip]:hover::after,
|
||||
[data-tooltip]:focus-visible::after {
|
||||
opacity: 1;
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
@@ -2365,7 +2507,9 @@ select option:checked {
|
||||
transform: translateX(-50%) translateY(-4px);
|
||||
}
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:hover::before,
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:hover::after {
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::before,
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:hover::after,
|
||||
[data-tooltip][data-tooltip-pos="bottom"]:focus-visible::after {
|
||||
transform: translateX(-50%) translateY(0);
|
||||
}
|
||||
|
||||
@@ -2389,6 +2533,7 @@ select option:checked {
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.lt-breadcrumb-item a:hover { color: var(--accent-cyan); }
|
||||
.lt-breadcrumb-item a:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
.lt-breadcrumb-item.active { color: var(--accent-orange); }
|
||||
.lt-breadcrumb-sep { color: var(--border-dim); }
|
||||
.lt-breadcrumb-sep::before { content: '/'; }
|
||||
@@ -2426,6 +2571,12 @@ select option:checked {
|
||||
box-shadow: var(--glow-orange);
|
||||
font-weight: 700;
|
||||
}
|
||||
.lt-page-btn:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 1px;
|
||||
border-color: var(--accent-cyan);
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
.lt-page-btn:disabled,
|
||||
.lt-page-btn[aria-disabled="true"] {
|
||||
opacity: 0.35;
|
||||
@@ -2458,7 +2609,9 @@ select option:checked {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
.lt-accordion-header:hover { background: var(--bg-tertiary); color: var(--accent-cyan); }
|
||||
.lt-accordion-header:hover { background: var(--bg-tertiary); color: var(--accent-cyan); }
|
||||
.lt-accordion-header:active { background: var(--bg-tertiary); opacity: 0.8; }
|
||||
.lt-accordion-header:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; }
|
||||
.lt-accordion-header[aria-expanded="true"] { color: var(--accent-orange); }
|
||||
.lt-accordion-icon {
|
||||
width: 14px;
|
||||
@@ -2515,6 +2668,7 @@ select option:checked {
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.lt-alert-close:hover { color: var(--accent-red); }
|
||||
.lt-alert-close:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
.lt-alert.dismissed { max-height: 0 !important; opacity: 0; padding-top: 0; padding-bottom: 0; pointer-events: none; }
|
||||
|
||||
|
||||
@@ -2529,7 +2683,21 @@ select option:checked {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.lt-toggle input { display: none; }
|
||||
/* Visually hidden but still keyboard-focusable */
|
||||
.lt-toggle input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 1px; height: 1px;
|
||||
margin: -1px; padding: 0;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lt-toggle input:focus-visible ~ .lt-toggle-track {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
.lt-toggle-track {
|
||||
width: 36px;
|
||||
height: 18px;
|
||||
@@ -2573,9 +2741,17 @@ input[type="range"].lt-range {
|
||||
height: 4px;
|
||||
background: var(--bg-tertiary);
|
||||
border: 1px solid var(--border-dim);
|
||||
outline: none;
|
||||
outline: none; /* reset; :focus-visible below provides keyboard ring */
|
||||
cursor: pointer;
|
||||
}
|
||||
input[type="range"].lt-range:focus-visible {
|
||||
outline: 2px solid var(--accent-orange);
|
||||
outline-offset: 4px;
|
||||
}
|
||||
input[type="range"].lt-range:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
input[type="range"].lt-range::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 14px; height: 14px;
|
||||
@@ -2586,6 +2762,7 @@ input[type="range"].lt-range::-webkit-slider-thumb {
|
||||
transition: transform 0.15s;
|
||||
}
|
||||
input[type="range"].lt-range::-webkit-slider-thumb:hover { transform: scale(1.3); }
|
||||
input[type="range"].lt-range:focus-visible::-webkit-slider-thumb { transform: scale(1.3); box-shadow: 0 0 0 3px var(--bg-primary), 0 0 0 5px var(--accent-cyan); }
|
||||
input[type="range"].lt-range::-moz-range-thumb {
|
||||
width: 14px; height: 14px;
|
||||
background: var(--accent-orange);
|
||||
@@ -2608,6 +2785,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
position: relative;
|
||||
clip-path: polygon(0 0, calc(100% - 16px) 0, 100% 16px, 100% 100%, 16px 100%, 0 calc(100% - 16px));
|
||||
}
|
||||
.lt-dropzone:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; }
|
||||
.lt-dropzone:hover,
|
||||
.lt-dropzone.drag-over {
|
||||
border-color: var(--accent-cyan);
|
||||
@@ -2638,6 +2816,8 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
.lt-file-item-name { color: var(--text-secondary); }
|
||||
.lt-file-item-size { color: var(--text-dim); }
|
||||
.lt-file-item-remove { background: none; border: none; color: var(--accent-red); cursor: pointer; padding: 0; font-size: 0.9rem; }
|
||||
.lt-file-item-remove:hover { opacity: 0.75; text-shadow: var(--glow-red); }
|
||||
.lt-file-item-remove:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@@ -2681,6 +2861,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
font-size: 0.9rem;
|
||||
caret-color: var(--accent-orange);
|
||||
}
|
||||
.lt-cmd-input-wrap:focus-within { border-bottom-color: var(--accent-cyan); }
|
||||
.lt-cmd-results { max-height: 320px; overflow-y: auto; }
|
||||
.lt-cmd-group-label {
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
@@ -2702,7 +2883,8 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.lt-cmd-item:hover,
|
||||
.lt-cmd-item.is-selected {
|
||||
.lt-cmd-item.is-selected,
|
||||
.lt-cmd-item:focus-visible {
|
||||
background: rgba(0,212,255,0.08);
|
||||
color: var(--accent-cyan);
|
||||
}
|
||||
@@ -2771,6 +2953,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
transition: all 0.15s;
|
||||
}
|
||||
.lt-code-copy:hover { border-color: var(--accent-cyan); color: var(--accent-cyan); }
|
||||
.lt-code-copy:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; }
|
||||
.lt-code-copy.copied { border-color: var(--accent-green); color: var(--accent-green); }
|
||||
.lt-code-block pre {
|
||||
margin: 0;
|
||||
@@ -2788,7 +2971,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
.tok-kw { color: var(--accent-cyan); }
|
||||
.tok-str { color: var(--accent-green); }
|
||||
.tok-num { color: var(--accent-orange); }
|
||||
.tok-cmt { color: #5c8c6a; font-style: italic; }
|
||||
.tok-cmt { color: var(--color-tok-cmt); font-style: italic; }
|
||||
.tok-fn { color: var(--accent-purple); }
|
||||
|
||||
|
||||
@@ -2827,6 +3010,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
line-height: 1;
|
||||
}
|
||||
.lt-tag-remove:hover { opacity: 1; }
|
||||
.lt-tag-remove:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; opacity: 1; }
|
||||
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
@@ -2948,6 +3132,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
flex: 1;
|
||||
}
|
||||
.lt-list-item a:hover { color: var(--accent-cyan); }
|
||||
.lt-list-item a:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
.lt-list-item-meta { color: var(--text-dim); font-size: 0.7rem; margin-left: auto; }
|
||||
.lt-list-item--active { background: rgba(255,107,0,0.06); border-left: 2px solid var(--accent-orange); }
|
||||
|
||||
@@ -3113,7 +3298,8 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
.lt-gauge svg { overflow: visible; }
|
||||
.lt-gauge-track { fill: none; stroke: var(--bg-tertiary); stroke-width: 8; }
|
||||
.lt-gauge-fill { fill: none; stroke: var(--accent-orange); stroke-width: 8; stroke-linecap: butt;
|
||||
transition: stroke-dashoffset 0.6s cubic-bezier(0.4,0,0.2,1); }
|
||||
transition: stroke-dashoffset 0.6s cubic-bezier(0.4,0,0.2,1);
|
||||
will-change: stroke-dashoffset; }
|
||||
.lt-gauge-label {
|
||||
position: absolute;
|
||||
bottom: 0; left: 50%;
|
||||
@@ -3138,6 +3324,7 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
border-top-color: var(--accent-orange);
|
||||
border-radius: 50%;
|
||||
animation: lt-spin 0.7s linear infinite;
|
||||
will-change: transform;
|
||||
}
|
||||
.lt-spinner--cyan { border-top-color: var(--accent-cyan); }
|
||||
.lt-spinner--green { border-top-color: var(--accent-green); }
|
||||
@@ -3247,7 +3434,6 @@ input[type="range"].lt-range::-moz-range-thumb {
|
||||
.lt-border-red { border-color: var(--accent-red) !important; }
|
||||
|
||||
/* Display */
|
||||
.lt-hidden { display: none !important; }
|
||||
.lt-visible { display: block !important; }
|
||||
.lt-flex { display: flex; }
|
||||
.lt-grid { display: grid; }
|
||||
@@ -3404,6 +3590,9 @@ html[data-theme="light"] {
|
||||
--box-glow-red: 0 0 0 2px rgba(181,0,31,0.22), 0 2px 8px rgba(181,0,31,0.12);
|
||||
--box-glow-amber: 0 0 0 2px rgba(138,90,0,0.22), 0 2px 8px rgba(138,90,0,0.12);
|
||||
|
||||
/* — Syntax highlight — */
|
||||
--color-tok-cmt: #2e6540;
|
||||
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@@ -3442,6 +3631,7 @@ html[data-theme="light"] .lt-nav-link.active { color: var(--accent-orange
|
||||
html[data-theme="light"] .lt-nav-drawer-link { color: var(--text-secondary); }
|
||||
html[data-theme="light"] .lt-nav-drawer-link:hover { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
html[data-theme="light"] .lt-nav-drawer-link.active { color: var(--accent-orange); background: var(--accent-orange-dim); }
|
||||
html[data-theme="light"] .lt-nav-drawer-link:focus-visible { outline: none; color: var(--accent-cyan); background: var(--accent-cyan-dim); box-shadow: inset 3px 0 0 var(--accent-cyan); }
|
||||
html[data-theme="light"] .lt-sidebar-nav-link { color: var(--text-secondary); }
|
||||
html[data-theme="light"] .lt-sidebar-nav-link:hover { background: var(--accent-cyan-dim); }
|
||||
html[data-theme="light"] .lt-sidebar-nav-link.active { background: var(--accent-orange-dim); color: var(--accent-orange); }
|
||||
@@ -3472,9 +3662,9 @@ html[data-theme="light"] .lt-textarea {
|
||||
color: var(--text-primary);
|
||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
|
||||
}
|
||||
html[data-theme="light"] .lt-input:focus,
|
||||
html[data-theme="light"] .lt-select:focus,
|
||||
html[data-theme="light"] .lt-textarea:focus {
|
||||
html[data-theme="light"] .lt-input:focus-visible,
|
||||
html[data-theme="light"] .lt-select:focus-visible,
|
||||
html[data-theme="light"] .lt-textarea:focus-visible {
|
||||
border-color: var(--accent-cyan);
|
||||
box-shadow: var(--box-glow-cyan);
|
||||
}
|
||||
@@ -3504,13 +3694,16 @@ html[data-theme="light"] .lt-table-wrap { border-color: var(--border-color);
|
||||
|
||||
/* — Badges & status — */
|
||||
html[data-theme="light"] .lt-badge { background: var(--bg-tertiary); color: var(--text-secondary); }
|
||||
html[data-theme="light"] .lt-badge-open { background: rgba(0,109,53,0.12); color: #006d35; }
|
||||
html[data-theme="light"] .lt-badge-closed { background: rgba(90,110,150,0.12); color: #5a6e8c; }
|
||||
html[data-theme="light"] .lt-badge-p1 { background: rgba(181,0,31,0.12); color: #b5001f; }
|
||||
html[data-theme="light"] .lt-badge-p2 { background: rgba(196,78,0,0.12); color: #c44e00; }
|
||||
html[data-theme="light"] .lt-badge-p3 { background: rgba(138,90,0,0.12); color: #8a5a00; }
|
||||
html[data-theme="light"] .lt-badge-p4 { background: rgba(50,80,130,0.10); color: #2d3d56; }
|
||||
html[data-theme="light"] .lt-badge-admin { background: rgba(107,47,184,0.12); color: #6b2fb8; }
|
||||
html[data-theme="light"] .lt-badge-open { background: rgba(0,109,53,0.12); color: #006d35; }
|
||||
html[data-theme="light"] .lt-badge-closed { background: rgba(90,110,150,0.12); color: #5a6e8c; }
|
||||
html[data-theme="light"] .lt-badge-in-progress,
|
||||
html[data-theme="light"] .lt-badge-progress { background: rgba(138,90,0,0.12); color: #8a5a00; text-shadow: none; }
|
||||
html[data-theme="light"] .lt-badge-pending { background: rgba(107,47,184,0.10); color: #7c22cc; }
|
||||
html[data-theme="light"] .lt-badge-p1 { background: rgba(181,0,31,0.12); color: #b5001f; text-shadow: none; }
|
||||
html[data-theme="light"] .lt-badge-p2 { background: rgba(196,78,0,0.12); color: #c44e00; }
|
||||
html[data-theme="light"] .lt-badge-p3 { background: rgba(138,90,0,0.12); color: #8a5a00; }
|
||||
html[data-theme="light"] .lt-badge-p4 { background: rgba(50,80,130,0.10); color: #2d3d56; }
|
||||
html[data-theme="light"] .lt-badge-admin { background: rgba(107,47,184,0.12); color: #6b2fb8; }
|
||||
|
||||
/* — Toast — */
|
||||
html[data-theme="light"] .lt-toast {
|
||||
@@ -3614,8 +3807,8 @@ html[data-theme="light"] .lt-divider-label { color: var(--text-dim); }
|
||||
html[data-theme="light"] .lt-tag { background: var(--bg-tertiary); border-color: var(--border-color); color: var(--text-secondary); }
|
||||
|
||||
/* — Tooltips — */
|
||||
html[data-theme="light"] [data-tooltip]::before { background: #1a2035; color: #fff; }
|
||||
html[data-theme="light"] [data-tooltip]::after { border-top-color: #1a2035; }
|
||||
html[data-theme="light"] [data-tooltip]::before { background: var(--bg-terminal); color: var(--text-primary); }
|
||||
html[data-theme="light"] [data-tooltip]::after { border-top-color: var(--bg-terminal); }
|
||||
|
||||
/* — Pagination — */
|
||||
html[data-theme="light"] .lt-page-btn { background: var(--bg-card); border-color: var(--border-color); color: var(--text-secondary); }
|
||||
@@ -3701,7 +3894,9 @@ html[data-theme="light"] .lt-empty-state-title { color: var(--text-secondary); }
|
||||
html[data-theme="light"] .lt-combobox-dropdown,
|
||||
html[data-theme="light"] .lt-typeahead-dropdown { background: var(--bg-card); border-color: var(--border-color); box-shadow: 0 4px 16px rgba(0,0,0,0.1); }
|
||||
html[data-theme="light"] .lt-combobox-option:hover,
|
||||
html[data-theme="light"] .lt-typeahead-option:hover { background: var(--accent-cyan-dim); }
|
||||
html[data-theme="light"] .lt-typeahead-item:hover,
|
||||
html[data-theme="light"] .lt-typeahead-item.is-focused,
|
||||
html[data-theme="light"] .lt-typeahead-item:focus-visible { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
html[data-theme="light"] .lt-combobox-tag { background: var(--accent-cyan-dim); color: var(--accent-cyan); border-color: var(--accent-cyan-border); }
|
||||
|
||||
/* — Sortable ghost — */
|
||||
@@ -3766,6 +3961,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
|
||||
animation: lt-shimmer 1.6s ease-in-out infinite;
|
||||
border-radius: 2px;
|
||||
display: block;
|
||||
will-change: opacity;
|
||||
}
|
||||
@keyframes lt-shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
@@ -3941,7 +4137,8 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
|
||||
border-radius: 2px;
|
||||
transition: var(--transition-fast);
|
||||
}
|
||||
.lt-drawer-right-close:hover { color: var(--accent-red); border-color: var(--accent-red); }
|
||||
.lt-drawer-right-close:hover { color: var(--accent-red); border-color: var(--accent-red); }
|
||||
.lt-drawer-right-close:active { color: var(--accent-red); opacity: 0.7; }
|
||||
.lt-drawer-right-close:focus-visible { outline: 1px solid var(--accent-cyan); outline-offset: 2px; }
|
||||
.lt-drawer-right-body {
|
||||
flex: 1;
|
||||
@@ -4056,6 +4253,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
|
||||
display: flex; align-items: center;
|
||||
}
|
||||
.lt-combobox-tag-remove:hover { color: var(--accent-red); }
|
||||
.lt-combobox-tag-remove:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; }
|
||||
/* Dropdown list */
|
||||
.lt-combobox-dropdown {
|
||||
position: absolute;
|
||||
@@ -4081,7 +4279,8 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.lt-combobox-option:hover,
|
||||
.lt-combobox-option.is-focused { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-combobox-option.is-focused,
|
||||
.lt-combobox-option:focus-visible { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-combobox-option.is-selected::before {
|
||||
content: '✓';
|
||||
color: var(--accent-green);
|
||||
@@ -4126,6 +4325,7 @@ html[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: var(--acc
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lt-context-menu-item:hover { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-context-menu-item:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-context-menu-item.is-danger:hover { background: var(--accent-red-dim); color: var(--accent-red); }
|
||||
.lt-context-menu-item .icon { width: 1rem; text-align: center; opacity: 0.7; font-size: 0.75rem; }
|
||||
.lt-context-menu-item kbd {
|
||||
@@ -4337,6 +4537,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
}
|
||||
.lt-split-divider:hover,
|
||||
.lt-split-divider.is-dragging { background: var(--accent-cyan); }
|
||||
.lt-split-divider:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 3px; }
|
||||
.lt-split--vertical .lt-split-divider::before { top: -6px; bottom: -6px; left: 0; right: 0; cursor: row-resize; }
|
||||
.lt-split--vertical .lt-split-divider { cursor: row-resize; }
|
||||
/* On mobile, stack vertically and hide divider */
|
||||
@@ -4481,7 +4682,8 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.lt-typeahead-item:hover,
|
||||
.lt-typeahead-item.is-focused { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-typeahead-item.is-focused,
|
||||
.lt-typeahead-item:focus-visible { background: var(--accent-cyan-dim); color: var(--accent-cyan); }
|
||||
.lt-typeahead-item mark {
|
||||
background: none;
|
||||
color: var(--accent-orange);
|
||||
@@ -4751,6 +4953,9 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
.lt-lightbox-close:hover,
|
||||
.lt-lightbox-prev:hover,
|
||||
.lt-lightbox-next:hover { color: var(--accent-cyan); border-color: var(--accent-cyan-border); box-shadow: var(--box-glow-cyan); }
|
||||
.lt-lightbox-close:focus-visible,
|
||||
.lt-lightbox-prev:focus-visible,
|
||||
.lt-lightbox-next:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; color: var(--accent-cyan); }
|
||||
.lt-lightbox-caption {
|
||||
position: fixed;
|
||||
bottom: 3rem;
|
||||
@@ -4798,6 +5003,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
border-radius: 2px;
|
||||
}
|
||||
.lt-sidebar-group-label:hover { color: var(--text-secondary); }
|
||||
.lt-sidebar-group-label:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; }
|
||||
.lt-sidebar-group-label .chevron {
|
||||
font-size: 0.5rem;
|
||||
transition: transform 0.2s ease;
|
||||
@@ -4828,6 +5034,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
white-space: nowrap;
|
||||
}
|
||||
.lt-sidebar-sub-link:hover { color: var(--accent-cyan); background: var(--accent-cyan-dim); }
|
||||
.lt-sidebar-sub-link:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 1px; border-radius: 2px; }
|
||||
.lt-sidebar-sub-link.active,
|
||||
.lt-sidebar-sub-link[aria-current="page"] {
|
||||
color: var(--accent-orange);
|
||||
@@ -4857,6 +5064,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
.lt-markdown hr { border: none; border-top: 1px solid var(--border-dim); margin: 1rem 0; }
|
||||
.lt-markdown a { color: var(--accent-cyan); text-decoration: none; }
|
||||
.lt-markdown a:hover { text-decoration: underline; }
|
||||
.lt-markdown a:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; }
|
||||
.lt-markdown strong { color: var(--text-primary); }
|
||||
.lt-markdown img { max-width: 100%; border: 1px solid var(--border-dim); }
|
||||
.lt-markdown table { width: 100%; border-collapse: collapse; font-size: 0.78rem; margin: 0.75rem 0; }
|
||||
@@ -4908,7 +5116,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
clip-path: polygon(0 0, calc(100% - 10px) 0, 100% 10px, 100% 100%, 0 100%);
|
||||
z-index: 10020;
|
||||
z-index: var(--z-panel);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,0.5);
|
||||
transform-origin: top right;
|
||||
transform: scale(0.95);
|
||||
@@ -4917,7 +5125,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
transition: opacity 0.15s ease, transform 0.15s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
.lt-notif-panel[aria-hidden="false"] {
|
||||
.lt-notif-panel:not([aria-hidden]) {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
@@ -4946,7 +5154,9 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.lt-notif-panel-clear:hover { text-decoration: underline; }
|
||||
.lt-notif-panel-clear:hover { text-decoration: underline; }
|
||||
.lt-notif-panel-clear:active { opacity: 0.7; }
|
||||
.lt-notif-panel-clear:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 2px; }
|
||||
|
||||
.lt-notif-panel-list { max-height: min(280px, calc(80vh - 110px)); overflow-y: auto; }
|
||||
|
||||
@@ -4960,6 +5170,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
transition: background 0.1s;
|
||||
}
|
||||
.lt-notif-item:hover { background: var(--bg-tertiary); }
|
||||
.lt-notif-item:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: -2px; background: var(--bg-tertiary); }
|
||||
.lt-notif-item--unread { background: rgba(0, 212, 255, 0.04); }
|
||||
|
||||
.lt-notif-dot {
|
||||
@@ -5015,7 +5226,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
clip-path: polygon(0 0, calc(100% - 8px) 0, 100% 8px, 100% 100%, 0 100%);
|
||||
z-index: 10020;
|
||||
z-index: var(--z-panel);
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
||||
transform-origin: top left;
|
||||
transform: scale(0.95);
|
||||
@@ -5028,7 +5239,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
right: 0;
|
||||
transform-origin: top right;
|
||||
}
|
||||
.lt-dropdown-panel[aria-hidden="false"] {
|
||||
.lt-dropdown-panel:not([aria-hidden]) {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
pointer-events: auto;
|
||||
@@ -5055,6 +5266,12 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.lt-dropdown-item:focus-visible {
|
||||
outline: 2px solid var(--accent-cyan);
|
||||
outline-offset: -2px;
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.lt-dropdown-item--danger { color: var(--accent-red); }
|
||||
.lt-dropdown-item--danger:hover { background: rgba(255,45,85,0.1); color: var(--accent-red); }
|
||||
|
||||
@@ -5250,3 +5467,24 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
||||
.lt-stats-grid { grid-template-columns: repeat(8, 1fr); max-width: 100%; }
|
||||
.lt-modal { max-width: 800px; }
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
79. FOOTER
|
||||
---------------------------------------------------------------- */
|
||||
.lt-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md) var(--space-lg);
|
||||
border-top: 1px solid var(--border-dim);
|
||||
margin-top: auto;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-muted);
|
||||
font-size: 0.7rem;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
@media (max-width: 479px) {
|
||||
.lt-footer { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
function showToast(message, type, duration) {
|
||||
type = type || 'info';
|
||||
duration = duration || 3500;
|
||||
if (_toastActive) { _toastQueue.push({ message, type, duration }); return; }
|
||||
if (_toastActive) { if (_toastQueue.length < 12) _toastQueue.push({ message, type, duration }); return; }
|
||||
_displayToast(message, type, duration);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@
|
||||
msgEl.textContent = message;
|
||||
|
||||
const closeEl = document.createElement('button');
|
||||
closeEl.type = 'button';
|
||||
closeEl.className = 'lt-toast-close';
|
||||
closeEl.textContent = '✕';
|
||||
closeEl.setAttribute('aria-label', 'Dismiss');
|
||||
@@ -214,7 +215,7 @@
|
||||
_modalTriggers.set(el, document.activeElement);
|
||||
}
|
||||
el.classList.add('is-open');
|
||||
el.setAttribute('aria-hidden', 'false');
|
||||
el.removeAttribute('aria-hidden'); /* removing is correct; setting 'false' is an anti-pattern */
|
||||
_lockScroll();
|
||||
// Focus first focusable element
|
||||
const first = el.querySelector(_FOCUSABLE);
|
||||
@@ -232,9 +233,14 @@
|
||||
_unlockScroll();
|
||||
// Remove trap handler
|
||||
if (el._ltTrapHandler) { el.removeEventListener('keydown', el._ltTrapHandler); delete el._ltTrapHandler; }
|
||||
// Return focus to trigger
|
||||
// Return focus to trigger (only if no other modal remains open)
|
||||
const trigger = _modalTriggers.get(el);
|
||||
if (trigger) { trigger.focus(); _modalTriggers.delete(el); }
|
||||
if (trigger) {
|
||||
_modalTriggers.delete(el);
|
||||
if (!document.querySelector('.lt-modal-overlay.is-open') && document.contains(trigger)) {
|
||||
trigger.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeAllModals() {
|
||||
@@ -263,22 +269,38 @@
|
||||
lt.tabs.switch('panel-id')
|
||||
---------------------------------------------------------------- */
|
||||
function switchTab(panelId) {
|
||||
document.querySelectorAll('.lt-tab').forEach(t => t.classList.remove('active'));
|
||||
document.querySelectorAll('.lt-tab[data-tab]').forEach(t => {
|
||||
t.classList.remove('active');
|
||||
t.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
document.querySelectorAll('.lt-tab-panel').forEach(p => p.classList.remove('active'));
|
||||
const btn = document.querySelector('.lt-tab[data-tab="' + panelId + '"]');
|
||||
const panel = document.getElementById(panelId);
|
||||
if (btn) btn.classList.add('active');
|
||||
if (btn) { btn.classList.add('active'); btn.setAttribute('aria-selected', 'true'); }
|
||||
if (panel) panel.classList.add('active');
|
||||
try { localStorage.setItem('lt_activeTab_' + location.pathname, panelId); } catch (_) {}
|
||||
}
|
||||
|
||||
let _tabsInitialized = false;
|
||||
function initTabs() {
|
||||
if (_tabsInitialized) return; _tabsInitialized = true;
|
||||
try {
|
||||
const saved = localStorage.getItem('lt_activeTab_' + location.pathname);
|
||||
if (saved && document.getElementById(saved)) { switchTab(saved); }
|
||||
} catch (_) {}
|
||||
document.querySelectorAll('.lt-tab[data-tab]').forEach(btn => {
|
||||
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
||||
document.querySelectorAll('[role="tablist"]').forEach(tablist => {
|
||||
const btns = Array.from(tablist.querySelectorAll('.lt-tab[data-tab]'));
|
||||
btns.forEach((btn, i) => {
|
||||
btn.addEventListener('click', () => switchTab(btn.dataset.tab));
|
||||
btn.addEventListener('keydown', e => {
|
||||
let idx = -1;
|
||||
if (e.key === 'ArrowRight') idx = (i + 1) % btns.length;
|
||||
else if (e.key === 'ArrowLeft') idx = (i - 1 + btns.length) % btns.length;
|
||||
else if (e.key === 'Home') idx = 0;
|
||||
else if (e.key === 'End') idx = btns.length - 1;
|
||||
if (idx >= 0) { e.preventDefault(); btns[idx].focus(); switchTab(btns[idx].dataset.tab); }
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -293,6 +315,7 @@
|
||||
function runBoot(appName, force) {
|
||||
const storageKey = 'lt_booted_' + (appName || 'app');
|
||||
if (!force && sessionStorage.getItem(storageKey)) return;
|
||||
sessionStorage.setItem(storageKey, '1'); // Claim the run immediately to block double-init
|
||||
const overlay = document.getElementById('lt-boot');
|
||||
const pre = document.getElementById('lt-boot-text');
|
||||
if (!overlay || !pre) return;
|
||||
@@ -338,7 +361,6 @@
|
||||
overlay.style.opacity = '0';
|
||||
setTimeout(() => { overlay.style.display = 'none'; overlay.style.opacity = ''; overlay.style.transition = ''; }, 520);
|
||||
}, 500);
|
||||
sessionStorage.setItem(storageKey, '1');
|
||||
}
|
||||
}, 65);
|
||||
}
|
||||
@@ -392,7 +414,9 @@
|
||||
----------------------------------------------------------------
|
||||
lt.sidebar.init()
|
||||
---------------------------------------------------------------- */
|
||||
let _sidebarInitialized = false;
|
||||
function initSidebar() {
|
||||
if (_sidebarInitialized) return; _sidebarInitialized = true;
|
||||
document.querySelectorAll('[data-sidebar-toggle]').forEach(btn => {
|
||||
const sidebar = document.getElementById(btn.dataset.sidebarToggle);
|
||||
if (!sidebar) return;
|
||||
@@ -429,8 +453,11 @@
|
||||
lt.api.put / patch / delete
|
||||
---------------------------------------------------------------- */
|
||||
async function apiFetch(method, url, body) {
|
||||
const opts = { method, headers: Object.assign({ 'Content-Type': 'application/json' }, csrfHeaders()) };
|
||||
if (body !== undefined) opts.body = JSON.stringify(body);
|
||||
const hasBody = body !== undefined;
|
||||
const headers = Object.assign({}, csrfHeaders());
|
||||
if (hasBody) headers['Content-Type'] = 'application/json'; // Only set on requests with a body
|
||||
const opts = { method, headers };
|
||||
if (hasBody) opts.body = JSON.stringify(body);
|
||||
let resp;
|
||||
try { resp = await fetch(url, opts); } catch (err) { throw new Error('Network error: ' + err.message); }
|
||||
let data;
|
||||
@@ -536,9 +563,12 @@
|
||||
const ths = Array.from(table.querySelectorAll('th[data-sort-key]'));
|
||||
ths.forEach((th, colIdx) => {
|
||||
let dir = 'asc';
|
||||
th.addEventListener('click', () => {
|
||||
ths.forEach(h => h.removeAttribute('data-sort'));
|
||||
th.setAttribute('aria-sort', 'none');
|
||||
th.setAttribute('tabindex', '0');
|
||||
const _sort = () => {
|
||||
ths.forEach(h => { h.removeAttribute('data-sort'); h.setAttribute('aria-sort', 'none'); });
|
||||
th.setAttribute('data-sort', dir);
|
||||
th.setAttribute('aria-sort', dir === 'asc' ? 'ascending' : 'descending');
|
||||
const tbody = table.querySelector('tbody');
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
rows.sort((a, b) => {
|
||||
@@ -550,7 +580,9 @@
|
||||
});
|
||||
rows.forEach(r => tbody.appendChild(r));
|
||||
dir = dir === 'asc' ? 'desc' : 'asc';
|
||||
});
|
||||
};
|
||||
th.addEventListener('click', _sort);
|
||||
th.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _sort(); } });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -563,14 +595,16 @@
|
||||
---------------------------------------------------------------- */
|
||||
function initStatsFilter() {
|
||||
document.querySelectorAll('.lt-stat-card[data-filter-key]').forEach(card => {
|
||||
card.addEventListener('click', () => {
|
||||
const _activate = () => {
|
||||
const key = card.dataset.filterKey, val = card.dataset.filterVal;
|
||||
const wasActive = card.classList.contains('active');
|
||||
document.querySelectorAll('.lt-stat-card').forEach(c => c.classList.remove('active'));
|
||||
if (!wasActive) card.classList.add('active');
|
||||
if (typeof global.lt_onStatFilter === 'function')
|
||||
global.lt_onStatFilter(wasActive ? null : key, wasActive ? null : val);
|
||||
});
|
||||
};
|
||||
card.addEventListener('click', _activate);
|
||||
card.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _activate(); } });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -625,7 +659,9 @@
|
||||
});
|
||||
}
|
||||
|
||||
let _accordionInitialized = false;
|
||||
function initAccordion() {
|
||||
if (_accordionInitialized) return; _accordionInitialized = true;
|
||||
// Support both data-accordion attribute (HTML) and .lt-accordion-trigger class
|
||||
document.querySelectorAll('[data-accordion], .lt-accordion-trigger').forEach(trigger => {
|
||||
if (trigger.getAttribute('aria-expanded') === 'true') {
|
||||
@@ -680,7 +716,11 @@
|
||||
case 'right': top = r.top + sy + r.height / 2 - tr.height / 2; left = r.right + sx + 8; break;
|
||||
default: top = r.top + sy - tr.height - 8; left = r.left + sx + r.width / 2 - tr.width / 2;
|
||||
}
|
||||
tip.style.cssText = 'position:absolute;top:' + Math.max(4, top) + 'px;left:' + Math.max(4, left) + 'px;z-index:9000';
|
||||
const maxLeft = (global.scrollX || 0) + global.innerWidth - tr.width - 4;
|
||||
const maxTop = (global.scrollY || 0) + global.innerHeight - tr.height - 4;
|
||||
left = Math.max(4 + (global.scrollX || 0), Math.min(maxLeft, left));
|
||||
top = Math.max(4 + (global.scrollY || 0), Math.min(maxTop, top));
|
||||
tip.style.cssText = 'position:absolute;top:' + top + 'px;left:' + left + 'px';
|
||||
requestAnimationFrame(() => tip.classList.add('is-visible'));
|
||||
}
|
||||
|
||||
@@ -688,7 +728,10 @@
|
||||
if (_tooltipEl) { _tooltipEl.remove(); _tooltipEl = null; }
|
||||
}
|
||||
|
||||
let _tooltipInitialized = false;
|
||||
function initTooltips() {
|
||||
if (_tooltipInitialized) return;
|
||||
_tooltipInitialized = true;
|
||||
document.addEventListener('mouseover', e => { const a = e.target.closest('[data-tooltip]'); if (a) _tooltipShow(a); });
|
||||
document.addEventListener('mouseout', e => { if (!e.target.closest('[data-tooltip]')) return; if (!e.relatedTarget || !e.relatedTarget.closest('[data-tooltip]')) _tooltipHide(); });
|
||||
document.addEventListener('focusin', e => { const a = e.target.closest('[data-tooltip]'); if (a) _tooltipShow(a); });
|
||||
@@ -718,7 +761,9 @@
|
||||
} catch (_) { return false; }
|
||||
}
|
||||
|
||||
let _copyInitialized = false;
|
||||
function initCopyButtons() {
|
||||
if (_copyInitialized) return; _copyInitialized = true;
|
||||
document.addEventListener('click', async function (e) {
|
||||
const btn = e.target.closest('[data-copy]'); if (!btn) return;
|
||||
const orig = btn.textContent;
|
||||
@@ -726,7 +771,7 @@
|
||||
if (ok) {
|
||||
btn.textContent = 'COPIED ✓'; btn.disabled = true;
|
||||
if (btn.hasAttribute('data-copy-toast')) toast.success('Copied to clipboard');
|
||||
setTimeout(() => { btn.textContent = orig; btn.disabled = false; }, 1500);
|
||||
setTimeout(() => { if (document.contains(btn)) { btn.textContent = orig; btn.disabled = false; } }, 1500);
|
||||
} else { toast.error('Copy failed'); }
|
||||
});
|
||||
}
|
||||
@@ -750,9 +795,11 @@
|
||||
}));
|
||||
}
|
||||
|
||||
let _alertsInitialized = false;
|
||||
function initAlerts() {
|
||||
if (_alertsInitialized) return; _alertsInitialized = true;
|
||||
document.addEventListener('click', function (e) {
|
||||
const btn = e.target.closest('.lt-alert-dismiss'); if (!btn) return;
|
||||
const btn = e.target.closest('.lt-alert-close, .lt-alert-dismiss'); if (!btn) return;
|
||||
const al = btn.closest('.lt-alert'); if (al) dismissAlert(al);
|
||||
});
|
||||
document.querySelectorAll('.lt-alert[data-alert-auto-dismiss]').forEach(el => {
|
||||
@@ -805,12 +852,13 @@
|
||||
|
||||
Command: { id, label, icon?, description?, kbd?, group?, tags?, action }
|
||||
---------------------------------------------------------------- */
|
||||
let _cpCommands = [], _cpSelected = 0;
|
||||
let _cpCommands = [], _cpSelected = 0, _cpTrigger = null;
|
||||
const _cpRecentKey = 'lt_cmd_recent';
|
||||
|
||||
function _cmdPaletteOpen() {
|
||||
const ov = document.getElementById('lt-cmd-overlay'); if (!ov) return;
|
||||
if (_mnOpen) _mnSetOpen(false);
|
||||
_cpTrigger = (document.activeElement && document.activeElement !== document.body) ? document.activeElement : null;
|
||||
ov.classList.add('is-open');
|
||||
_lockScroll();
|
||||
const palette = document.getElementById('lt-cmd-palette');
|
||||
@@ -823,6 +871,9 @@
|
||||
const ov = document.getElementById('lt-cmd-overlay'); if (!ov) return;
|
||||
ov.classList.remove('is-open');
|
||||
_unlockScroll();
|
||||
const inp = document.querySelector('#lt-cmd-palette .lt-cmd-input');
|
||||
if (inp) inp.removeAttribute('aria-activedescendant');
|
||||
if (_cpTrigger) { if (document.contains(_cpTrigger)) _cpTrigger.focus(); _cpTrigger = null; }
|
||||
}
|
||||
|
||||
function _cpHighlight(text, q) {
|
||||
@@ -865,7 +916,7 @@
|
||||
if (!groups[g] || !groups[g].length) return;
|
||||
html += '<div class="lt-cmd-section-label">' + escHtml(g) + '</div>';
|
||||
groups[g].forEach(cmd => {
|
||||
html += '<div class="lt-cmd-item' + (idx === 0 ? ' is-selected' : '') + '" data-cmd-id="' + escHtml(cmd.id) + '">' +
|
||||
html += '<div class="lt-cmd-item' + (idx === 0 ? ' is-selected' : '') + '" id="lt-cmd-item-' + idx + '" data-cmd-id="' + escHtml(cmd.id) + '">' +
|
||||
'<span class="lt-cmd-item-icon">' + escHtml(cmd.icon || '◦') + '</span>' +
|
||||
'<span class="lt-cmd-item-label">' + _cpHighlight(cmd.label, query) + '</span>' +
|
||||
(cmd.kbd ? '<span class="lt-cmd-item-kbd">' + escHtml(cmd.kbd) + '</span>' : '') +
|
||||
@@ -875,10 +926,15 @@
|
||||
});
|
||||
|
||||
results.innerHTML = html;
|
||||
results.querySelectorAll('.lt-cmd-item').forEach((item, i) => {
|
||||
const pal = document.getElementById('lt-cmd-palette');
|
||||
const inp = pal && pal.querySelector('.lt-cmd-input');
|
||||
const allItems = Array.from(results.querySelectorAll('.lt-cmd-item'));
|
||||
if (inp && allItems[0]) inp.setAttribute('aria-activedescendant', allItems[0].id);
|
||||
allItems.forEach((item, i) => {
|
||||
item.addEventListener('mouseenter', () => {
|
||||
results.querySelectorAll('.lt-cmd-item').forEach(x => x.classList.remove('is-selected'));
|
||||
allItems.forEach(x => x.classList.remove('is-selected'));
|
||||
item.classList.add('is-selected'); _cpSelected = i;
|
||||
if (inp) inp.setAttribute('aria-activedescendant', item.id);
|
||||
});
|
||||
item.addEventListener('click', () => _cpExec(item.dataset.cmdId));
|
||||
});
|
||||
@@ -903,6 +959,8 @@
|
||||
_cpSelected = (_cpSelected + dir + items.length) % items.length;
|
||||
items[_cpSelected] && items[_cpSelected].classList.add('is-selected');
|
||||
items[_cpSelected] && items[_cpSelected].scrollIntoView({ block: 'nearest' });
|
||||
const inp = ov.querySelector('.lt-cmd-input');
|
||||
if (inp && items[_cpSelected]) inp.setAttribute('aria-activedescendant', items[_cpSelected].id);
|
||||
}
|
||||
|
||||
function initCmdPalette(commands) {
|
||||
@@ -943,6 +1001,8 @@
|
||||
|
||||
function _validateField(el) {
|
||||
const val = el.value || '', type = (el.type || '').toLowerCase();
|
||||
if ((type === 'checkbox' || type === 'radio') && el.required && !el.checked) return { valid: false, message: 'This field is required' };
|
||||
if (el.tagName === 'SELECT' && el.multiple && el.required && el.selectedOptions.length === 0) return { valid: false, message: 'Please select at least one option' };
|
||||
if (el.required && !val.trim()) return { valid: false, message: 'This field is required' };
|
||||
if (el.minLength > 0 && val.length < el.minLength) return { valid: false, message: 'Minimum ' + el.minLength + ' characters' };
|
||||
if (el.maxLength > 0 && val.length > el.maxLength) return { valid: false, message: 'Maximum ' + el.maxLength + ' characters' };
|
||||
@@ -960,12 +1020,22 @@
|
||||
function _showError(el, msg) {
|
||||
el.classList.add('is-invalid'); el.classList.remove('is-valid');
|
||||
let err = el.parentElement && el.parentElement.querySelector('.lt-field-error');
|
||||
if (!err) { err = document.createElement('span'); err.className = 'lt-field-error'; if (el.parentElement) el.parentElement.appendChild(err); }
|
||||
if (!err) {
|
||||
err = document.createElement('span');
|
||||
err.className = 'lt-field-error';
|
||||
err.id = (el.id || ('lt-field-' + Math.random().toString(36).slice(2))) + '-err';
|
||||
if (el.parentElement) el.parentElement.appendChild(err);
|
||||
}
|
||||
err.textContent = msg;
|
||||
err.setAttribute('role', 'alert');
|
||||
el.setAttribute('aria-describedby', err.id);
|
||||
el.setAttribute('aria-invalid', 'true');
|
||||
}
|
||||
|
||||
function _clearError(el) {
|
||||
el.classList.remove('is-invalid'); el.classList.add('is-valid');
|
||||
el.removeAttribute('aria-invalid');
|
||||
el.removeAttribute('aria-describedby');
|
||||
const err = el.parentElement && el.parentElement.querySelector('.lt-field-error');
|
||||
if (err) err.remove();
|
||||
}
|
||||
@@ -990,7 +1060,7 @@
|
||||
e.preventDefault();
|
||||
const r = _validateForm(formEl);
|
||||
if (r.valid && typeof onSubmit === 'function') onSubmit(formEl, e);
|
||||
else if (!r.valid) r.errors[0].el.focus();
|
||||
else if (!r.valid && r.errors.length) r.errors[0].el.focus();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1343,7 +1413,7 @@
|
||||
Auto-inits on [id="lt-menu-btn"] + [id="lt-nav-drawer"]
|
||||
Swipe right from left edge (≤ 20px) opens; swipe left closes.
|
||||
================================================================ */
|
||||
let _mnOpen = false;
|
||||
let _mnOpen = false, _mnTrigger = null;
|
||||
|
||||
function _mnSetOpen(open) {
|
||||
_mnOpen = open;
|
||||
@@ -1353,20 +1423,28 @@
|
||||
if (!drawer) return;
|
||||
|
||||
if (open) {
|
||||
_mnTrigger = (document.activeElement && document.activeElement !== document.body) ? document.activeElement : null;
|
||||
drawer.classList.add('open');
|
||||
drawer.setAttribute('aria-hidden', 'false');
|
||||
drawer.removeAttribute('aria-hidden');
|
||||
if (overlay) overlay.classList.add('open');
|
||||
if (btn) { btn.classList.add('open'); btn.setAttribute('aria-expanded', 'true'); }
|
||||
document.body.style.overflow = 'hidden';
|
||||
// Trap focus inside drawer
|
||||
if (!drawer._mnTrapHandler) {
|
||||
drawer._mnTrapHandler = e => { if (e.key === 'Tab') _trapFocus(drawer, e); };
|
||||
drawer.addEventListener('keydown', drawer._mnTrapHandler);
|
||||
}
|
||||
const first = drawer.querySelector('button, a, [tabindex]');
|
||||
if (first) setTimeout(() => first.focus(), 50);
|
||||
if (first) setTimeout(() => { if (document.contains(first)) first.focus(); }, 50);
|
||||
} else {
|
||||
drawer.classList.remove('open');
|
||||
drawer.setAttribute('aria-hidden', 'true');
|
||||
if (overlay) overlay.classList.remove('open');
|
||||
if (btn) { btn.classList.remove('open'); btn.setAttribute('aria-expanded', 'false'); }
|
||||
document.body.style.overflow = '';
|
||||
if (drawer._mnTrapHandler) { drawer.removeEventListener('keydown', drawer._mnTrapHandler); delete drawer._mnTrapHandler; }
|
||||
if (_mnTrigger && document.contains(_mnTrigger)) { _mnTrigger.focus(); }
|
||||
_mnTrigger = null;
|
||||
}
|
||||
bus.emit('mobileNav:' + (open ? 'open' : 'close'));
|
||||
}
|
||||
@@ -1544,15 +1622,17 @@
|
||||
const ov = document.getElementById(ovId);
|
||||
if (_mnOpen) _mnSetOpen(false);
|
||||
drawer.classList.add('is-open');
|
||||
drawer.setAttribute('aria-hidden', 'false');
|
||||
drawer.removeAttribute('aria-hidden');
|
||||
if (ov) ov.classList.add('is-open');
|
||||
_lockScroll();
|
||||
if (triggerEl) _modalTriggers.set(drawer, triggerEl);
|
||||
const first = drawer.querySelector(_FOCUSABLE);
|
||||
if (first) setTimeout(() => first.focus(), 50);
|
||||
// ESC to close
|
||||
// ESC to close + Tab trap
|
||||
drawer._rdKeyHandler = e => { if (e.key === 'Escape') _rdClose(drawer); };
|
||||
document.addEventListener('keydown', drawer._rdKeyHandler);
|
||||
drawer._rdTrapHandler = e => { if (e.key === 'Tab') _trapFocus(drawer, e); };
|
||||
drawer.addEventListener('keydown', drawer._rdTrapHandler);
|
||||
// Overlay click
|
||||
if (ov) ov._rdClick = () => _rdClose(drawer);
|
||||
if (ov) ov.addEventListener('click', ov._rdClick);
|
||||
@@ -1570,8 +1650,12 @@
|
||||
drawer.classList.remove('is-open');
|
||||
drawer.setAttribute('aria-hidden', 'true');
|
||||
if (ov) { ov.classList.remove('is-open'); if (ov._rdClick) { ov.removeEventListener('click', ov._rdClick); delete ov._rdClick; } }
|
||||
drawer.querySelectorAll('[data-drawer-close]').forEach(btn => {
|
||||
if (btn._rdHandler) { btn.removeEventListener('click', btn._rdHandler); delete btn._rdHandler; }
|
||||
});
|
||||
_unlockScroll();
|
||||
if (drawer._rdKeyHandler) { document.removeEventListener('keydown', drawer._rdKeyHandler); delete drawer._rdKeyHandler; }
|
||||
if (drawer._rdKeyHandler) { document.removeEventListener('keydown', drawer._rdKeyHandler); delete drawer._rdKeyHandler; }
|
||||
if (drawer._rdTrapHandler) { drawer.removeEventListener('keydown', drawer._rdTrapHandler); delete drawer._rdTrapHandler; }
|
||||
const trigger = _modalTriggers.get(drawer);
|
||||
if (trigger) { trigger.focus(); _modalTriggers.delete(drawer); }
|
||||
}
|
||||
@@ -1594,9 +1678,10 @@
|
||||
lt.contextMenu.register(selector, items)
|
||||
items = [{ label, icon, kbd, danger, divider, action }]
|
||||
================================================================ */
|
||||
let _ctxMenu = null;
|
||||
let _ctxMenu = null, _ctxTrigger = null;
|
||||
const _ctxItems = {};
|
||||
function _ctxShow(x, y, items) {
|
||||
function _ctxShow(x, y, items, trigger) {
|
||||
_ctxTrigger = trigger || (document.activeElement !== document.body ? document.activeElement : null);
|
||||
if (!_ctxMenu) {
|
||||
_ctxMenu = document.createElement('div');
|
||||
_ctxMenu.className = 'lt-context-menu';
|
||||
@@ -1613,14 +1698,22 @@
|
||||
el.setAttribute('tabindex', '0');
|
||||
el.innerHTML = `${item.icon ? `<span class="icon">${escHtml(item.icon)}</span>` : '<span class="icon"></span>'}<span>${escHtml(item.label || '')}</span>${item.kbd ? `<kbd>${escHtml(item.kbd)}</kbd>` : ''}`;
|
||||
el.addEventListener('click', () => { _ctxHide(); if (item.action) item.action(); });
|
||||
el.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); el.click(); } });
|
||||
el.addEventListener('keydown', e => {
|
||||
const items = Array.from(_ctxMenu.querySelectorAll('[role="menuitem"]'));
|
||||
const idx = items.indexOf(e.currentTarget);
|
||||
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); el.click(); }
|
||||
else if (e.key === 'ArrowDown') { e.preventDefault(); (items[idx + 1] || items[0]).focus(); }
|
||||
else if (e.key === 'ArrowUp') { e.preventDefault(); (items[idx - 1] || items[items.length - 1]).focus(); }
|
||||
else if (e.key === 'Home') { e.preventDefault(); items[0].focus(); }
|
||||
else if (e.key === 'End') { e.preventDefault(); items[items.length - 1].focus(); }
|
||||
});
|
||||
_ctxMenu.appendChild(el);
|
||||
});
|
||||
_ctxMenu.classList.add('is-open');
|
||||
// Position — keep on screen
|
||||
const vw = window.innerWidth, vh = window.innerHeight;
|
||||
const mw = _ctxMenu.offsetWidth || 180, mh = _ctxMenu.offsetHeight || 200;
|
||||
_ctxMenu.style.left = Math.min(x, vw - mw - 8) + 'px';
|
||||
_ctxMenu.style.left = Math.max(8, Math.min(x, vw - mw - 8)) + 'px';
|
||||
_ctxMenu.style.top = Math.min(y, vh - mh - 8) + 'px';
|
||||
// Focus first item
|
||||
const first = _ctxMenu.querySelector('[role="menuitem"]');
|
||||
@@ -1628,6 +1721,8 @@
|
||||
}
|
||||
function _ctxHide() {
|
||||
if (_ctxMenu) _ctxMenu.classList.remove('is-open');
|
||||
if (_ctxTrigger && document.contains(_ctxTrigger)) { _ctxTrigger.focus(); }
|
||||
_ctxTrigger = null;
|
||||
}
|
||||
document.addEventListener('click', () => _ctxHide());
|
||||
document.addEventListener('contextmenu', e => {
|
||||
@@ -1636,7 +1731,7 @@
|
||||
e.preventDefault();
|
||||
const menuId = target.dataset.contextMenu;
|
||||
const items = _ctxItems[menuId];
|
||||
if (items) _ctxShow(e.clientX, e.clientY, items);
|
||||
if (items) _ctxShow(e.clientX, e.clientY, items, target);
|
||||
});
|
||||
document.addEventListener('keydown', e => { if (e.key === 'Escape') _ctxHide(); });
|
||||
const contextMenu = {
|
||||
@@ -1791,6 +1886,15 @@
|
||||
let focusedIdx = -1;
|
||||
let filtered = [...options];
|
||||
|
||||
// ARIA combobox wiring
|
||||
const dropId = dropdown.id || ('lt-cb-drop-' + Math.random().toString(36).slice(2));
|
||||
dropdown.id = dropId;
|
||||
dropdown.setAttribute('role', 'listbox');
|
||||
inputEl.setAttribute('role', 'combobox');
|
||||
inputEl.setAttribute('aria-expanded', 'false');
|
||||
inputEl.setAttribute('aria-controls', dropId);
|
||||
inputEl.setAttribute('aria-autocomplete', 'list');
|
||||
|
||||
function _renderTags() {
|
||||
wrap.querySelectorAll('.lt-combobox-tag').forEach(t => t.remove());
|
||||
selected.forEach(v => {
|
||||
@@ -1798,7 +1902,7 @@
|
||||
if (!opt) return;
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'lt-combobox-tag';
|
||||
tag.innerHTML = `${escHtml(opt.label)}<button class="lt-combobox-tag-remove" data-value="${escHtml(v)}" aria-label="Remove ${escHtml(opt.label)}">✕</button>`;
|
||||
tag.innerHTML = `${escHtml(opt.label)}<button type="button" class="lt-combobox-tag-remove" data-value="${escHtml(v)}" aria-label="Remove ${escHtml(opt.label)}">✕</button>`;
|
||||
inputWrap.insertBefore(tag, inputEl);
|
||||
});
|
||||
}
|
||||
@@ -1813,10 +1917,12 @@
|
||||
}
|
||||
filtered.forEach((opt, i) => {
|
||||
const el = document.createElement('div');
|
||||
el.id = dropId + '-opt-' + i;
|
||||
el.className = 'lt-combobox-option' + (selected.includes(opt.value) ? ' is-selected' : '');
|
||||
el.setAttribute('role', 'option');
|
||||
el.setAttribute('data-value', opt.value);
|
||||
const hl = q ? opt.label.replace(new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`, 'gi'), '<mark>$1</mark>') : escHtml(opt.label);
|
||||
const safeLabel = escHtml(opt.label);
|
||||
const hl = q ? safeLabel.replace(new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`, 'gi'), '<mark>$1</mark>') : safeLabel;
|
||||
el.innerHTML = `${opt.icon ? `<span class="icon">${escHtml(opt.icon)}</span>` : ''}<span>${hl}</span>`;
|
||||
el.addEventListener('mousedown', e => { e.preventDefault(); _toggle(opt.value); });
|
||||
dropdown.appendChild(el);
|
||||
@@ -1836,20 +1942,26 @@
|
||||
}
|
||||
|
||||
function _moveFocus(dir) {
|
||||
const items = dropdown.querySelectorAll('.lt-combobox-option');
|
||||
const items = Array.from(dropdown.querySelectorAll('.lt-combobox-option'));
|
||||
if (!items.length) return;
|
||||
focusedIdx = Math.max(0, Math.min(focusedIdx + dir, items.length - 1));
|
||||
items.forEach((el, i) => el.classList.toggle('is-focused', i === focusedIdx));
|
||||
items[focusedIdx].scrollIntoView({ block: 'nearest' });
|
||||
inputEl.setAttribute('aria-activedescendant', items[focusedIdx].id);
|
||||
}
|
||||
|
||||
inputEl.addEventListener('input', () => { dropdown.classList.add('is-open'); _renderDropdown(inputEl.value); });
|
||||
inputEl.addEventListener('focus', () => { dropdown.classList.add('is-open'); _renderDropdown(inputEl.value); });
|
||||
function _setOpen(open) {
|
||||
dropdown.classList.toggle('is-open', open);
|
||||
inputEl.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||||
if (!open) { inputEl.removeAttribute('aria-activedescendant'); focusedIdx = -1; }
|
||||
}
|
||||
inputEl.addEventListener('input', () => { _setOpen(true); _renderDropdown(inputEl.value); });
|
||||
inputEl.addEventListener('focus', () => { _setOpen(true); _renderDropdown(inputEl.value); });
|
||||
inputEl.addEventListener('keydown', e => {
|
||||
if (e.key === 'ArrowDown') { e.preventDefault(); _moveFocus(1); }
|
||||
if (e.key === 'ArrowUp') { e.preventDefault(); _moveFocus(-1); }
|
||||
if (e.key === 'Enter') { e.preventDefault(); if (focusedIdx >= 0 && filtered[focusedIdx]) _toggle(filtered[focusedIdx].value); }
|
||||
if (e.key === 'Escape') { dropdown.classList.remove('is-open'); }
|
||||
if (e.key === 'Escape') { _setOpen(false); }
|
||||
if (e.key === 'Backspace' && !inputEl.value && selected.length) { _toggle(selected[selected.length - 1]); }
|
||||
});
|
||||
inputWrap.addEventListener('mousedown', e => {
|
||||
@@ -1857,7 +1969,7 @@
|
||||
if (rmBtn) { e.preventDefault(); _toggle(rmBtn.dataset.value); return; }
|
||||
inputEl.focus();
|
||||
});
|
||||
document.addEventListener('click', e => { if (!wrap.contains(e.target)) dropdown.classList.remove('is-open'); });
|
||||
document.addEventListener('click', e => { if (!wrap.contains(e.target)) _setOpen(false); });
|
||||
|
||||
_renderTags();
|
||||
_renderDropdown('');
|
||||
@@ -1897,9 +2009,11 @@
|
||||
const q = query.toLowerCase();
|
||||
_items.forEach((item, i) => {
|
||||
const el = document.createElement('div');
|
||||
el.id = (inputEl.id || 'lt-ta') + '-item-' + i;
|
||||
el.className = 'lt-typeahead-item';
|
||||
el.setAttribute('role', 'option');
|
||||
const hl = item.label.replace(new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`, 'gi'), '<mark>$1</mark>');
|
||||
const safeItemLabel = escHtml(item.label);
|
||||
const hl = safeItemLabel.replace(new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`, 'gi'), '<mark>$1</mark>');
|
||||
el.innerHTML = `${item.icon ? `<span class="icon">${escHtml(item.icon)}</span>` : ''}<span>${hl}</span>${item.meta ? `<span style="margin-left:auto;color:var(--text-muted);font-size:0.68rem">${escHtml(item.meta)}</span>` : ''}`;
|
||||
el.addEventListener('mousedown', e => { e.preventDefault(); _select(item); });
|
||||
dropdown.appendChild(el);
|
||||
@@ -1910,27 +2024,32 @@
|
||||
async function _search(query) {
|
||||
dropdown.innerHTML = '<div class="lt-typeahead-loading">Searching…</div>';
|
||||
dropdown.classList.add('is-open');
|
||||
inputEl.setAttribute('aria-busy', 'true');
|
||||
try {
|
||||
const results = typeof source === 'function' ? await source(query) : source.filter(i => i.label.toLowerCase().includes(query.toLowerCase()));
|
||||
_render(results, query);
|
||||
} catch(e) {
|
||||
dropdown.innerHTML = '<div class="lt-typeahead-empty">Error loading results</div>';
|
||||
} finally {
|
||||
inputEl.setAttribute('aria-busy', 'false');
|
||||
}
|
||||
}
|
||||
|
||||
function _select(item) {
|
||||
inputEl.value = item.label;
|
||||
inputEl.removeAttribute('aria-activedescendant');
|
||||
dropdown.classList.remove('is-open');
|
||||
if (onSelect) onSelect(item);
|
||||
bus.emit('typeahead:select', { item });
|
||||
}
|
||||
|
||||
function _moveFocus(dir) {
|
||||
const els = dropdown.querySelectorAll('.lt-typeahead-item');
|
||||
const els = Array.from(dropdown.querySelectorAll('.lt-typeahead-item'));
|
||||
if (!els.length) return;
|
||||
_focusedIdx = Math.max(0, Math.min(_focusedIdx + dir, els.length - 1));
|
||||
els.forEach((el, i) => el.classList.toggle('is-focused', i === _focusedIdx));
|
||||
els[_focusedIdx].scrollIntoView({ block: 'nearest' });
|
||||
inputEl.setAttribute('aria-activedescendant', els[_focusedIdx].id);
|
||||
}
|
||||
|
||||
inputEl.addEventListener('input', () => {
|
||||
@@ -2035,6 +2154,25 @@
|
||||
});
|
||||
divider.addEventListener('pointerup', () => { dragging = false; divider.classList.remove('is-dragging'); });
|
||||
|
||||
// Keyboard resize support
|
||||
divider.setAttribute('tabindex', '0');
|
||||
divider.setAttribute('role', 'separator');
|
||||
divider.setAttribute('aria-label', 'Resize panes');
|
||||
divider.addEventListener('keydown', e => {
|
||||
const step = 0.05;
|
||||
const total = vertical ? container.clientHeight : container.clientWidth;
|
||||
const divSize = vertical ? divider.offsetHeight : divider.offsetWidth;
|
||||
const available = total - divSize;
|
||||
const currentSize = vertical ? panes[0].offsetHeight : panes[0].offsetWidth;
|
||||
const currentRatio = currentSize / available;
|
||||
if ((e.key === 'ArrowRight' && !vertical) || (e.key === 'ArrowDown' && vertical)) {
|
||||
e.preventDefault(); _setRatio(Math.min(1, currentRatio + step));
|
||||
} else if ((e.key === 'ArrowLeft' && !vertical) || (e.key === 'ArrowUp' && vertical)) {
|
||||
e.preventDefault(); _setRatio(Math.max(0, currentRatio - step));
|
||||
} else if (e.key === 'Home') { e.preventDefault(); _setRatio(0); }
|
||||
else if (e.key === 'End') { e.preventDefault(); _setRatio(1); }
|
||||
});
|
||||
|
||||
_setRatio(initial);
|
||||
return { setRatio: _setRatio };
|
||||
},
|
||||
@@ -2052,10 +2190,21 @@
|
||||
group._sbInit = true;
|
||||
const label = group.querySelector('.lt-sidebar-group-label');
|
||||
if (!label) return;
|
||||
label.addEventListener('click', () => group.classList.toggle('is-open'));
|
||||
label.setAttribute('tabindex', '0');
|
||||
label.setAttribute('role', 'button');
|
||||
const chevron = label.querySelector('.chevron, .lt-sidebar-chevron');
|
||||
if (chevron) chevron.setAttribute('aria-hidden', 'true');
|
||||
const _toggle = () => {
|
||||
group.classList.toggle('is-open');
|
||||
label.setAttribute('aria-expanded', group.classList.contains('is-open') ? 'true' : 'false');
|
||||
};
|
||||
label.setAttribute('aria-expanded', group.classList.contains('is-open') ? 'true' : 'false');
|
||||
label.addEventListener('click', _toggle);
|
||||
label.addEventListener('keydown', e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); _toggle(); } });
|
||||
// Open group if it contains the active link
|
||||
if (group.querySelector('.lt-sidebar-sub-link.active, .lt-sidebar-sub-link[aria-current="page"]')) {
|
||||
group.classList.add('is-open');
|
||||
label.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2119,8 +2268,9 @@
|
||||
const dist = el.scrollHeight - el.scrollTop - el.clientHeight;
|
||||
if (dist < threshold) _load();
|
||||
}
|
||||
scrollRoot.addEventListener('scroll', throttle(_onScroll, 150), { passive: true });
|
||||
return { reset() { _done = false; _loading = false; }, stop() { scrollRoot.removeEventListener('scroll', _onScroll); } };
|
||||
const _onScrollThrottled = throttle(_onScroll, 150);
|
||||
scrollRoot.addEventListener('scroll', _onScrollThrottled, { passive: true });
|
||||
return { reset() { _done = false; _loading = false; }, stop() { scrollRoot.removeEventListener('scroll', _onScrollThrottled); } };
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -2151,7 +2301,7 @@
|
||||
function _show(idx) {
|
||||
steps.forEach((s, i) => {
|
||||
s.classList.toggle('is-active', i === idx);
|
||||
s.setAttribute('aria-hidden', i !== idx ? 'true' : 'false');
|
||||
if (i !== idx) s.setAttribute('aria-hidden', 'true'); else s.removeAttribute('aria-hidden');
|
||||
});
|
||||
// Update step indicators
|
||||
container.querySelectorAll('[data-wizard-indicator]').forEach((ind, i) => {
|
||||
@@ -2175,16 +2325,23 @@
|
||||
if (first) setTimeout(() => first.focus(), 60);
|
||||
}
|
||||
|
||||
let _wizBusy = false;
|
||||
async function _next() {
|
||||
if (validate) {
|
||||
const ok = await validate(current + 1, _getStepData(current));
|
||||
if (!ok) {
|
||||
container.querySelectorAll('[data-wizard-indicator]')[current]?.classList.add('is-error');
|
||||
return;
|
||||
if (_wizBusy) return;
|
||||
_wizBusy = true;
|
||||
try {
|
||||
if (validate) {
|
||||
const ok = await validate(current + 1, _getStepData(current));
|
||||
if (!ok) {
|
||||
container.querySelectorAll('[data-wizard-indicator]')[current]?.classList.add('is-error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
Object.assign(formData, _getStepData(current));
|
||||
if (current < total - 1) { current++; _show(current); }
|
||||
} finally {
|
||||
_wizBusy = false;
|
||||
}
|
||||
Object.assign(formData, _getStepData(current));
|
||||
if (current < total - 1) { current++; _show(current); }
|
||||
}
|
||||
|
||||
function _prev() {
|
||||
@@ -2367,7 +2524,7 @@
|
||||
const lightbox = {
|
||||
init(selector, opts = {}) {
|
||||
const { caption = 'alt', loop = true } = opts;
|
||||
let _images = [], _current = 0, _overlay = null;
|
||||
let _images = [], _current = 0, _overlay = null, _lbKeyBound = null, _lbTrigger = null;
|
||||
|
||||
function _getCaption(img) {
|
||||
if (typeof caption === 'function') return caption(img);
|
||||
@@ -2382,9 +2539,9 @@
|
||||
_overlay.setAttribute('aria-modal', 'true');
|
||||
_overlay.setAttribute('aria-label', 'Image viewer');
|
||||
_overlay.innerHTML = `
|
||||
<button class="lt-lightbox-close" aria-label="Close">×</button>
|
||||
<button class="lt-lightbox-prev" aria-label="Previous">‹</button>
|
||||
<button class="lt-lightbox-next" aria-label="Next">›</button>
|
||||
<button type="button" class="lt-lightbox-close" aria-label="Close">×</button>
|
||||
<button type="button" class="lt-lightbox-prev" aria-label="Previous">‹</button>
|
||||
<button type="button" class="lt-lightbox-next" aria-label="Next">›</button>
|
||||
<div class="lt-lightbox-img-wrap">
|
||||
<img class="lt-lightbox-img" src="" alt="">
|
||||
</div>
|
||||
@@ -2397,7 +2554,8 @@
|
||||
_overlay.querySelector('.lt-lightbox-prev').addEventListener('click', () => lightbox.prev());
|
||||
_overlay.querySelector('.lt-lightbox-next').addEventListener('click', () => lightbox.next());
|
||||
_overlay.addEventListener('click', e => { if (e.target === _overlay) lightbox.close(); });
|
||||
document.addEventListener('keydown', _lbKey);
|
||||
_lbKeyBound = _lbKey.bind(null);
|
||||
document.addEventListener('keydown', _lbKeyBound);
|
||||
}
|
||||
|
||||
function _lbKey(e) {
|
||||
@@ -2409,6 +2567,9 @@
|
||||
|
||||
function _show(idx) {
|
||||
if (!_overlay) _buildOverlay();
|
||||
if (!_overlay.classList.contains('is-open')) {
|
||||
_lbTrigger = (document.activeElement && document.activeElement !== document.body) ? document.activeElement : null;
|
||||
}
|
||||
_current = idx;
|
||||
const img = _images[idx];
|
||||
const el = _overlay.querySelector('.lt-lightbox-img');
|
||||
@@ -2420,7 +2581,7 @@
|
||||
_overlay.querySelector('.lt-lightbox-next').style.display = (loop || idx < _images.length - 1) && _images.length > 1 ? '' : 'none';
|
||||
_overlay.classList.add('is-open');
|
||||
_lockScroll();
|
||||
setTimeout(() => el.focus?.(), 50);
|
||||
setTimeout(() => { if (document.contains(el)) el.focus?.(); }, 50);
|
||||
}
|
||||
|
||||
function _collect() {
|
||||
@@ -2444,6 +2605,9 @@
|
||||
if (!_overlay) return;
|
||||
_overlay.classList.remove('is-open');
|
||||
_unlockScroll();
|
||||
if (_lbKeyBound) { document.removeEventListener('keydown', _lbKeyBound); _lbKeyBound = null; }
|
||||
if (_lbTrigger && document.contains(_lbTrigger)) { _lbTrigger.focus(); }
|
||||
_lbTrigger = null;
|
||||
},
|
||||
prev() { _show(loop ? (_current - 1 + _images.length) % _images.length : Math.max(0, _current - 1)); },
|
||||
next() { _show(loop ? (_current + 1) % _images.length : Math.min(_images.length - 1, _current + 1)); },
|
||||
@@ -2574,10 +2738,16 @@
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
.replace(/__(.+?)__/g, '<strong>$1</strong>')
|
||||
.replace(/_(.+?)_/g, '<em>$1</em>')
|
||||
// Links
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener noreferrer">$1</a>')
|
||||
// Images
|
||||
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%">')
|
||||
// Links — block javascript: and data: URIs
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, url) => {
|
||||
const safeUrl = /^(https?:\/\/|\/|#|\.\.?\/)/i.test(url) ? url : '#';
|
||||
return `<a href="${safeUrl}" target="_blank" rel="noopener noreferrer">${escHtml(text)}</a>`;
|
||||
})
|
||||
// Images — block javascript: and data: URIs
|
||||
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_, alt, src) => {
|
||||
const safeSrc = /^(https?:\/\/|\/|\.\.?\/)/i.test(src) ? src : '';
|
||||
return `<img src="${safeSrc}" alt="${escHtml(alt)}" style="max-width:100%">`;
|
||||
})
|
||||
// Blockquote
|
||||
.replace(/^>\s(.+)$/gm, '<blockquote>$1</blockquote>')
|
||||
// Horizontal rule
|
||||
@@ -2622,7 +2792,7 @@
|
||||
const pages = _pages();
|
||||
let html = '';
|
||||
// Prev
|
||||
html += `<button class="lt-page-btn" ${page <= 1 ? 'disabled' : ''} data-page="${page - 1}">«</button>`;
|
||||
html += `<button type="button" class="lt-page-btn" ${page <= 1 ? 'disabled' : ''} data-page="${page - 1}" aria-label="Previous page">«</button>`;
|
||||
// Page buttons with ellipsis
|
||||
const half = Math.floor((maxBtns - 2) / 2);
|
||||
let start = Math.max(2, page - half);
|
||||
@@ -2631,15 +2801,17 @@
|
||||
if (start === 2) end = Math.min(pages - 1, start + maxBtns - 3);
|
||||
else start = Math.max(2, end - maxBtns + 3);
|
||||
}
|
||||
html += `<button class="lt-page-btn${page === 1 ? ' active' : ''}" data-page="1">1</button>`;
|
||||
if (start > 2) html += `<button class="lt-page-btn" disabled>…</button>`;
|
||||
html += `<button type="button" class="lt-page-btn${page === 1 ? ' active' : ''}" data-page="1"${page === 1 ? ' aria-current="page"' : ''} aria-label="Page 1">1</button>`;
|
||||
if (start > 2) html += `<button type="button" class="lt-page-btn" disabled aria-hidden="true">…</button>`;
|
||||
for (let i = start; i <= end; i++) {
|
||||
html += `<button class="lt-page-btn${page === i ? ' active' : ''}" data-page="${i}">${i}</button>`;
|
||||
html += `<button type="button" class="lt-page-btn${page === i ? ' active' : ''}" data-page="${i}"${page === i ? ' aria-current="page"' : ''} aria-label="Page ${i}">${i}</button>`;
|
||||
}
|
||||
if (end < pages - 1) html += `<button class="lt-page-btn" disabled>…</button>`;
|
||||
if (pages > 1) html += `<button class="lt-page-btn${page === pages ? ' active' : ''}" data-page="${pages}">${pages}</button>`;
|
||||
if (end < pages - 1) html += `<button type="button" class="lt-page-btn" disabled aria-hidden="true">…</button>`;
|
||||
if (pages > 1) html += `<button type="button" class="lt-page-btn${page === pages ? ' active' : ''}" data-page="${pages}"${page === pages ? ' aria-current="page"' : ''} aria-label="Page ${pages}">${pages}</button>`;
|
||||
// Next
|
||||
html += `<button class="lt-page-btn" ${page >= pages ? 'disabled' : ''} data-page="${page + 1}">»</button>`;
|
||||
html += `<button type="button" class="lt-page-btn" ${page >= pages ? 'disabled' : ''} data-page="${page + 1}" aria-label="Next page">»</button>`;
|
||||
if (!nav.getAttribute('role')) nav.setAttribute('role', 'navigation');
|
||||
if (!nav.getAttribute('aria-label')) nav.setAttribute('aria-label', 'Pagination');
|
||||
nav.innerHTML = html;
|
||||
}
|
||||
|
||||
@@ -2671,7 +2843,10 @@
|
||||
alerts: bool, clipboard: bool, sidebar: bool, submenus: bool }
|
||||
Individual modules can still be called manually.
|
||||
================================================================ */
|
||||
let _ltInitialized = false;
|
||||
function ltInit(opts) {
|
||||
if (_ltInitialized) return; // Guard: safe to call multiple times
|
||||
_ltInitialized = true;
|
||||
const o = Object.assign({
|
||||
boot: true,
|
||||
bootName: null,
|
||||
|
||||
Reference in New Issue
Block a user