@@ -5,13 +5,17 @@ var ModalComponent = (function() {
55 'use strict' ;
66
77 var currentTrap = null ;
8+ var lastFocusedElement = null ;
9+ var overlayClickHandler = null ;
810
911 function show ( titleOrHtml , content , buttons ) {
1012 var overlay = Helpers . $ ( 'modal-overlay' ) ;
1113 var modal = Helpers . $ ( 'modal-container' ) ;
1214 if ( ! overlay || ! modal ) return ;
1315
1416 var html ;
17+ var accessibleTitle = '' ;
18+
1519 // Support two calling conventions:
1620 // 1. show(rawHtml) — guild.js style: pass pre-built HTML string
1721 // 2. show({title,text,buttons}) — object style used by marketplace/crafting
@@ -22,42 +26,57 @@ var ModalComponent = (function() {
2226 } else if ( titleOrHtml && typeof titleOrHtml === 'object' ) {
2327 // Object mode: {title, text, buttons}
2428 var opts = titleOrHtml ;
29+ accessibleTitle = opts . title || '' ;
2530 html = '<div class="modal-content">' ;
2631 if ( opts . title ) html += '<h2 class="modal-title">' + Helpers . escapeHtml ( opts . title ) + '</h2>' ;
2732 if ( opts . text ) html += '<p>' + Helpers . escapeHtml ( opts . text ) + '</p>' ;
2833 html += '<div class="modal-actions">' ;
2934 if ( opts . buttons ) {
3035 for ( var i = 0 ; i < opts . buttons . length ; i ++ ) {
3136 var b = opts . buttons [ i ] ;
32- html += '<button class="btn ' + ( b . className || 'btn-secondary' ) + '" data-action="' + i + '">' ;
37+ html += '<button type="button" class="btn ' + ( b . className || 'btn-secondary' ) + '" data-action="' + i + '">' ;
3338 html += Helpers . escapeHtml ( b . text || b . label || '' ) ;
3439 html += '</button>' ;
3540 }
3641 }
37- html += '<button class="btn btn-secondary modal-close" aria-label="' + Helpers . t ( 'close' ) + '">' + Helpers . t ( 'close' ) + '</button>' ;
42+ html += '<button type="button" class="btn btn-secondary modal-close" aria-label="' + Helpers . t ( 'close' ) + '">' + Helpers . t ( 'close' ) + '</button>' ;
3843 html += '</div></div>' ;
3944 buttons = opts . buttons ;
4045 } else {
4146 // Legacy positional: show(title, content, buttons)
47+ accessibleTitle = titleOrHtml || '' ;
4248 html = '<div class="modal-content">' ;
4349 html += '<h2 class="modal-title">' + Helpers . escapeHtml ( titleOrHtml ) + '</h2>' ;
4450 html += '<div class="modal-body">' + ( content || '' ) + '</div>' ;
4551 html += '<div class="modal-actions">' ;
4652 if ( buttons ) {
4753 for ( var bi = 0 ; bi < buttons . length ; bi ++ ) {
4854 var btn = buttons [ bi ] ;
49- html += '<button class="btn ' + ( btn . primary ? 'btn-primary' : 'btn-secondary' ) + '" data-action="' + bi + '">' ;
55+ html += '<button type="button" class="btn ' + ( btn . primary ? 'btn-primary' : 'btn-secondary' ) + '" data-action="' + bi + '">' ;
5056 html += Helpers . escapeHtml ( btn . label || btn . text || '' ) ;
5157 html += '</button>' ;
5258 }
5359 }
54- html += '<button class="btn btn-secondary modal-close" aria-label="' + Helpers . t ( 'close' ) + '">' + Helpers . t ( 'close' ) + '</button>' ;
60+ html += '<button type="button" class="btn btn-secondary modal-close" aria-label="' + Helpers . t ( 'close' ) + '">' + Helpers . t ( 'close' ) + '</button>' ;
5561 html += '</div></div>' ;
5662 }
5763
64+ lastFocusedElement = document . activeElement ;
5865 modal . innerHTML = html ;
66+ modal . className = 'modal show' ;
67+ modal . setAttribute ( 'role' , 'dialog' ) ;
68+ modal . setAttribute ( 'aria-modal' , 'true' ) ;
5969 overlay . classList . add ( 'show' ) ;
60- modal . classList . add ( 'show' ) ;
70+
71+ var titleEl = modal . querySelector ( '.modal-title, h1, h2, h3' ) ;
72+ if ( titleEl ) {
73+ if ( ! titleEl . id ) titleEl . id = 'modal-title' ;
74+ modal . setAttribute ( 'aria-labelledby' , titleEl . id ) ;
75+ modal . removeAttribute ( 'aria-label' ) ;
76+ } else {
77+ modal . removeAttribute ( 'aria-labelledby' ) ;
78+ modal . setAttribute ( 'aria-label' , accessibleTitle || 'Dialog' ) ;
79+ }
6180
6281 currentTrap = A11y . trapFocus ( modal ) ;
6382 A11y . focusFirst ( modal ) ;
@@ -66,7 +85,7 @@ var ModalComponent = (function() {
6685 var actionBtns = modal . querySelectorAll ( '[data-action]' ) ;
6786 for ( var j = 0 ; j < actionBtns . length ; j ++ ) {
6887 actionBtns [ j ] . addEventListener ( 'click' , function ( ) {
69- var idx = parseInt ( this . getAttribute ( 'data-action' ) ) ;
88+ var idx = parseInt ( this . getAttribute ( 'data-action' ) , 10 ) ;
7089 if ( buttons && buttons [ idx ] && ( buttons [ idx ] . action || buttons [ idx ] . onClick ) ) {
7190 var fn = buttons [ idx ] . action || buttons [ idx ] . onClick ;
7291 fn ( ) ;
@@ -76,9 +95,14 @@ var ModalComponent = (function() {
7695 }
7796 var closeBtn = modal . querySelector ( '.modal-close' ) ;
7897 if ( closeBtn ) closeBtn . addEventListener ( 'click' , hide ) ;
79- overlay . addEventListener ( 'click' , function ( e ) {
98+
99+ if ( overlayClickHandler ) {
100+ overlay . removeEventListener ( 'click' , overlayClickHandler ) ;
101+ }
102+ overlayClickHandler = function ( e ) {
80103 if ( e . target === overlay ) hide ( ) ;
81- } ) ;
104+ } ;
105+ overlay . addEventListener ( 'click' , overlayClickHandler ) ;
82106
83107 document . addEventListener ( 'keydown' , _escHandler ) ;
84108 }
@@ -87,16 +111,30 @@ var ModalComponent = (function() {
87111 var overlay = Helpers . $ ( 'modal-overlay' ) ;
88112 var modal = Helpers . $ ( 'modal-container' ) ;
89113 if ( overlay ) overlay . classList . remove ( 'show' ) ;
90- if ( modal ) modal . classList . remove ( 'show' ) ;
114+ if ( modal ) {
115+ modal . className = 'modal' ;
116+ modal . removeAttribute ( 'aria-labelledby' ) ;
117+ modal . removeAttribute ( 'aria-label' ) ;
118+ modal . removeAttribute ( 'aria-modal' ) ;
119+ modal . removeAttribute ( 'role' ) ;
120+ }
91121 if ( currentTrap ) { currentTrap ( ) ; currentTrap = null ; }
122+ if ( overlay && overlayClickHandler ) {
123+ overlay . removeEventListener ( 'click' , overlayClickHandler ) ;
124+ overlayClickHandler = null ;
125+ }
92126 document . removeEventListener ( 'keydown' , _escHandler ) ;
127+
128+ if ( lastFocusedElement && lastFocusedElement . focus ) {
129+ lastFocusedElement . focus ( ) ;
130+ }
93131 }
94132
95133 function _escHandler ( e ) {
96134 if ( e . key === 'Escape' ) hide ( ) ;
97135 }
98136
99- return { show : show , hide : hide } ;
137+ return { show : show , hide : hide , close : hide } ;
100138} ) ( ) ;
101139
102140// Convenience alias used across all screen modules
0 commit comments