a11y: exported widget ARIA (slider/tabs/gallery)
ContentSlider, Tabs, and Gallery toHtml exports and their inline scripts now carry ARIA semantics and keyboard support: - ContentSlider: prev/next arrows and dot buttons get aria-labels, the slide stack is wrapped in an aria-live="polite" region, and the decorative chevron icons are aria-hidden. - Tabs: tablist/tab/tabpanel roles, aria-selected/aria-controls/ aria-labelledby, and Left/Right/Home/End arrow-key navigation in the inline script. The tab<->panel id scope is now derived from a deterministic hash of anchorId/labels instead of Math.random, since the ARIA linking ids must be stable across export runs. - Gallery: the lightbox overlay gets role="dialog"/aria-modal/aria-label, Escape closes it, and thumbnails are keyboard-operable (role="button" tabindex="0" plus Enter/Space handling in the existing delegated listener). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -144,7 +144,7 @@ Gallery.craft = {
|
||||
// inline onclick with an interpolated src -- a single delegated click
|
||||
// listener below reads it, so a src containing a quote can't break out
|
||||
// of a per-item event-handler string.
|
||||
const lbAttr = lightbox ? ` data-lb-src="${escapeAttr(safeUrl(img.src || ''))}"` : '';
|
||||
const lbAttr = lightbox ? ` data-lb-src="${escapeAttr(safeUrl(img.src || ''))}" role="button" tabindex="0"` : '';
|
||||
const itemStyle = lightbox ? 'cursor:pointer;position:relative;overflow:hidden;border-radius:8px' : 'position:relative;overflow:hidden;border-radius:8px';
|
||||
return `<div${lbAttr} style="${itemStyle}">
|
||||
<img src="${escapeAttr(safeUrl(img.src || ''))}" alt="${escapeAttr(img.alt)}" style="width:100%;height:200px;object-fit:cover;display:block;border-radius:8px;background-color:#f1f5f9" />
|
||||
@@ -157,18 +157,30 @@ Gallery.craft = {
|
||||
if (lightbox) {
|
||||
gridIdAttr = ` id="${galleryId}_grid"`;
|
||||
lightboxHtml = `
|
||||
<div id="${galleryId}_overlay" onclick="${galleryId}_close()" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:9999;justify-content:center;align-items:center;cursor:pointer">
|
||||
<div id="${galleryId}_overlay" role="dialog" aria-modal="true" aria-label="Image preview" onclick="${galleryId}_close()" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.9);z-index:9999;justify-content:center;align-items:center;cursor:pointer">
|
||||
<img id="${galleryId}_img" src="" alt="" style="max-width:90%;max-height:90%;object-fit:contain;border-radius:8px" />
|
||||
</div>
|
||||
<script>
|
||||
function ${galleryId}_close(){document.getElementById('${galleryId}_overlay').style.display='none';}
|
||||
document.getElementById('${galleryId}_grid').addEventListener('click', function(e){
|
||||
var t = e.target.closest('[data-lb-src]');
|
||||
if(!t) return;
|
||||
var src = t.getAttribute('data-lb-src');
|
||||
function ${galleryId}_open(src){
|
||||
var o = document.getElementById('${galleryId}_overlay');
|
||||
document.getElementById('${galleryId}_img').src = src;
|
||||
o.style.display = 'flex';
|
||||
}
|
||||
document.getElementById('${galleryId}_grid').addEventListener('click', function(e){
|
||||
var t = e.target.closest('[data-lb-src]');
|
||||
if(!t) return;
|
||||
${galleryId}_open(t.getAttribute('data-lb-src'));
|
||||
});
|
||||
document.getElementById('${galleryId}_grid').addEventListener('keydown', function(e){
|
||||
if(e.key!=='Enter' && e.key!==' ') return;
|
||||
var t = e.target.closest('[data-lb-src]');
|
||||
if(!t) return;
|
||||
e.preventDefault();
|
||||
${galleryId}_open(t.getAttribute('data-lb-src'));
|
||||
});
|
||||
document.addEventListener('keydown', function(e){
|
||||
if(e.key==='Escape'){ ${galleryId}_close(); }
|
||||
});
|
||||
</script>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user