-
-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
337 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,337 @@ | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>Prompts.js</title> | ||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||
<style> | ||
body { | ||
font-family: Helvetica, Arial, sans-serif; | ||
font-size: 16px; | ||
margin: 2rem; | ||
padding: 2rem; | ||
max-width: 800px; | ||
margin: 2rem auto; | ||
line-height: 1.6; | ||
} | ||
input, textarea { | ||
font-size: 16px; | ||
} | ||
.button-container { | ||
display: flex; | ||
gap: 10px; | ||
margin-bottom: 20px; | ||
} | ||
.button-container button { | ||
padding: 10px 15px; | ||
background-color: #007bff; | ||
color: white; | ||
border: none; | ||
border-radius: 4px; | ||
cursor: pointer; | ||
} | ||
.result { | ||
margin-top: 10px; | ||
padding: 10px; | ||
background-color: #f4f4f4; | ||
border-radius: 4px; | ||
} | ||
</style> | ||
</head> | ||
<body> | ||
<h1>Prompts.js</h1> | ||
<p>A lightweight JavaScript library for creating modal dialogs with alert, confirm, and prompt functionalities.</p> | ||
<pre style="font-size: 0.8em; white-space: pre-wrap"> | ||
await Prompts.alert("This is an alert message!"); | ||
const resultBoolean = await Prompts.confirm("Do you want to proceed?"); | ||
const name = await Prompts.prompt("What is your name?"); | ||
</pre> | ||
|
||
<div class="button-container"> | ||
<button id="alertBtn">Show Alert</button> | ||
<button id="confirmBtn">Show Confirm</button> | ||
<button id="promptBtn">Show Prompt</button> | ||
</div> | ||
|
||
<div id="resultArea" class="result"></div> | ||
|
||
<script type="module"> | ||
const Prompts = (function() { | ||
let modalOpen = false; | ||
let previouslyFocusedElement = null; | ||
|
||
// Common styles | ||
const overlayStyle = { | ||
position: 'fixed', | ||
top: '0', left: '0', right: '0', bottom: '0', | ||
backgroundColor: 'rgba(0,0,0,0.5)', | ||
display: 'flex', | ||
alignItems: 'center', | ||
justifyContent: 'center', | ||
zIndex: '999999', | ||
}; | ||
|
||
const modalStyle = { | ||
backgroundColor: '#fff', | ||
borderRadius: '6px', | ||
padding: '20px', | ||
minWidth: '300px', | ||
maxWidth: '80%', | ||
boxSizing: 'border-box', | ||
fontFamily: 'sans-serif', | ||
boxShadow: '0 2px 10px rgba(0,0,0,0.2)', | ||
}; | ||
|
||
const messageStyle = { | ||
marginBottom: '20px', | ||
fontSize: '16px', | ||
color: '#333', | ||
wordWrap: 'break-word', | ||
whiteSpace: 'pre-wrap', | ||
}; | ||
|
||
const buttonRowStyle = { | ||
textAlign: 'right', | ||
marginTop: '20px', | ||
}; | ||
|
||
const buttonStyle = { | ||
backgroundColor: '#007bff', | ||
color: '#fff', | ||
border: 'none', | ||
borderRadius: '4px', | ||
padding: '8px 12px', | ||
fontSize: '14px', | ||
cursor: 'pointer', | ||
marginLeft: '8px', | ||
}; | ||
|
||
const inputStyle = { | ||
width: '100%', | ||
boxSizing: 'border-box', | ||
padding: '8px', | ||
fontSize: '14px', | ||
marginBottom: '20px', | ||
borderRadius: '4px', | ||
border: '1px solid #ccc', | ||
}; | ||
|
||
function applyStyles(element, styles) { | ||
for (const prop in styles) { | ||
element.style[prop] = styles[prop]; | ||
} | ||
} | ||
|
||
function createModal(message) { | ||
const overlay = document.createElement('div'); | ||
applyStyles(overlay, overlayStyle); | ||
|
||
const modal = document.createElement('div'); | ||
applyStyles(modal, modalStyle); | ||
|
||
const form = document.createElement('form'); | ||
form.setAttribute('novalidate', 'true'); | ||
modal.appendChild(form); | ||
|
||
const msg = document.createElement('div'); | ||
applyStyles(msg, messageStyle); | ||
msg.textContent = message; | ||
|
||
form.appendChild(msg); | ||
overlay.appendChild(modal); | ||
|
||
return { overlay, modal, form }; | ||
} | ||
|
||
function createButton(label, onClick, type = 'button') { | ||
const btn = document.createElement('button'); | ||
applyStyles(btn, buttonStyle); | ||
btn.type = type; | ||
btn.textContent = label; | ||
if (onClick) { | ||
btn.addEventListener('click', onClick); | ||
} | ||
return btn; | ||
} | ||
|
||
function showModal(overlay, onClose) { | ||
if (modalOpen) return; // Prevent multiple modals | ||
modalOpen = true; | ||
|
||
previouslyFocusedElement = document.activeElement; | ||
document.body.appendChild(overlay); | ||
|
||
// Focus trap and ESC handler | ||
const focusableElements = overlay.querySelectorAll('button, input, [href], select, textarea, [tabindex]:not([tabindex="-1"])'); | ||
const firstFocusable = focusableElements[0]; | ||
const lastFocusable = focusableElements[focusableElements.length - 1]; | ||
|
||
function keyHandler(e) { | ||
if (e.key === 'Escape') { | ||
e.preventDefault(); | ||
cleanup(); | ||
onClose('escape'); | ||
} else if (e.key === 'Tab') { | ||
// Focus trapping | ||
if (focusableElements.length === 1) { | ||
e.preventDefault(); // Only one focusable element, cycle back to it. | ||
} else { | ||
if (e.shiftKey && document.activeElement === firstFocusable) { | ||
// If Shift+Tab on first element, go to last | ||
e.preventDefault(); | ||
lastFocusable.focus(); | ||
} else if (!e.shiftKey && document.activeElement === lastFocusable) { | ||
// If Tab on last element, go to first | ||
e.preventDefault(); | ||
firstFocusable.focus(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
document.addEventListener('keydown', keyHandler); | ||
|
||
function cleanup() { | ||
document.removeEventListener('keydown', keyHandler); | ||
if (overlay.parentNode) { | ||
document.body.removeChild(overlay); | ||
} | ||
modalOpen = false; | ||
if (previouslyFocusedElement && previouslyFocusedElement.focus) { | ||
previouslyFocusedElement.focus(); | ||
} | ||
} | ||
|
||
return { cleanup, firstFocusable, focusableElements }; | ||
} | ||
|
||
async function alert(message) { | ||
return new Promise((resolve) => { | ||
const { overlay, form } = createModal(message); | ||
|
||
const buttonRow = document.createElement('div'); | ||
applyStyles(buttonRow, buttonRowStyle); | ||
|
||
// OK button submits the form | ||
const okBtn = createButton('OK', null, 'submit'); | ||
buttonRow.appendChild(okBtn); | ||
form.appendChild(buttonRow); | ||
|
||
form.onsubmit = (e) => { | ||
e.preventDefault(); | ||
cleanup(); | ||
resolve(); | ||
}; | ||
|
||
const { cleanup, firstFocusable } = showModal(overlay, (reason) => { | ||
// Escape pressed | ||
resolve(); | ||
}); | ||
|
||
// Move focus to OK button | ||
firstFocusable.focus(); | ||
}); | ||
} | ||
|
||
async function confirm(message) { | ||
return new Promise((resolve) => { | ||
const { overlay, form } = createModal(message); | ||
|
||
const buttonRow = document.createElement('div'); | ||
applyStyles(buttonRow, buttonRowStyle); | ||
|
||
const cancelBtn = createButton('Cancel', (e) => { | ||
e.preventDefault(); | ||
cleanup(); | ||
resolve(false); | ||
}); | ||
cancelBtn.style.backgroundColor = '#6c757d'; | ||
|
||
const okBtn = createButton('OK', null, 'submit'); | ||
|
||
buttonRow.appendChild(cancelBtn); | ||
buttonRow.appendChild(okBtn); | ||
form.appendChild(buttonRow); | ||
|
||
form.onsubmit = (e) => { | ||
e.preventDefault(); | ||
// Enter key (submit) is treated as clicking OK | ||
cleanup(); | ||
resolve(true); | ||
}; | ||
|
||
const { cleanup, focusableElements } = showModal(overlay, (reason) => { | ||
// If escaped, treat as cancel | ||
resolve(false); | ||
}); | ||
|
||
// Move focus to OK button (second button) | ||
focusableElements[1].focus(); | ||
}); | ||
} | ||
|
||
async function prompt(message) { | ||
return new Promise((resolve) => { | ||
const { overlay, form } = createModal(message); | ||
|
||
const input = document.createElement('input'); | ||
applyStyles(input, inputStyle); | ||
input.type = 'text'; | ||
input.name = 'promptInput'; | ||
form.appendChild(input); | ||
|
||
const buttonRow = document.createElement('div'); | ||
applyStyles(buttonRow, buttonRowStyle); | ||
|
||
const cancelBtn = createButton('Cancel', (e) => { | ||
e.preventDefault(); | ||
cleanup(); | ||
resolve(null); | ||
}); | ||
cancelBtn.style.backgroundColor = '#6c757d'; | ||
|
||
const okBtn = createButton('OK', null, 'submit'); | ||
|
||
buttonRow.appendChild(cancelBtn); | ||
buttonRow.appendChild(okBtn); | ||
form.appendChild(buttonRow); | ||
|
||
form.onsubmit = (e) => { | ||
e.preventDefault(); | ||
const val = input.value; | ||
cleanup(); | ||
resolve(val); | ||
}; | ||
|
||
const { cleanup, firstFocusable } = showModal(overlay, (reason) => { | ||
// If escaped, treat as cancel | ||
resolve(null); | ||
}); | ||
|
||
// Focus on the input for convenience | ||
input.focus(); | ||
}); | ||
} | ||
|
||
return { alert, confirm, prompt }; | ||
})(); | ||
|
||
|
||
const resultArea = document.getElementById('resultArea'); | ||
|
||
document.getElementById('alertBtn').addEventListener('click', async () => { | ||
await Prompts.alert("This is an alert message!"); | ||
resultArea.textContent = "Alert was shown and dismissed."; | ||
}); | ||
|
||
document.getElementById('confirmBtn').addEventListener('click', async () => { | ||
const result = await Prompts.confirm("Do you want to proceed?"); | ||
resultArea.textContent = `Confirm result: ${result}`; | ||
}); | ||
|
||
document.getElementById('promptBtn').addEventListener('click', async () => { | ||
const name = await Prompts.prompt("What is your name?"); | ||
resultArea.textContent = name ? `Hello, ${name}!` : "Prompt was cancelled."; | ||
}); | ||
</script> | ||
</body> | ||
</html> |