1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[38753],{38753:(e,t,o)=>{o.r(t),o.d(t,{default:()=>G});var a=o(45993),s=o(75837),n=o(17832),i=o(77771),l=o(30845),r=o(41945),d=o(73950),c=o(22925),h=o(95521),u=o(14552),p=o(19914);let m=[{id:"showmodal",title:"Opened with showModal(), not the open attribute",why:"Only showModal() puts the dialog in the top layer, so only showModal() gets you a backdrop, an inert page behind it, and Escape. Writing open in the markup, or calling show(), gives you the other dialog: no backdrop, page still clickable, Escape does nothing. This is the number one reason a dialog looks like it opened but nothing dimmed.",severity:"trap",needs:"js"},{id:"backdrop",title:"The backdrop is actually styled",why:"A modal dialog paints a ::backdrop box behind itself, and the browser default is much lighter than most designs expect. It is a separate pseudo element, so nothing you set on the dialog reaches it.",severity:"trap",needs:"css"},{id:"animation",title:"The open and close animation actually runs",why:"A dialog goes from display none to displayed, and display is not an animatable property, so a plain transition on opacity never fires. The fix is three parts together: display and overlay both in the transition list with allow-discrete, and an @starting-style rule holding the entry values. Miss the overlay part and the exit animation plays after the dialog has already left the top layer, which looks like nothing happened.",severity:"trap",needs:"css"},{id:"display-hack",title:"A closed dialog is not forced to stay rendered",why:"The old workaround for the animation problem is to keep the dialog displayed at opacity zero so it has something to fade from. It leaves a closed dialog sitting in the page, catching clicks over whatever is underneath, and it breaks what showModal() is supposed to mean.",severity:"trap",needs:"css"},{id:"autofocus",title:"Focus starts on the control you chose",why:"showModal() focu
1ses the first focusable thing inside the dialog. On a delete confirmation that is very often the destructive button, so a keyboard user opens the dialog and presses Enter on Delete. autofocus on the safe control overrides it.",severity:"trap",needs:"html"},{id:"focus-restore",title:"Focus goes back where it came from",why:"The browser normally returns focus to whatever opened the dialog. It cannot when that element has been unmounted or re-rendered in the meantime, which is the everyday React case, and focus falls back to the top of the page.",severity:"polish",needs:"js"},{id:"focus-trap",title:"Tab stays inside the dialog",why:"You do not need a focus trap library for a modal dialog. showModal() makes the rest of the document inert, so Tab is already contained. A dialog opened with show() has none of that, and Tab walks straight out into the page behind.",severity:"trap",needs:"js"},{id:"escape",title:"Escape works, or there is another way out",why:"Escape fires a cancel event and then closes. Calling preventDefault inside cancel is the unsaved changes guard, and it is fine, right up until it is the only way out and there is no visible close control. Then the dialog is a trap.",severity:"trap",needs:"html"},{id:"form-cancel",title:"Cancel closes even when a field is required",why:'A form with method="dialog" still runs normal constraint validation before it submits. So a Cancel button sitting in a form with an empty required field does nothing at all when clicked, and the browser scrolls to the field instead. formnovalidate on Cancel is the whole fix, and almost nobody ships it.',severity:"trap",needs:"html"},{id:"return-value",title:"The page learns which button was pressed",why:"The close event fires the same way whether someone confirmed, cancelled, or pressed Escape. Without returnValue the page has no idea which of those happened.",severity:"polish",needs:"js"},{id:"scroll-lock",title:"The page behind stops scrolling",why:"The browser does not lock background scroll for a modal dialog. Put the pointer over the backdrop, spin the wheel, and the page behind slides around under your modal.",severity:"trap",needs:"js"},{id:"light-dismiss",title:"Clicking outside behaves itself",why:"There is no built in outside click close. The handler everyone copies compares the click target to the dialog, which works only by accident and breaks two ways: a child that fills the dialog swallows the click so it never closes, and dragging a text selection from inside to outside closes it when you did not mean to.",severity:"polish",needs:"html"},{id:"inert",title:"The page behind is inert",why:"Everything outside a modal dialog has to be unreachable by keyboard and by screen reader, not just dimmed. showModal() does that for you. show() does not, and setting inert by hand on top of showModal() is doing work the browser already did.",severity:"trap",needs:"js"},{id:"aria-name",title:"The dialog has an accessible name",why:"Point aria-labelledby at the heading inside the dialog, or use aria-label when there is no visible title. Without it a screen reader announces a dialog and then nothing about what it is for.",severity:"trap",needs:"html"},{id:"ua-reset",title:"The browser default box is reset",why:"The user agent stylesheet gives every dialog its own border, its own padding, and auto margins that center it. A bottom sheet or a full width panel that skips the reset ends up with a stray hairline border and padding nobody asked for.",severity:"polish",needs:"css"},{id:"reduced-motion",title:"Motion respects the system setting",why:"A dialog that slides and fades is exactly the kind of motion people turn off at the operating system level. One media query honors that.",severity:"polish",needs:"css"}],g=Object.fromEntries(m.map(e=>[e.id,e])),f={pass:"Handled",fail:"Not handled",unknown:"Cannot tell",na:"Does not apply"};var b=o(10974);function y(e){return e.replace(/\/\*[\s\S]*?\*\//g," ")}let w=/^(dialog|#[A-Za-z0-9_-]*(?:dialog|modal)[A-Za-z0-9_-]*|\.[A-Za-z0-9_-]*(?:dialog|modal)[A-Za-z0-9_-]*)$/i;function v(e){return e.replace(/[>+~]/g," ").split(/\s+/).map(e=>
1e.trim()).filter(Boolean)}function x(e,t){let o=e.replace(/(\[[^\]]*\]|::?[A-Za-z-]+(\([^)]*\))?)+$/,"");return!!o&&(t.has(o.toLowerCase())||w.test(o))}let k=(e,t)=>RegExp("(^|[;{\\s])"+t+"\\s*:","i").test(e),j=(e,t,o)=>{let a,s=RegExp("(^|[;{\\s])"+t+"\\s*:\\s*([^;}]*)","gi");for(;null!==(a=s.exec(e));)if(o.test(a[2]??""))return!0;return!1};function C(e){let t=e||"",o={pass:0,fail:0,unknown:0,na:0};if(0===t.trim().length)return{refused:!1,refusedReason:"",notes:[],findings:[],counts:o,dialogCount:0,ran:!1};if(t.length>12e4)return{refused:!0,refusedReason:`That paste is ${t.length.toLocaleString()} characters. Nothing over ${12e4.toLocaleString()} is read, so nothing at all was checked here and no result below should be treated as clean. Cut it down to the dialog, its CSS and its JavaScript and run it again.`,notes:[],findings:[],counts:o,dialogCount:0,ran:!1};let a=function(e){let t,o=[],a=[],s=/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi,n=/<script\b[^>]*>([\s\S]*?)<\/script\s*>/gi;for(;null!==(t=s.exec(e));)o.push(t[1]??"");for(;null!==(t=n.exec(e));)a.push(t[1]??"");let i=o.length>0,l=a.length>0,r=e.replace(s," "),d=e.replace(n," "),c=r.replace(n," ");return{html:(0,b.Y1)(c," "),css:y(i?o.join("\n"):d),js:y(l?a.join("\n"):r).replace(/(^|[^:"'`\\])\/\/[^\n]*/g,"$1 "),hadStyleTag:i,hadScriptTag:l}}(t),s=a.html,n=a.css,i=a.js,l=function(e){let t=new Set,o=e.match(/<dialog\b[^>]*>/gi)||[];for(let e of(o.length&&t.add("dialog"),o)){let o=e.match(/\bid\s*=\s*["']([^"']+)["']/i);o&&o[1]&&t.add("#"+o[1].trim().toLowerCase());let a=e.match(/\bclass\s*=\s*["']([^"']+)["']/i);if(a&&a[1])for(let e of a[1].split(/\s+/).filter(Boolean))t.add("."+e.toLowerCase())}return t}(s),r=function(e){let t,o=[],a=/([^{}]*)\{([^{}]*)\}/g;for(;null!==(t=a.exec(e));){let e=(t[1]??"").trim(),a=t[2]??"";e&&/[-a-z]+\s*:\s*[^;{}]/i.test(a)&&o.push({selector:e,body:a})}return o}(n),d=r.filter(e=>{var t;return t=e.selector,t.split(",").some(e=>v(e).some(e=>x(e,l)))}),c=r.filter(e=>{var t;return t=e.selector,!/::/.test(t)&&t.split(",").some(e=>{let t=v(e),o=t[t.length-1];return void 0!==o&&x(o,l)})}),h=/<dialog[\s>]/i.test(s),u=function(e){let t=e.search(/<dialog\b/i);if(t<0)return"";let o=e.lastIndexOf("</dialog"),a=o>t?o+9:e.length;return e.slice(t,a)}(s),p=(s.match(/<dialog[\s>]/gi)||[]).length,g=/role\s*=\s*["']dialog["']/i.test(s),f=d.length>0||/::backdrop/i.test(n),w=/\.showModal\s*\(|\.show\s*\(|addEventListener\s*\(|\.close\s*\(|\.open\b/.test(i),C=[];if(!h)if(g)C.push('No dialog element here. This looks like a div with role="dialog", which is the hand built pattern the native element replaces. Most of the markup checks below come back as cannot tell because there is no dialog for them to read.');else{let e=[];a.hadStyleTag&&e.push("a style block"),a.hadScriptTag&&e.push("a script block"),r.length&&e.push(`${r.length} CSS rules`),C.push(`No dialog element found in this paste. ${e.length?"What is here: "+e.join(", ")+".":"Nothing recognizable as markup either."} Paste the dialog markup along with its CSS and JavaScript for a real read.`)}p>1&&C.push(`This paste has ${p} dialog elements. The checks run over the whole paste, so a row marked handled may be satisfied by any one of them and not the one you care about. Audit them one at a time when you want a per dialog verdict.`),f||C.push("No CSS targeting the dialog was in this paste, so the CSS checks report cannot tell rather than guessing. Paste the stylesheet too, or at least the rules that name the dialog."),w||C.push("No JavaScript was in this paste, so the JavaScript checks report cannot tell. Paste the code that opens and closes it."),i.split("\n").reduce((e,t)=>Math.max(e,t.length),0)>400&&C.push("The JavaScript here looks minified. Reading minified code as text is unreliable, so treat every JavaScript verdict below as cannot tell no matter what it says, and re run this against the source version.");let N=/\.showModal\s*\(/.test(i),E=/(?<!\w)(?:\w+\.)?show\s*\(\s*\)/.test(i)&&!/\.showModal\s*\(/.test(i),T=/<dialog[^>]*\s(?:open\b|open\s*=)/i.test(s),$=[],S=(e,t,o,a)=>
1$.push({id:e,verdict:t,detail:o,code:a});T?S("showmodal","fail","Your dialog element carries the open attribute. That renders the non modal dialog, which has no backdrop, leaves the page behind fully clickable, and ignores Escape. Take the attribute off the markup and open it from script instead.","document.getElementById('your-dialog').showModal();"):E?S("showmodal","fail","This opens with show(), not showModal(). show() gives you a dialog that sits in the normal page flow with no top layer, so no backdrop renders and the rest of the page stays interactive.","dialog.showModal();"):N?S("showmodal","pass","Opened with showModal(), so the dialog goes into the top layer and the backdrop, the inert page behind it, and Escape all come for free."):S("showmodal","unknown",w?"There is JavaScript here but no call to showModal() or show() that can be read as text. If the open call is assembled from variables or lives in a framework wrapper, this one cannot be decided by reading.":"Nothing in this paste opens the dialog, so there is no way to tell which method is used. Paste the script that opens it.");let L=r.find(e=>/::backdrop/i.test(e.selector));f?L?S("backdrop","pass","There is a ::backdrop rule, so the area around the dialog is yours rather than the browser default."):S("backdrop","fail","No ::backdrop rule anywhere. The backdrop still renders under showModal(), it just uses the browser default, which is far lighter than most people expect and is why a modal can look like it did not open.","dialog::backdrop {\n background: rgb(2 6 23 / 0.55);\n}"):S("backdrop","unknown","No CSS for the dialog in this paste, so nothing to read.");let F=d.some(e=>k(e.body,"transition")||k(e.body,"animation"))||/@keyframes/i.test(n),M=function(e,t){let o,a=[],s=RegExp("@"+t+"\\b","gi");for(;null!==(o=s.exec(e));){let t=e.indexOf("{",o.index);if(t<0)continue;let s=0,n=t;for(;n<e.length;n++)if("{"===e[n])s++;else if("}"===e[n]&&0==--s)break;a.push(e.slice(t+1,n))}return a}(n,"starting-style"),O=M.length>0,q=M.some(e=>/::backdrop/i.test(e)),A=/allow-discrete/i.test(n),R=d.some(e=>j(e.body,"transition",/(^|[\s,])overlay(\s|$)/i)||j(e.body,"transition-property",/(^|[\s,])overlay(\s|,|$)/i));if(f)if(F){let e=[];O||e.push("an @starting-style rule with the entry values"),A||e.push("allow-discrete on the display part of the transition"),R||e.push("overlay in the transition list"),q||e.push("a second @starting-style for ::backdrop"),0===e.length?S("animation","pass","All four parts are here: @starting-style for the dialog, @starting-style for the backdrop, allow-discrete, and overlay in the transition list. That is the combination that actually runs."):S("animation","fail",`There is a transition on this dialog but it is missing ${e.join(", ")}. Without all of it the open transition never starts, because the dialog jumps straight from display none with no value to animate from, and the close transition plays after the dialog has already left the top layer.`,"dialog {\n opacity: 0;\n transition:\n opacity 200ms ease,\n display 200ms allow-discrete,\n overlay 200ms allow-discrete;\n}\n\ndialog[open] { opacity: 1; }\n\n@starting-style {\n dialog[open] { opacity: 0; }\n}\n\n@starting-style {\n dialog[open]::backdrop { opacity: 0; }\n}")}else S("animation","na","This dialog does not animate, so there is nothing here that can silently fail. Turn the animation on in the builder if you want one that works.");else S("animation","unknown","No CSS for the dialog in this paste, so nothing to read.");let D=c.find(e=>!/\[open\]/i.test(e.selector)&&j(e.body,"display",/^\s*(block|flex|grid|inline-block|inline-flex)\s*$/i)&&(j(e.body,"opacity",/^\s*0*\.?0+\s*$/)||j(e.body,"visibility",/hidden/i)));f?D?S("display-hack","fail",`The rule "${D.selector.replace(/\s+/g," ").slice(0,60)}" forces a closed dialog to stay rendered so it has something to fade from. That leaves an invisible dialog sitting over your page catching clicks, and it stops showModal() behaving like a modal at all. Delete the display line and let the allow-discrete transition handle it.`):S("display-hack","pass","No rule keeps a closed dialog rendered, so a closed dialog is genuinely out of the page."):S("display-hack","unknown","No CSS for the dialog in this paste, so nothing to read."),h?/\bautofocus\b/i.test(u)?S("autofocus","pass","An element carries autofocus, so focu
1s lands where you put it rather than on whichever control happens to be first."):S("autofocus","fail","Nothing carries autofocus. showModal() falls back to the first focusable element inside the dialog, which on a confirmation is often the destructive button, so a keyboard user can open the dialog and press Enter straight into it.",'<button value="cancel" formnovalidate autofocus>Cancel</button>'):S("autofocus","unknown","No dialog markup in this paste, so there is nothing to read the focus target from."),w?/activeElement/.test(i)&&/\.focus\s*\(/.test(i)?S("focus-restore","pass","The trigger is captured before opening and focus is put back afterwards, which covers the case where the browser cannot do it for you."):S("focus-restore","fail","Nothing captures the element that was focused before the dialog opened. The browser usually restores it on its own, so this is not a visible bug until the trigger gets re-rendered while the dialog is open, and then focus drops to the top of the page.","let lastFocused = null;\n// before showModal()\nlastFocused = document.activeElement;\n// in the close handler\nif (lastFocused && lastFocused.isConnected) lastFocused.focus();"):S("focus-restore","unknown","No JavaScript in this paste, so nothing to read."),N&&!E?S("focus-trap","pass","showModal() makes everything outside the dialog inert, so Tab is already contained. No trapping library needed."):E?S("focus-trap","fail","This dialog opens with show(), which leaves the rest of the page reachable. Tab out of the last control and focus walks into the page behind, and a screen reader can read all of it.","dialog.showModal();"):S("focus-trap","unknown",w?"No readable open call, so this depends on something that cannot be decided by reading the text.":"No JavaScript in this paste, so there is no way to tell how it opens.");let V=/addEventListener\s*\(\s*['"`]cancel['"`][\s\S]{0,240}?preventDefault/.test(i)||/oncancel[\s\S]{0,160}?preventDefault/i.test(i+s),W=/<button[^>]*>[^<]*\b(close|cancel|dismiss|no thanks|not now|ok|okay|got it|done|accept|continue)\b/i.test(u)||/formmethod\s*=\s*["']dialog["']/i.test(u)||/<form[^>]*method\s*=\s*["']dialog["']/i.test(u)&&/<button/i.test(u)||/aria-label\s*=\s*["'][^"']*close/i.test(u)||/\.close\s*\(/.test(i);h||w?V&&!W?S("escape","fail","The cancel event is blocked with preventDefault and there is no visible close control to be found. That is a dialog with no exit, which strands anyone on a keyboard.",'<button value="cancel" formnovalidate>Cancel</button>'):W?S("escape","pass",V?"Escape is blocked on purpose, and there is a visible close control, so the dialog still has a way out. Worth a second look if that guard was meant to be conditional on unsaved changes rather than always on.":"Escape closes it and there is a visible control too."):S("escape","fail","No close control found in this markup. Escape probably still works, but a dialog needs a visible way out too, and pointer users get nothing here.",'<button value="cancel" formnovalidate>Cancel</button>'):S("escape","unknown","Nothing here to read a close path from.");let B=/<form[^>]*method\s*=\s*["']dialog["']/i.test(u),I=u.match(/<button[\s\S]{0,400}?<\/button\s*>/gi)||[],z=I.filter(e=>/(value\s*=\s*["'][^"']*(cancel|close|dismiss|back)|>\s*[^<]*\b(cancel|close|dismiss|no thanks|not now|back)\b)/i.test(e)),J=z.filter(e=>!/formnovalidate/i.test(e)),Y=/<(?:input|select|textarea)\b[^>]*\brequired\b/i.test(u);B?0===z.length?S("form-cancel","na",'There is a form with method="dialog" but no cancel style button in it, so nothing is at risk of being blocked by validation.'):Y&&J.length>0?S("form-cancel","fail","Your form has a required field and the cancel button does not say formnovalidate. Click Cancel with that field empty and absolutely nothing happens. The browser runs validation first, refuses the submit, and points at the empty field instead of closing the dialog. This is the single most reported dialog bug and the fix is one attribute.",'<button value="cancel" formnovalidate>Cancel</button>'):J.length>0?S("form-cancel","pass","Cancel closes fine today because no field in the form is required. Add a required field later and it will stop closing, so putting formnovalidate on Cancel now saves you that afternoon.",'<button value="cancel" formnovalidate>Cancel</button>'):S("form-cancel","pass","Cancel carries formnovalidate, so it closes the dialog whether or not the form validates."):S("form-cancel","na",'No form with method="dialog" here, so this particular trap cannot bite. It is the one that catches people the moment they add one.');let X=I.length;h&&1===X&&!/returnValue/.test(i)?S("return-value","na","One button and nothing to choose between, so there is no answer for returnValue to carry. It starts mattering the moment a second button goes in."):w?/returnValue/.test(i)||/\.close\s*\(\s*['"`]/.test(i)?S("return-value","pass","The result is carried out of the dialog, so the page can tell confirm from cancel."):S("return-value","fail","Nothing reads returnValue and nothing passes a value into close(). The close event fires the same way for confirm, for cancel and for Escape, so right now the page cannot tell those three apart.","dialog.addEventListener('close', () => {\n console.log('closed with:', dialog.returnValue);\n});"):S("return-value","unknown","No JavaScript in this paste, so nothing to read.");let P=/(documentElement|document\.body|\bbody\b)\s*\.\s*style\s*\.\s*overflow/.test(i)||/classList\s*\.\s*\w+\s*\(\s*['"`][^'"`]*(no-?scroll|scroll-?lock|modal-?open|overflow-hidden
1)/i.test(i)||/overflow\s*:\s*hidden/i.test(r.filter(e=>/(^|[\s,])(html|body)(\s|$|[.:[])/i.test(e.selector)).map(e=>e.body).join(" "));if(w)if(P){let e=/clientWidth/.test(i)||/scrollbar-gutter/i.test(n);S("scroll-lock","pass",e?"Background scroll is locked and the scrollbar width is compensated, so the page does not jump sideways when the dialog opens.":"Background scroll is locked. The page will shift sideways by the scrollbar width as it goes on, which is the visible flicker people blame on the animation. Measure the gap and pad it out, or use scrollbar-gutter.",e?void 0:"const gap = window.innerWidth - document.documentElement.clientWidth;\ndocument.documentElement.style.paddingRight = gap + 'px';")}else S("scroll-lock","fail","Nothing stops the page behind from scrolling. The browser does not do this for a modal dialog, so put the pointer over the backdrop, spin the wheel, and the page slides around under your modal.","document.documentElement.style.overflow = 'hidden';\n// and put it back in the close handler\ndocument.documentElement.style.overflow = '';");else S("scroll-lock","unknown","No JavaScript in this paste, so nothing to read.");let _=/\bclosedby\s*=/i.test(s),H=/addEventListener\s*\(\s*['"`](?:click|mousedown|pointerdown)['"`][\s\S]{0,200}?\.target\s*===/.test(i)||/\.target\s*===\s*\w*(dialog|modal)\w*/i.test(i),K=/pointerdown/i.test(i)&&/getBoundingClientRect/.test(i);_?S("light-dismiss","pass","The closedby attribute is set, so the browser handles clicking outside and you carry no handler for it at all. That attribute is newer than the element itself, so check current support before you rely on it as the only way out."):K?S("light-dismiss","pass","Outside clicks are decided from the pointer position against the dialog box, which is the version that survives a full width child and a text selection dragged out of the dialog."):H?S("light-dismiss","fail","This is the copy and paste outside click handler that compares the event target to the dialog. It works by accident, because a click on the backdrop area reports the dialog as its target, and it breaks two ways: a child that fills the dialog swallows the click so it never closes, and selecting text inside the dialog and releasing outside closes the thing you were reading.","let downWasOutside = false;\ndialog.addEventListener('pointerdown', (e) => {\n const b = dialog.getBoundingClientRect();\n downWasOutside = e.clientX < b.left || e.clientX > b.right || e.clientY < b.top || e.clientY > b.bottom;\n});\ndialog.addEventListener('pointerup', (e) => {\n const b = dialog.getBoundingClientRect();\n const upOutside = e.clientX < b.left || e.clientX > b.right || e.clientY < b.top || e.clientY > b.bottom;\n if (downWasOutside && upOutside) dialog.close('dismiss');\n downWasOutside = false;\n});"):S("light-dismiss","na","No outside click close here. That is a fair choice for a confirmation, where a stray click should not count as an answer. If you meant to have one, it is not in this code.");let Z=/\binert\b/.test(i)||/\binert\b/i.test(s);E?Z?S("inert","pass","This is a non modal dialog and inert is being set by hand, which is what show() requires."):S("inert","fail","A dialog opened with show() leaves the whole page behind reachable by keyboard and by screen reader. If this is meant to be modal, switch to showModal() and the browser handles it. If it is genuinely non modal, that is fine, just do not put a backdrop on it.","dialog.showModal();"):N?S("inert","pass",Z?"showModal() already makes the rest of the document inert, so the inert attribute you are setting is doing nothing. Harmless, just not needed.":"showModal() makes everything outside the dialog inert. Nothing to add."):S("inert","unknown",w?"No readable open call, so this cannot be decided from the text.":"No JavaScript in this paste, so there is no way to tell how it opens.");let G=(s.match(/<dialog[^>]*>/i)||[""])[0]??"";h?/aria-labelledby\s*=|aria-label\s*=/i.test(G)?S("aria-name","pass",/role\s*=\s*["']dialog["']|aria-modal\s*=\s*["']true["']/i.test(G)?'The dialog has a name. It also carries role="dialog" or aria-modal="true", and under showModal() both of those are already implicit, so they add nothing and one day they will disagree with the element.':"The dialog has an accessible name, so a screen reader announces what it is for rather than just announcing a dialog."):S("aria-name","fail","The dialog has no aria-labelledby and no aria-label. A screen reader announces that a dialog opened and then says nothing about what it wants.",'<dialog id="confirm" aria-labelledby="confirm-title">\n <h2 id="confirm-title">Delete this project?</h2>'):S("aria-name","unknown","No dialog markup in this paste, so there is no element to read a name off.");let U=c.find(e=>k(e.body,"padding")&&k(e.body,"border")||j(e.body,"all",/unset|revert|initial/i));f?U?S("ua-reset","pass","The dialog box sets its own border and padding, so the browser defaults are not showing through."):S("ua-reset","fail","Nothing resets the browser default box. Every dialog starts with its own border, its own padding and auto margins that center it, which is why a bottom sheet or a full width panel turns up with a hairline border and an inset nobody asked for.","dialog {\n border: none;\n padding: 20px;\n margin: auto;\n}"):S("ua-reset","unknown","No CSS for the dialog in this paste, so nothing to read."),f?F?/prefers-reduced-motion/i.test(n)?S("reduced-motion","pass","The animation is behind a prefers-reduced-motion query, so the people who turned motion off at the system level get a dialog that just appears."):S("reduced-motion","fail","The dialog animates with no reduced motion query. Sliding panels are exactly the motion people switch off in their system settings.","@media (prefers-reduced-motion: reduce) {\n dialog,\n dialog::backdrop {\n transition-duration: 1ms;\n }\n}"):S("reduced-motion","na","Nothing animates here, so there is no motion to turn down."):S("reduced-motion","unknown","No CSS for the dialog in this paste, so nothing to read.");let Q=new Map($.map(e=>[e.id,e])),ee=m.map(e=>
1Q.get(e.id)??{id:e.id,verdict:"unknown",detail:"Not decided from this paste."});for(let e of ee)o[e.verdict]++;return{refused:!1,refusedReason:"",notes:C,findings:ee,counts:o,dialogCount:p,ran:!0}}var N=o(19226);let E={id:"confirm-dialog",title:"Delete this project?",body:"This removes the project and everything inside it. There is no undo.",confirmLabel:"Delete project",cancelLabel:"Cancel",showCancel:!0,useForm:!0,requiredField:!1,layout:"centered",backdrop:"dim",animate:!0,duration:200,scrollLock:!0,dismiss:"closedby",guardEscape:!1,returnValue:!0,restoreFocus:!0,framework:"html"},T=[{key:"confirm",label:"Confirm",note:"Two buttons, focus parked on the safe one, and the page finds out which was pressed.",patch:{title:"Delete this project?",body:"This removes the project and everything inside it. There is no undo.",confirmLabel:"Delete project",cancelLabel:"Cancel",showCancel:!0,useForm:!0,requiredField:!1,layout:"centered",dismiss:"closedby",guardEscape:!1,returnValue:!0}},{key:"alert",label:"Alert",note:"One button, Escape allowed, nothing to decide.",patch:{title:"Your export is ready",body:"We finished building the file. It has been saved to your downloads folder.",confirmLabel:"Got it",showCancel:!1,useForm:!0,requiredField:!1,layout:"centered",dismiss:"closedby",guardEscape:!1,returnValue:!1}},{key:"form",label:"Form with a required field",note:"The one that catches everyone. A required field blocks Cancel until Cancel says formnovalidate.",patch:{title:"Invite a teammate",body:"They get an email with a link to join. You can change their role later.",confirmLabel:"Send invite",cancelLabel:"Cancel",showCancel:!0,useForm:!0,requiredField:!0,layout:"centered",dismiss:"none",guardEscape:!1,returnValue:!0}},{key:"consent",label:"Consent, no light dismiss",note:"Escape is blocked on purpose, so the visible button has to be the way out.",patch:{title:"Before you continue",body:"We use cookies to keep you signed in and to count visits. Nothing is shared with advertisers.",confirmLabel:"Accept",cancelLabel:"Only what is needed",showCancel:!0,useForm:!0,requiredField:!1,layout:"centered",dismiss:"none",guardEscape:!0,returnValue:!0}},{key:"sheet",label:"Bottom sheet",note:"Slides up from the bottom edge, which means resetting the browser default box first.",patch:{title:"Share this page",body:"Pick where it goes. The link stays the same wherever you send it.",confirmLabel:"Copy link",cancelLabel:"Close",showCancel:!0,useForm:!0,requiredField:!1,layout:"sheet",dismiss:"manual",guardEscape:!1,returnValue:!0}},{key:"drawer",label:"Side drawer",note:"Full height panel off the right edge, same top layer rules.",patch:{title:"Filters",body:"Narrow the list down. Everything applies as soon as you close this panel.",confirmLabel:"Ap
1ply",cancelLabel:"Reset",showCancel:!0,useForm:!0,requiredField:!1,layout:"drawer",dismiss:"manual",guardEscape:!1,returnValue:!0}}];function $(e){return(e||"").trim().replace(/[^A-Za-z0-9_-]/g,"").replace(/^[^A-Za-z_]+/,"")||"confirm-dialog"}function S(e){return Number.isFinite(e)?Math.min(600,Math.max(60,Math.round(e))):200}let L=(e,t,o)=>t.includes(e)?e:o,F=(e,t,o=200)=>"string"==typeof e?e.slice(0,o):t,M=(e,t)=>"boolean"==typeof e?e:t;function O(e){let t=e&&"object"==typeof e?e:{};return{id:$(F(t.id,E.id,60)),title:F(t.title,E.title),body:F(t.body,E.body,400),confirmLabel:F(t.confirmLabel,E.confirmLabel,60),cancelLabel:F(t.cancelLabel,E.cancelLabel,60),showCancel:M(t.showCancel,E.showCancel),useForm:M(t.useForm,E.useForm),requiredField:M(t.requiredField,E.requiredField),layout:L(t.layout,["centered","sheet","drawer","full"],E.layout),backdrop:L(t.backdrop,["dim","frosted","light"],E.backdrop),animate:M(t.animate,E.animate),duration:S(Number(t.duration)),scrollLock:M(t.scrollLock,E.scrollLock),dismiss:L(t.dismiss,["none","closedby","manual"],E.dismiss),guardEscape:M(t.guardEscape,E.guardEscape),returnValue:M(t.returnValue,E.returnValue),restoreFocus:M(t.restoreFocus,E.restoreFocus),framework:L(t.framework,["html","react","vue"],E.framework)}}let q={centered:{box:["margin: auto;","max-width: 32rem;","width: calc(100% - 2rem);","border-radius: 12px;"],from:"0 12px"},sheet:{box:["margin: auto auto 0;","width: 100%;","max-width: none;","border-radius: 16px 16px 0 0;","border-bottom: none;"],from:"0 100%"},drawer:{box:["margin: 0 0 0 auto;","width: min(24rem, 92vw);","height: 100dvh;","max-height: 100dvh;","border-radius: 0;"],from:"100% 0"},full:{box:["margin: 0;","width: 100vw;","max-width: none;","height: 100dvh;","max-height: none;","border-radius: 0;"],from:"0 0"}},A={dim:["background: rgb(2 6 23 / 0.55);"],light:["background: rgb(15 23 42 / 0.22);"],frosted:["background: rgb(2 6 23 / 0.35);","/* backdrop-filter is the newer part of this rule. Where it is not supported you"," still get the flat tint above, so this degrades on its own. */","backdrop-filter: blur(5px);"]};function R(e){let t={...e,id:$(e.id)},o=t.id,a=(0,N.X)(t.title||"Dialog"),s=(0,N.X)(t.body||""),n=(0,N.X)(t.confirmLabel||"OK"),i=(0,N.X)(t.cancelLabel||"Cancel"),l=t.requiredField?"field":t.showCancel?"cancel":"confirm",r=[];r.push(`<button type="button" id="${o}-open">Open the dialog</button>`),r.push("");let d=[`id="${o}"`,`aria-labelledby="${o}-title"`];"closedby"===t.dismiss&&d.push('closedby="closerequest"'),r.push(`<dialog ${d.join(" ")}>`);let c=[];c.push(`<h2 id="${o}-title">${a}</h2>`),s&&c.push(`<p>${s}</p>`),t.requiredField&&(c.push(`<label for="${o}-email">Email address</label>`),c.push(`<input type="email" id="${o}-email" name="email" required${"field"===l?" autofocus":""}>`));let h=[];for(let e of(t.useForm?(t.showCancel&&h.push(`<button value="cancel" formnovalidate${"cancel"===l?" autofocus":""}>${i}</button>`),h.push(`<button value="confirm" class="primary"${"confirm"===l?" autofocus":""}>${n}</button>`)):(t.showCancel&&h.push(`<button type="button" id="${o}-cancel"${"cancel"===l?" autofocus":""}>${i}</button>`),h.push(`<button type="button" id="${o}-confirm" class="primary"${"confirm"===l?" autofocus":""}>${n}</button>`)),c.push('<div class="dialog-actions">'),h))c.push(" "+e);if(c.push("</div>"),t.useForm){for(let e of(r.push(' <form method="dialog">'),c))r.push(" "+e);r.push(" </form>")}else for(let e of c)r.push(" "+e);return r.push("</dialog>"),r.join("\n")}function D(e){let t={...e,id:$(e.id)},o=t.id,a=S(t.duration),s=q[t.layout],n=[];return n.push(["/* The browser gives every dialog its own border, padding and auto margins."," Reset them here or they turn up in your design uninvited. */",`#${o} {`," border: 1px solid rgb(15 23 42 / 0.14);"," padding: 20px;"," background: #ffffff;"," color: #0f172a;"," font: inherit;",...s.box.map(e=>" "+e),"}"].join("\n")),n.push(["/* ::backdrop is a separate box behind the dialog. It only exists for a dialog"," opened with showModal(), and it inherits nothing from the rule above. */",`#${o}::backdrop {`,...A[t.backdrop].map(e=>" "+e),"}"].join("\n")),n.push(`#${o} h2 { 2 margin: 0 0 8px; 3 font-size: 1.125rem; 4} 5 6#${o} p { 7 margin: 0 0 16px; 8 line-height: 1.5; 9} 10 11#${o} .dialog-actions { 12 display: flex; 13 gap: 8px; 14 justify-content: flex-end; 15} 16 17#${o} button { 18 font: inherit; 19 padding: 8px 14px; 20 border-radius: 8px; 21 border: 1px solid rgb(15 23 42 / 0.18); 22 background: #f1f5f9; 23 cursor: pointer; 24} 25 26#${o} button.primary { 27 background: #0f172a; 28 border-color: #0f172a; 29 color: #ffffff; 30}`),t.requiredField&&n.push(`#${o} label { 31 display: block; 32 margin-bottom: 4px; 33 font-size: 0.875rem; 34} 35 36#${o} input { 37 font: inherit; 38 width: 100%; 39 box-sizing: border-box; 40 padding: 8px 10px; 41 margin-bottom: 16px; 42 border: 1px solid rgb(15 23 42 / 0.25); 43 border-radius: 8px; 44}`),t.animate&&(n.push(`/* The animation, and this is the part that trips people up. 45 A dialog moves between display none and displayed, so a plain transition on 46 opacity never fires. Three things have to be true together: display and overlay 47 are both in the transition list with allow-discrete, and @starting-style holds 48 the values it animates FROM on the way in. overlay is controlled by the browser 49 and can only be transitioned, never set. Leave it out and the exit animation 50 plays after the dialog has already dropped out of the top layer, which reads as 51 nothing happening at all. */ 52#${o} { 53 opacity: 0; 54 translate: ${s.from}; 55 transition: 56 opacity ${a}ms ease, 57 translate ${a}ms ease, 58 display ${a}ms allow-discrete, 59 overlay ${a}ms allow-discrete; 60} 61 62#${o}[open] { 63 opacity: 1; 64 translate: 0 0; 65} 66 67@starting-style { 68 #${o}[open] { 69 opacity: 0; 70 translate: ${s.from}; 71 } 72}`),n.push(`/* The backdrop is its own box, so it needs its own transition and its own 73 @starting-style. Nothing above reaches it. */ 74#${o}::backdrop { 75 opacity: 0; 76 transition: 77 opacity ${a}ms ease, 78 display ${a}ms allow-discrete, 79 overlay ${a}ms allow-discrete; 80} 81 82#${o}[open]::backdrop { 83 opacity: 1; 84} 85 86@starting-style { 87 #${o}[open]::backdrop { 88 opacity: 0; 89 } 90}`),n.push(`@media (prefers-reduced-motion: reduce) { 91 #${o}, 92 #${o}::backdrop { 93 transition-duration: 1ms; 94 } 95}`)),n.join("\n\n")}function V(e){let t={...e,id:$(e.id)},o=t.id,a=[];a.push(`const dialog = document.getElementById('${o}'); 96const openButton = document.getElementById('${o}-open');`);let s=[];t.restoreFocus&&s.push("let lastFocused = null;"),t.scrollLock&&s.push("let scrollbarGap = 0;"),"manual"===t.dismiss&&s.push("let downWasOutside = false;"),s.length&&a.push(s.join("\n"));let n=[];t.restoreFocus&&(n.push(" // Keep a handle on the trigger. The browser normally restores focus for you,"),n.push(" // but it cannot if this element gets re-rendered while the dialog is open."),n.push(" lastFocused = document.activeElement;")),t.scrollLock&&n.push(" lockScroll();"),n.push(" dialog.showModal();"),a.push(["openButton.addEventListener('click', () => {",...n,"});"].join("\n"));let i=[];if(t.scrollLock&&i.push(" unlockScroll();"),t.restoreFocus&&i.push(" if (lastFocused && lastFocused.isConnected) lastFocused.focus();"),t.returnValue&&(i.push(" // 'confirm', 'cancel', or an empty string when it was dismissed instead"),i.push(" console.log('closed with:', dialog.returnValue);")),i.length&&a.push(["dialog.addEventListener('close', () => {",...i,"});"].join("\n")),!t.useForm){let e=[];t.showCancel&&e.push(`document.getElementById('${o}-cancel').addEventListener('click', () => dialog.close('cancel'));`),e.push(`document.getElementById('${o}-confirm').addEventListener('click', () => dialog.close('confirm'));`),a.push(e.join("\n"))}return t.guardEscape&&a.push("// Escape fires cancel before it closes anything, so this is where an unsaved\n// changes guard goes. Only block
96it while a visible button can still get out,\n// otherwise the dialog has no exit for a keyboard user.\ndialog.addEventListener('cancel', (event) => {\n event.preventDefault();\n});"),"manual"===t.dismiss&&a.push("// Click outside to close, the version that holds up. A click on the backdrop area\n// reports the dialog itself as the target, which is why both listeners sit on the\n// dialog. Comparing coordinates against the box means a child that fills the dialog\n// cannot swallow the click, and a text selection dragged from inside to outside no\n// longer closes the thing you were reading.\ndialog.addEventListener('pointerdown', (event) => {\n downWasOutside = isOutside(event, dialog);\n});\n\ndialog.addEventListener('pointerup', (event) => {\n if (downWasOutside && isOutside(event, dialog)) dialog.close('dismiss');\n downWasOutside = false;\n});\n\nfunction isOutside(event, element) {\n const box = element.getBoundingClientRect();\n return (\n event.clientX < box.left ||\n event.clientX > box.right ||\n event.clientY < box.top ||\n event.clientY > box.bottom\n );\n}"),t.scrollLock&&a.push("// The browser does not stop the page behind a modal from scrolling, so this does.\n// The gap is measured, never a hard coded pixel value, because the scrollbar is a\n// different width on every platform and zero on most phones.\nfunction lockScroll() {\n scrollbarGap = window.innerWidth - document.documentElement.clientWidth;\n document.documentElement.style.overflow = 'hidden';\n document.documentElement.style.paddingRight = scrollbarGap + 'px';\n}\n\nfunction unlockScroll() {\n document.documentElement.style.overflow = '';\n document.documentElement.style.paddingRight = '';\n}"),a.join("\n\n")}var W=o(35339),B=o(99935);let I="tb-dialog-preview",z="toolbreezy:html-dialog-generator",J=["hdgs","hdgc"];function Y(e){if(!e)return{};try{let t=JSON.parse(e);return t&&"object"==typeof t?t:{}}catch{return{}}}function X({on:e,label:t,hint:o,onClick:s}){return(0,a.jsxs)("button",{type:"button",onClick:s,"aria-pressed":e,className:"flex w-full items-start gap-2 rounded-md border p-2 text-left hover:bg-muted",children:[(0,a.jsx)("span",{className:(0,u.cn)("mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded border text-[10px] font-bold",e?"border-sky-600 bg-sky-600 text-white":"border-input"),"aria-hidden":"true",children:e?"x":""}),(0,a.jsxs)("span",{className:"min-w-0",children:[(0,a.jsx)("span",{className:"block text-sm font-medium",children:t}),o&&(0,a.jsx)("span",{className:"block text-xs text-muted-foreground",children:o})]})]})}function P({value:e,options:t,onChange:o,label:s}){return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)("div",{className:"text-xs font-medium text-muted-foreground",children:s}),(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:t.map(t=>(0,a.jsx)("button",{type:"button",onClick:()=>o(t.value),"aria-pressed":e===t.value,className:(0,u.cn)("rounded border px-2 py-1 text-xs",e===t.value?"border-sky-600 bg-sky-600 text-white":"hover:bg-muted"),children:t.label},t.value))})]})}let _={pass:"border-emerald-600/40 bg-emerald-50 text-emerald-800 dark:bg-emerald-950/40 dark:text-emerald-300",fail:"border-red-600/40 bg-red-50 text-red-800 dark:bg-red-950/40 dark:text-red-300",unknown:"border-amber-600/40 bg-amber-50 text-amber-800 dark:bg-amber-950/40 dark:text-amber-300",na:"border-slate-500/30 bg-muted text-muted-foreground"};function H({text:e,label:t,onCopy:o,copied:s}){return(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,a.jsx)("span",{className:"text-xs font-semibold uppercase tracking-wide text-muted-foreground",children:t}),(0,a.jsx)(i.$,{type:"button",size:"sm",variant:"outline",onClick:o,children:s?"Copied":"Copy"})]}),(0,a.jsx)("pre",{className:"max-h-80 overflow-auto rounded-md border bg-muted/40 p-3 font-mono text-xs leading-relaxed",children:e})]})}function K(e){if(!e)return"nothing";let t=e.tagName.toLowerCase();if("body"===t||"html"===t)return t+" (focus is outside the dialog)";let o=(e.textContent||"").trim().slice(0,24),a=e.id?"#"+e.id:"";return`${t}${a}
96${o?` "${o}"`:""}`}function Z({cfg:e}){let t=(0,s.useRef)(null),o=(0,s.useRef)(null),n=(0,s.useRef)(null),l=(0,s.useRef)(!1),[r,d]=(0,s.useState)(!1),[c,h]=(0,s.useState)("nothing"),[u,p]=(0,s.useState)("nothing yet"),[m,g]=(0,s.useState)(""),f=e.requiredField?"field":e.showCancel?"cancel":"confirm",b=(0,s.useCallback)(()=>{if(!e.scrollLock||l.current)return;let t=window.innerWidth-document.documentElement.clientWidth;document.documentElement.style.overflow="hidden",document.documentElement.style.paddingRight=t+"px",l.current=!0},[e.scrollLock]),y=(0,s.useCallback)(()=>{l.current&&(document.documentElement.style.overflow="",document.documentElement.style.paddingRight="",l.current=!1)},[]);(0,s.useEffect)(()=>{let o=t.current;if(!o)return;o.querySelectorAll("[autofocus]").forEach(e=>e.removeAttribute("autofocus"));let a="field"===f?o.querySelector("input"):"cancel"===f?o.querySelector('button[value="cancel"], button[data-role="cancel"]'):o.querySelector('button[value="confirm"], button[data-role="confirm"]');a&&a.setAttribute("autofocus",""),"closedby"===e.dismiss?o.setAttribute("closedby","closerequest"):o.removeAttribute("closedby")},[e,f]),(0,s.useEffect)(()=>{let o=t.current;if(!o)return;let a=t=>{p("cancel (a close request, usually Escape)"),e.guardEscape&&(t.preventDefault(),p("cancel, blocked by preventDefault, so it stayed open"))},s=()=>{y(),d(!1),g(o.returnValue||"(empty, so it was dismissed rather than answered)"),p("close");let t=n.current;e.restoreFocus&&t&&t.isConnected&&t.focus(),h(K(document.activeElement))};return o.addEventListener("cancel",a),o.addEventListener("close",s
96),()=>{o.removeEventListener("cancel",a),o.removeEventListener("close",s)}},[e.guardEscape,e.restoreFocus,y]),(0,s.useEffect)(()=>{if(!r)return;let e=()=>h(K(document.activeElement)),t=e=>{"Tab"===e.key&&p(e.shiftKey?"Shift and Tab":"Tab"),"Escape"===e.key&&p("Escape pressed")};return document.addEventListener("focusin",e),document.addEventListener("keydown",t),()=>{document.removeEventListener("focusin",e),document.removeEventListener("keydown",t)}},[r]),(0,s.useEffect)(()=>{let o=t.current;if(!o||"manual"!==e.dismiss)return;let a=!1,s=e=>{let t=o.getBoundingClientRect();return e.clientX<t.left||e.clientX>t.right||e.clientY<t.top||e.clientY>t.bottom},n=e=>{a=s(e)},i=e=>{a&&s(e)&&o.close("dismiss"),a=!1};return o.addEventListener("pointerdown",n),o.addEventListener("pointerup",i),()=>{o.removeEventListener("pointerdown",n),o.removeEventListener("pointerup",i)}},[e.dismiss]),(0,s.useEffect)(()=>y,[y]);let w=e=>t.current?.close(e),v=(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("h2",{id:`${I}-title`,children:e.title||"Dialog"}),e.body?(0,a.jsx)("p",{children:e.body}):null,e.requiredField?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("label",{htmlFor:`${I}-email`,children:"Email address"}),(0,a.jsx)("input",{type:"email",id:`${I}-email`,name:"email",required:!0})]}):null,(0,a.jsxs)("div",{className:"dialog-actions",children:[e.showCancel?e.useForm?(0,a.jsx)("button",{value:"cancel",formNoValidate:!0,children:e.cancelLabel||"Cancel"}):(0,a.jsx)("button",{type:"button","data-role":"cancel",onClick:()=>w("cancel"),children:e.cancelLabel||"Cancel"}):null,e.useForm?(0,a.jsx)("button",{value:"confirm",className:"primary",children:e.confirmLabel||"OK"}):(0,a.jsx)("button",{type:"button","data-role":"confirm",className:"primary",onClick:()=>w("confirm"),children:e.confirmLabel||"OK"})]})]});return(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsx)("style",{dangerouslySetInnerHTML:{__html:D({...e,id:I})}}),(0,a.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,a.jsx)(i.$,{type:"button",ref:o,onClick:()=>{let e=t.current;e&&!e.open&&(n.current=o.current,b(),e.showModal(),d(!0),g(""),p("opened with showModal()"),requestAnimationFrame(()=>h(K(document.activeElement))))},children:"Open the dialog"}),(0,a.jsx)("span",{className:"text-xs text-muted-foreground",children:"This is the generated dialog, running. Press Tab, Shift and Tab, and Escape yourself."})]}),(0,a.jsx)("dialog",{id:I,ref:t,"aria-labelledby":`${I}-title`,children:e.useForm?(0,a.jsx)("form",{method:"dialog",children:v}):v}),(0,a.jsxs)("div",{className:"space-y-1 rounded-md border bg-muted/30 p-3 font-mono text-xs",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("span",{className:"text-muted-foreground",children:"focus is on: "}),c]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("span",{className:"text-muted-foreground",children:"last event: "}),u]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("span",{className:"text-muted-foreground",children:"returnValue: "}),m||"not closed yet"]})]}),(0,a.jsx)("p",{className:"text-xs text-muted-foreground",children:"Only what this page can genuinely observe is listed here, which is the focused element and the events the dialog fired. It says nothing about what a screen reader announces."})]})}function G(){let[e,t]=(0,s.useState)(E),[o,b]=(0,s.useState)("build"),[y,w]=(0,s.useState)(""),[v,x]=(0,s.use
96State)(""),[k,j]=(0,s.useState)(!1),[L,F]=(0,s.useState)(""),[M,q]=(0,s.useState)(null),A=(0,s.useRef)(null);(0,s.useEffect)(()=>{try{let e=(0,c.qA)(window.location.search,window.location.hash);if((0,c.Yc)(e,J)){let o={...Y(e.get("hdgs")),...Y(e.get("hdgc"))};Object.keys(o).length>0&&t(O(o));return}let o=(0,h.y5)(z);o.found&&t(O(o.value))}catch{}},[]),(0,n.Vd)(z,()=>JSON.stringify(e),[e]);let I=(e,o)=>t(t=>({...t,[e]:o})),K=(0,s.useMemo)(()=>R(e),[e]),G=(0,s.useMemo)(()=>D(e),[e]),U=(0,s.useMemo)(()=>V(e),[e]),Q=(0,s.useMemo)(()=>(function(e){let t={...e,id:$(e.id)},o=t.id,a=t.title||"Dialog",s=t.body||"",n=t.confirmLabel||"OK",i=t.cancelLabel||"Cancel";return["import { useEffect, useRef } from 'react';","","export function ConfirmDialog({ open, onClose }) {"," const ref = useRef(null);"," const restoreTo = useRef(null);"," const initialFocus = useRef(null);",""," useEffect(() => {"," const el = ref.current;"," if (!el) return;"," if (open && !el.open) {"," restoreTo.current = document.activeElement;"," el.showModal();
96"," // showModal() focuses the first focusable child, which on a confirmation is"," // often the destructive button. React's autoFocus prop will not fix that,"," // because it calls focus() at mount rather than writing the attribute the"," // browser reads when the dialog opens. So put focus where you want it."," if (initialFocus.current) initialFocus.current.focus();"," } else if (!open && el.open) {"," el.close();"," }"," }, [open]);",""," function handleClose() {"," const back = restoreTo.current;"," // React may have replaced the trigger while the dialog was open, and the browser"," // cannot restore focus to an element that is no longer in the page."," if (back && back.isConnected) back.focus();"," onClose(ref.current ? ref.current.returnValue : '');"," }",""," return ("," // Never write <dialog open> in JSX. That renders the non modal dialog: no backdrop,"," // the page behind still clickable, Escape does nothing. Call showModal() on a ref.",` <dialog id="${o}" ref={ref} aria-labelledby="${o}-title" onClose={handleClose}>`,' <form method="dialog">',` <h2 id="${o}-title">${a}</h2>`,...s?[` <p>${s}</p>`]:[],' <div className="dialog-actions">',...t.showCancel?[` <button value="cancel" formNoValidate ref={initialFocus}>${i}</button>`,` <button value="confirm" className="primary">${n}</button>`]:[` <button value="confirm" className="primary" ref={initialFocus}>${n}</button>`]," </div>"," </form>"," </dialog>"," );","}"].join("\n")})(e),[e]),ee=(0,s.useMemo)(()=>(function(e){let t={...e,id:$(e.id)},o=t.id,a=t.title||"Dialog",s=t.body||"",n=t.confirmLabel||"OK",i=t.cancelLabel||"Cancel";return["<script setup>","import { ref, watch } from 'vue';","","const props = defineProps({ open: Boolean });","const emit = defineEmits(['close']);","const el = ref(null);","let restoreTo = null;","","// The open prop drives showModal(), never the open attribute. Binding :open would","// give you the non modal dialog with no backdrop.","watch("," () => props.open,"," (isOpen) => {"," if (!el.value) return;"," if (isOpen && !el.value.open) {"," restoreTo = document.activeElement;"," el.value.showModal();"," } else if (!isOpen && el.value.open) {"," el.value.close();"," }"," },",");","","function onClose() {"," if (restoreTo && restoreTo.isConnected) restoreTo.focus();"," emit('close', el.value.returnValue);","}","<\/script>","","<template>",` <dialog id="${o}" ref="el" aria-labelledby="${o}-title" @close="onClose">`,' <form method="dialog">',` <h2 id="${o}-title">${a}</h2>`,...s?[` <p>${s}</p>`]:[],' <div class="dialog-actions">',...t.showCancel?[` <button value="cancel" formnovalidate autofocus>${i}</button>`,` <button value="confirm" class="primary">${n}</button>`]:[` <button value="confirm" class="primary" autofocus>${n}</button>`]," </div>"," </form>"," </dialog>","</template>"].join("\n")})(e),[e]),et=(0,s.useMemo)(()=>C([K,"<style>",G,"</style>\n<script>",U,"<\/script>"].join("\n")),[K,G,U]),eo=(0,s.useMemo)(()=>C(v),[v]);async function ea(e,t){if(!e)return;let{ok:o,note:a}=await (0,B.copyText)(e);o&&(w(t),setTimeout(()=>w(e=>e===t?"":e),1500)),q(a)}let es=(0,s.useCallback)(async e=>{if(!e||0===e.length)return;let t=Array.from(e),o=t.slice(0,6),a=t.length-o.length,s=[],n=[];for(let e of o){let t=await (0,p.G)(e);s.push(`/* ${e.name} */ 97`+t.text),""!==t.note&&n.push(`${e.name}: ${t.note}`)}let i=s.join("\n\n");x(i);let l=[`Read ${o.length} file${1===o.length?"":"s"}.`];l.push(...n),a>0&&l.push(`${a} more were not opened at all, because 6 is the limit.`),i.length>12e4&&l.push("The combined text is over the size limit, so the audit below was refused rather than run on part of it."),F(l.join(" "))},[]),en=(0,s.useMemo)(()=>({id:e.id,title:e.title,body:e.body,confirmLabel:e.confirmLabel,cancelLabel:e.cancelLabel}),[e.id,e.title,e.body,e.confirmLabel,e.cancelLabel]),ei=(0,s.useMemo)(()=>{let t={...e};for(let e of Object.keys(en))delete t[e];return JSON.stringify(t)},[e,en]),el=(0,s.useMemo)(()=>JSON.stringify(en),[en]),er=ei+el,ed=(0,s.useMemo)(()=>JSON.stringify({...en,body:""}),[en]),ec=(0,s.useCallback)(()=>{let e=er.length<=2e3,t=!e&&ei.length+ed.length<=2e3;return(0,c.CZ)([{key:"hdgs",value:ei},{key:"hdgc",value:e?el:t?ed:null,personal:"the dialog id, title, body and button labels you typed"}])},[ei,el,ed,er.length]),eh=er.length<=2e3?"full":ei.length+ed.length<=2e3?"partial":"none";return(0,a.jsxs)("div",{className:"space-y-5",children:[(0,a.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,a.jsx)(i.$,{type:"button",variant:"build"===o?"default":"outline",onClick:()=>b("build"),children:"Build one"}),(0,a.jsx)(i.$,{type:"button",variant:"audit"===o?"default":"outline",onClick:()=>b("audit"),children:"Audit the one you have"})]}),"build"===o?(0,a.jsxs)("div",{className:"space-y-5",children:[(0,a.jsxs)("div",{className:"space-y-2 rounded-lg border p-3",children:[(0,a.jsx)("div",{className:"text-sm font-medium",children:"Start from a shape"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2",children:T.map(e=>(0,a.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>{let o;return o=e.patch,t(e=>({...e,...o}
97))},children:e.label},e.key))}),(0,a.jsx)("p",{className:"text-xs text-muted-foreground",children:"Each preset changes the code it writes, not just the wording. Everything runs in your browser and nothing you type is uploaded."})]}),(0,a.jsxs)("div",{className:"grid gap-4 lg:grid-cols-2",children:[(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsxs)("div",{className:"grid gap-2 sm:grid-cols-2",children:[(0,a.jsxs)("label",{className:"space-y-1",children:[(0,a.jsx)("span",{className:"text-xs font-medium text-muted-foreground",children:"Element id"}),(0,a.jsx)(l.p,{value:e.id,onChange:e=>I("id",e.target.value),maxLength:60,className:"font-mono text-xs"})]}),(0,a.jsxs)("label",{className:"space-y-1",children:[(0,a.jsx)("span",{className:"text-xs font-medium text-muted-foreground",children:"Heading"}),(0,a.jsx)(l.p,{value:e.title,onChange:e=>I("title",e.target.value),maxLength:200})]})]}),(0,a.jsxs)("label",{className:"block space-y-1",children:[(0,a.jsx)("span",{className:"text-xs font-medium text-muted-foreground",children:"Body text"}),(0,a.jsx)(r.T,{value:e.body,onChange:e=>I("body",e.target.value),rows:2,maxLength:400})]}),(0,a.jsxs)("div",{className:"grid gap-2 sm:grid-cols-2",children:[(0,a.jsxs)("label",{className:"space-y-1",children:[(0,a.jsx)("span",{className:"text-xs font-medium text-muted-foreground",children:"Confirm button"}),(0,a.jsx)(l.p,{value:e.confirmLabel,onChange:e=>I("confirmLabel",e.target.value),maxLength:60})]}),(0,a.jsxs)("label",{className:"space-y-1",children:[(0,a.jsx)("span",{className:"text-xs font-medium text-muted-foreground",children:"Cancel button"}),(0,a.jsx)(l.p,{value:e.cancelLabel,onChange:e=>I("cancelLabel",e.target.value),maxLength:60,disabled:!e.showCancel})]})]}),(0,a.jsx)(P,{label:"Layout",value:e.layout,onChange:e=>I("layout",e),options:[{value:"centered",label:"Centered"},{value:"sheet",label:"Bottom sheet"},{value:"drawer",label:"Side drawer"},{value:"full",label:"Full screen"}]}),(0,a.jsx)(P,{label:"Backdrop",value:e.backdrop,onChange:e=>I("backdrop",e),options:[{value:"dim",label:"Dim"},{value:"light",label:"Light"},{value:"frosted",label:"Frosted blur"}]}),(0,a.jsx)(P,{label:"Clicking outside",value:e.dismiss,onChange:e=>I("dismiss",e),options:[{value:"none",label:"Does nothing"},{value:"closedby",label:"closedby attribute"},{value:"manual",label:"Handler"}]}),(0,a.jsxs)("div",{className:"grid gap-2 sm:grid-cols-2",children:[(0,a.jsx)(X,{on:e.showCancel,label:"Second button",hint:"A cancel next to the confirm.",onClick:()=>I("showCancel",!e.showCancel)}),(0,a.jsx)(X,{on:e.useForm,label:"Wrap it in a dialog form",hint:"Submitting closes without navigating.",onClick:()=>I("useForm",!e.useForm)}),(0,a.jsx)(X,{on:e.requiredField,label:"Include a required field",hint:"The case that blocks Cancel unless it says formnovalidate.",onClick:()=>I("requiredField",!e.requiredField)}),(0,a.jsx)(X,{on:e.animate,label:"Animate it",hint:"Writes the top layer transition that actually runs.",onClick:()=>I("animate",!e.animate)}),(0,a.jsx)(X,{on:e.scrollLock,label:"Lock the page behind",hint:"The browser will not do this for you.",onClick:()=>I("scrollLock",!e.scrollLock)}),(0,a.jsx)(X,{on:e.returnValue,label:"Report which button won",hint:"Reads returnValue in the close handler.",onClick:()=>I("returnValue",!e.returnValue)}),(0,a.jsx)(X,{on:e.restoreFocus,label:"Put focus back afterwards",hint:"Covers the case the browser cannot.",onClick:()=>I("restoreFocus",!e.restoreFocus)}),(0,a.jsx)(X,{on:e.guardEscape,label:"Block Escape",hint:"Only sane while a visible button can still close it.",onClick:()=>I("guardEscape",!e.guardEscape)})]}),e.animate&&(0,a.jsxs)("label",{className:"block space-y-1",children:[(0,a.jsxs)("span",{className:"text-xs font-medium text-muted-foreground",children:["Animation length: ",S(e.duration),"ms"]}),(0,a.jsx)("input",{type:"range",min:60,max:600,step:10,value:S(e.duration),onChange:e=>I("duration",S(Number(e.target.value))),className:"w-full","aria-label":"Animation length in milliseconds"})]}),e.guardEscape&&(0,a.jsx)("p",{className:"rounded-md border border-amber-600/40 bg-amber-50 p-2 text-xs text-amber-800 dark:bg-amber-950/40 dark:text-amber-300",children:"Blocking Escape means the visible button is the only way out. Keep one, and keep it reachable, or you have built a dialog nobody on a keyboard can leave."})]}),(0,a.jsxs)("div",{className:"space-y-3",children:[(0,a.jsx)(Z,{cfg:e}),(0,a.jsxs)("div",{className:"space-y-2 rounded-lg border p-3",children:[(0,a.jsxs)("div",{className:"text-sm font-medium",children:[et.counts.pass," of ",m.length," traps handled by this configuration"]}),(0,a.jsx)("p",{className:"text-xs text-muted-foreground",children:"This is the auditor on the right hand tab reading the code below, not a promise written into the page. Turn something off and watch a row go red."}),(0,a.jsx)("div",{className:"grid gap-1 sm:grid-cols-2",children:et.findings.map(e=>(0,a.jsxs)("div",{className:"flex items-start gap-2 text-xs",children:[(0,a.jsx)("span",{className:(0,u.cn)("mt-0.5 shrink-0 rounded border px-1 text-[10px] font-semibold",_[e.verdict]),children:f[e.verdict]}),(0,a.jsx)("span",{className:"min-w-0",children:g[e.id].title})]},e.id))})]})]})]}),(0,a.jsxs)("div",{className:"space-y-3 rounded-lg border p-3",children:[(0,a.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,a.jsx)(P,{label:"Output",value:e.framework,onChange:e=>I("framework",e),options:[{value:"html",label:"HTML, CSS and JS"},{value:"react",label:"React"},{value:"vue",label:"Vue"}]}),(0,a.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,a.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>ea("react"===e.framework?Q+"\n\n/* CSS */\n"+G:"vue"===e.framework?ee+"\n\n<style>\n"+G+"\n</style>":[K,"\n<style>",G,"</style>\n\n<script>",U,"<\/script>"].join("\n"),"all"),children:"all"===y?"Copied":"Copy everything"}),(0,a.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>{let t=new Blob([function(e){let t=(0,N.X)(e.title||"Dialog");return['<!doctype html>\n<html lang="en">\n<head>\n<meta charset="utf-8">\n<meta name="viewport" content="width=device-width, initial-scale=1">',`<title>${t}</title>`,"<style>\nbody { font-family: system-ui, sans-serif; margin: 0; padding: 40px 20px; min-height: 200vh; }\n",D(e),"</style>\n</head>\n<body>\n<p>
97Scroll down a little first, then open the dialog and try to scroll the page behind it.</p>\n",R(e),"\n<script>",V(e),"<\/script>\n</body>\n</html>"].join("\n")}(e)],{type:"text/html"});(0,W.WN)(t,$(e.id)+".html")},children:"Download a working page"})]})]}),"html"===e.framework&&(0,a.jsxs)("div",{className:"grid gap-3 lg:grid-cols-3",children:[(0,a.jsx)(H,{text:K,label:"HTML",copied:"html"===y,onCopy:()=>ea(K,"html")}),(0,a.jsx)(H,{text:G,label:"CSS",copied:"css"===y,onCopy:()=>ea(G,"css")}),(0,a.jsx)(H,{text:U,label:"JavaScript",copied:"js"===y,onCopy:()=>ea(U,"js")})]}),"react"===e.framework&&(0,a.jsxs)("div",{className:"grid gap-3 lg:grid-cols-2",children:[(0,a.jsx)(H,{text:Q,label:"Component",copied:"react"===y,onCopy:()=>ea(Q,"react")}),(0,a.jsx)(H,{text:G,label:"CSS",copied:"css"===y,onCopy:()=>ea(G,"css")})]}),"vue"===e.framework&&(0,a.jsxs)("div",{className:"grid gap-3 lg:grid-cols-2",children:[(0,a.jsx)(H,{text:ee,label:"Single file component",copied:"vue"===y,onCopy:()=>ea(ee,"vue")}),(0,a.jsx)(H,{text:G,label:"CSS",copied:"css"===y,onCopy:()=>ea(G,"css")})]})]}),"partial"===eh&&(0,a.jsxs)("p",{className:"text-sm text-muted-foreground",children:["Your dialog text pushes this over ",2e3," characters, so the share link below carries every setting but leaves the body copy out. Whoever opens it gets your layout, dismiss rules and framework, with an empty body to fill in."]}),"none"===eh&&(0,a.jsxs)("p",{className:"text-sm text-muted-foreground",children:["Your settings come to over ",2e3," characters, so the share link below cannot carry them and opens without your dialog at all. Copy the code above and send that."]}),(0,a.jsx)(d.ToolActions,{share:ec,title:"HTML Dialog Generator",embedCarriesPersonal:!1})]}):(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{onDragOver:e=>{e.preventDefault(),j(!0)},onDragLeave:()=>j(!1),onDrop:e=>{e.preventDefault(),j(!1),es(e.dataTransfer.files)},className:(0,u.cn)("space-y-2 rounded-lg border-2 border-dashed p-3",k&&"border-sky-600 bg-sky-50 dark:bg-sky-950/30"),children:[(0,a.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,a.jsx)("span",{className:"text-sm font-medium",children:"Paste the dialog you already have"}),(0,a.jsxs)("div",{className:"flex gap-2",children:[(0,a.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>A.current?.click(),children:"Choose files"}),(0,a.jsx)(i.$,{type:"button",variant:"outline",size:"sm",onClick:()=>{x(""),F("")},children:"Clear"})]})]}),(0,a.jsxs)("p",{className:"text-xs text-muted-foreground",children:["Markup, CSS and JavaScript together give the fullest read. Paste it, drop the files into this box, or click to choose them. Up to ",6," files at once, and none of it leaves your browser."]}),(0,a.jsx)("input",{ref:A,type:"file",multiple:!0,accept:".html,.htm,.css,.js,.jsx,.ts,.tsx,.vue,.svelte,.txt",className:"hidden",onChange:e=>{es(e.target.files),e.target.value=""}}),(0,a.jsx)(r.T,{value:v,onChange:e=>{x(e.target.value),F("")},rows:10,placeholder:'<dialog id="confirm">\n ...\n</dialog>\n\ndialog::backdrop { ... }\n\ndialog.showModal();',className:"font-mono text-xs","aria-label":"Your dialog markup, CSS and JavaScript"}),M&&(0,a.jsx)("p",{role:"status","aria-live":"polite",className:"text-xs text-destructive",children:M}),L&&(0,a.jsx)("p",{className:"text-xs text-muted-foreground",children:L})]}),eo.refused&&(0,a.jsx)("p",{className:"rounded-md border border-red-600/40 bg-red-50 p-3 text-sm text-red-800 dark:bg-red-950/40 dark:text-red-300",children:eo.refusedReason}),eo.ran&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsxs)("div",{className:"rounded-md border p-3",children:[(0,a.jsx)("div",{className:"text-sm font-medium",children:`${m.length} checks. ${eo.counts.pass} handled, ${eo.counts.fail} not handled, ${eo.counts.unknown} cannot tell, ${eo.counts.na} do not apply.`}),eo.notes.length>0&&(0,a.jsx)("ul",{className:"mt-2 space-y-1 text-xs text-muted-foreground",children:eo.notes.map(e=>(0,a.jsx)("li",{children:e},e))}),(0,a.jsx)("p",{className:"mt-2 text-xs text-muted-foreground",children:"This reads your code as text. It never runs it, so anything decided while your page is running comes back as cannot tell rather than a verdict, and a clean result means the traps on this list were handled, not that the dialog is accessible."})]}),(0,a.jsx)("div",{className:"space-y-2",children:eo.findings.map(e=>((e,t,o,s)=>{let n=g[e];return(0,a.jsxs)("div",{className:"space-y-1 rounded-md border p-3",children:[(0,a.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-2",children:[(0,a.jsx)("span",{className:"text-sm font-medium",children:n.title}),(0,a.jsx)("span",{className:(0,u.cn)("shrink-0 rounded border px-1.5 py-0.5 text-[11px] font-semibold",_[t]),children:f[t]})]}),(0,a.jsx)("p",{className:"text-xs text-muted-foreground",children:o}),s&&(0,a.jsx)("pre",{className:"overflow-x-auto rounded border bg-muted/40 p-2 font-mono text-[11px] leading-relaxed",children:s})]},e)})(e.id,e.verdict,e.detail,e.code))})]}),!eo.ran&&!eo.refused&&(0,a.jsxs)("div",{className:"space-y-2 rounded-md border p-3",children:[(0,a.jsx)("p",{className:"text-sm text-muted-foreground",children:"Nothing pasted yet. Every check below runs against your co
97de, and each one is something the native element does not do for you."}),(0,a.jsx)("ul",{className:"grid gap-1 text-xs text-muted-foreground sm:grid-cols-2",children:m.map(e=>(0,a.jsx)("li",{children:e.title},e.id))})]})]})]})}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.