Initial commit
This commit is contained in:
commit
907199a05e
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/dist
|
||||
/node_modules
|
||||
377
demo.html
Normal file
377
demo.html
Normal file
@ -0,0 +1,377 @@
|
||||
<!doctype html>
|
||||
<html lang="en-GB">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="content-type" content="text/html" charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description" content="" />
|
||||
<meta name="keyword" content="" />
|
||||
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
|
||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@5/themes.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<script src="dist/tw4dialog.dist.js"></script>
|
||||
<title></title>
|
||||
|
||||
<style>
|
||||
|
||||
p {
|
||||
margin: 0 0 1em 0;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body class="py-5">
|
||||
<div class="container mx-auto mt-4">
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
<p><b>Example. Simple Text Modal</b></p>
|
||||
<p>Launch a modal with a text body.</p>
|
||||
|
||||
<p><button id="buttonL1" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
|
||||
<script>
|
||||
document.querySelector("#buttonL1").addEventListener("click", async function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
TW4Dialog.Show({
|
||||
Title: "Modal Example",
|
||||
Content: "Hello Momo!",
|
||||
Size: "md"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
TW4Dialog.Show({
|
||||
Title: "Modal Example",
|
||||
Content: "Hello Momo!",
|
||||
Size: "md"
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
<p><b>Example. Basic Modal</b></p>
|
||||
<p>Launch a modal and fetch a html body from a URL.</p>
|
||||
|
||||
<p><button id="buttonL11" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
|
||||
<script>
|
||||
document.querySelector("#buttonL11").addEventListener("click", async function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
TW4Dialog.Show({
|
||||
Title: "Modal Fetch Example",
|
||||
Content: "Loading...",
|
||||
Url: "https://cdn.hiimray.co.uk/sample/hellomomo.txt",
|
||||
Size: "lg"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
TW4Dialog.Show({
|
||||
Title: "Modal Fetch Example",
|
||||
Content: "Loading...",
|
||||
Url: "https://cdn.hiimray.co.uk/sample/hellomomo.txt",
|
||||
Size: "lg"
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
|
||||
<p><b>Example. Update Modal</b></p>
|
||||
<p>Make changes (title, body, size or footer) to a modal.</p>
|
||||
|
||||
<p><button id="buttonR1" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
<script>
|
||||
document.querySelector("#buttonR1").addEventListener("click", function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
TW4Dialog.Show({
|
||||
Id: "modalR1",
|
||||
Title: "Modal Title",
|
||||
Content: "Hello Momo!",
|
||||
Url: null,
|
||||
Size: "md"
|
||||
});
|
||||
|
||||
TW4Dialog.Update({
|
||||
Id: "modalR1",
|
||||
Title: "Modal Changed Title",
|
||||
Content: "Hello momo again!",
|
||||
Url: null,
|
||||
Size: "sm",
|
||||
ShowFooter: false
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
TW4Dialog.Show({
|
||||
Id: "modalR1",
|
||||
Title: "Modal Title",
|
||||
Content: "Hello Momo!",
|
||||
Url: null,
|
||||
Size: "md"
|
||||
});
|
||||
|
||||
TW4Dialog.Update({
|
||||
Id: "modalR1",
|
||||
Title: "Modal Changed Title",
|
||||
Content: "Hello momo again!",
|
||||
Url: null,
|
||||
Size: "sm",
|
||||
ShowFooter: false
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5 border-b border-solid border-base-400 pb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
|
||||
<p><b>Example. Multiple Modals (Stack)</b></p>
|
||||
<p>Launch multiple modals, stacked on top of each other.</p>
|
||||
|
||||
<p><button id="buttonL3" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
<script>
|
||||
document.querySelector("#buttonL3").addEventListener("click", function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
TW4Dialog.Show({
|
||||
Id: "modalL3a",
|
||||
Title: "Modal A Title",
|
||||
Content: "First!",
|
||||
Url: null,
|
||||
Size: "md"
|
||||
});
|
||||
|
||||
TW4Dialog.Show({
|
||||
Id: "modalL3b",
|
||||
Title: "Modal B Title",
|
||||
Content: "Second!",
|
||||
Url: null,
|
||||
Size: "md"
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
TW4Dialog.Show({
|
||||
Id: "modalL3a",
|
||||
Title: "Modal A Title",
|
||||
Content: "First!",
|
||||
Url: null,
|
||||
Size: "md"
|
||||
});
|
||||
|
||||
TW4Dialog.Show({
|
||||
Id: "modalL3b",
|
||||
Title: "Modal B Title",
|
||||
Content: "Second!",
|
||||
Url: null,
|
||||
Size: "md"
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5 border-b border-solid border-base-400 pb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
|
||||
<p><b>Example. MessageBox Example (Button Prompts)</b></p>
|
||||
<p>Launch a modal with a prompt of several buttons. Modal waits for a response from one of the buttons or on closing.</p>
|
||||
|
||||
<p><button id="buttonL2" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
<script>
|
||||
document.querySelector("#buttonL2").addEventListener("click", async function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const result = await TW4MessageBox.Show({
|
||||
Title: "MessageBox Example 1",
|
||||
Content: "Click a button!",
|
||||
Buttons: [
|
||||
{ Label: "Yes", Value: "Yes" },
|
||||
{ Label: "No", Value: "No" },
|
||||
{ Label: "Cancel", Value: "Cancel" }
|
||||
]
|
||||
});
|
||||
|
||||
TW4Dialog.Show({
|
||||
Title: "MessageBox Result",
|
||||
Content: result
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
const result = await TW4MessageBox.Show({
|
||||
Title: "MessageBox Example 1",
|
||||
Content: "Click a button!",
|
||||
Buttons: [
|
||||
{ Label: "Yes", Value: "Yes" },
|
||||
{ Label: "No", Value: "No" },
|
||||
{ Label: "Cancel", Value: "Cancel" }
|
||||
]
|
||||
});
|
||||
|
||||
TW4Dialog.Show({
|
||||
Title: "MessageBox Result",
|
||||
Content: result
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5 border-b border-solid border-base-400 pb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
|
||||
<p><b>Example. PromptBox (Text Prompt)</b></p>
|
||||
<p>Launch a modal with a text prompt. Modal waits for a response from the textbox.</p>
|
||||
|
||||
<p><button id="buttonL2a" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
<script>
|
||||
document.querySelector("#buttonL2a").addEventListener("click", async function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
const result = await TW4PromptBox.Show({
|
||||
Title: "PromptBox Example 1",
|
||||
Content: "Enter text!",
|
||||
Placeholder: "Text me",
|
||||
Value: ""
|
||||
});
|
||||
|
||||
TW4Dialog.Show({
|
||||
Title: "PromptBox Result",
|
||||
Content: JSON.stringify(result)
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
const result = await TW4PromptBox.Show({
|
||||
Title: "PromptBox Example 1",
|
||||
Content: "Enter text!",
|
||||
Placeholder: "Text me",
|
||||
Value: ""
|
||||
});
|
||||
|
||||
TW4Dialog.Show({
|
||||
Title: "PromptBox Result",
|
||||
Content: JSON.stringify(result)
|
||||
});
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
|
||||
<p><b>Example. Close Modal</b></p>
|
||||
<p>Close a modal using its identifier</p>
|
||||
<p><button id="buttonR2" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
<script>
|
||||
document.querySelector("#buttonR2").addEventListener("click", function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
TW4Dialog.Close("modalR1");
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
TW4Dialog.Close("modalR1");
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 divide-x divide-solid divide-base-300 mb-5">
|
||||
<div class="col-span-1 pr-2">
|
||||
|
||||
<p><b>Example. Clear Modal</b></p>
|
||||
<p>Close all modals</p>
|
||||
<p><button id="buttonR3" type="button" class="btn btn-primary">Launch Modal</button></p>
|
||||
<script>
|
||||
document.querySelector("#buttonR3").addEventListener("click", function(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
TW4Dialog.Clear();
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
|
||||
<div class="mockup-code w-full px-5 text-sm">
|
||||
<pre><code>
|
||||
TW4Dialog.Clear();
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
3498
package-lock.json
generated
Normal file
3498
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
16
package.json
Normal file
16
package.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "twdialog4",
|
||||
"version": "0.1.0.201",
|
||||
"devDependencies": {
|
||||
"css-loader": "^7.1.2",
|
||||
"sass": "^1.77.8",
|
||||
"sass-loader": "^16.0.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"webpack": "^5.93.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"watch": "webpack --watch"
|
||||
}
|
||||
}
|
||||
8
src/index.js
Normal file
8
src/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import TWDialog4 from "./twdialog4.js"
|
||||
import TWMessageBox4 from "./twmessagebox4.js"
|
||||
import TWPromptBox4 from "./twpromptbox4.js"
|
||||
|
||||
|
||||
window.TW4Dialog = new TWDialog4();
|
||||
window.TW4MessageBox = new TWMessageBox4();
|
||||
window.TW4PromptBox = new TWPromptBox4();
|
||||
481
src/twdialog4.js
Normal file
481
src/twdialog4.js
Normal file
@ -0,0 +1,481 @@
|
||||
class TWDialog4 {
|
||||
#elName = {};
|
||||
#modalSizes = [
|
||||
"md",
|
||||
"xl",
|
||||
"4xl",
|
||||
"screen",
|
||||
];
|
||||
|
||||
|
||||
constructor() {
|
||||
const a = this;
|
||||
const prefix = "twdia4";
|
||||
|
||||
a.#elName = {
|
||||
prefix: prefix,
|
||||
container: prefix + "-container",
|
||||
backdrop: prefix + "-backdrop",
|
||||
modal: prefix + "-modal",
|
||||
header: prefix + "-header",
|
||||
title: prefix + "-h1",
|
||||
body: prefix + "-body",
|
||||
footer: prefix + "-footer"
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
get DefaultOptions() {
|
||||
return {
|
||||
Id: null,
|
||||
Title: "",
|
||||
Content: "",
|
||||
Url: null,
|
||||
Size: "md",
|
||||
ShowFooter: true,
|
||||
// EasyClose: true,
|
||||
EnableMovement: true,
|
||||
EnableResize: true,
|
||||
CloseButton: {
|
||||
colour: "bg-white hover:bg-gray-50 text-gray-700"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
get #documentBody() {
|
||||
return document.getElementsByTagName("body")[0];
|
||||
}
|
||||
|
||||
|
||||
async Show(options) {
|
||||
const a = this;
|
||||
const newOptions = a.#loadOptions(options);
|
||||
|
||||
a.#addBackdrop();
|
||||
|
||||
const modal = a.Find(newOptions.Id);
|
||||
if (modal == null) {
|
||||
await a.#addModal(newOptions);
|
||||
} else {
|
||||
await a.Update(newOptions);
|
||||
}
|
||||
}
|
||||
|
||||
Clear() {
|
||||
const a = this;
|
||||
const container = a.#findNode("." + a.#elName.container);
|
||||
|
||||
if (container != null) {
|
||||
a.#removeNode(container);
|
||||
}
|
||||
}
|
||||
|
||||
Close(id) {
|
||||
const a = this;
|
||||
const modalId = a.#elName.prefix + "-" + id;
|
||||
|
||||
const container = a.#findNode("." + a.#elName.container);
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = a.#findNode("#" + modalId);
|
||||
if (modal != null) {
|
||||
a.#removeNode(modal);
|
||||
}
|
||||
|
||||
const foundModals = a.#documentBody.querySelectorAll("." + a.#elName.modal);
|
||||
if (foundModals.length <= 0) {
|
||||
a.#removeNode(container);
|
||||
|
||||
a.#documentBody.classList.remove('overflow-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
Exists(id) {
|
||||
return (this.Find(id) !== null);
|
||||
}
|
||||
|
||||
Find(id) {
|
||||
const a = this;
|
||||
const modalId = a.#elName.prefix + "-" + id;
|
||||
|
||||
const modal = a.#findNode("#" + modalId);
|
||||
if (modal == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
Header: modal.querySelector("." + a.#elName.header),
|
||||
Title: modal.querySelector("." + a.#elName.title),
|
||||
Body: modal.querySelector("." + a.#elName.body),
|
||||
Footer: modal.querySelector("." + a.#elName.footer),
|
||||
Modal: modal
|
||||
};
|
||||
}
|
||||
|
||||
SetSize(id, size) {
|
||||
const a = this;
|
||||
|
||||
const modal = a.Find(id);
|
||||
if (modal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childNode = modal.Modal.firstElementChild;
|
||||
|
||||
for (let i=0; i<a.#modalSizes.length; i++) {
|
||||
if (childNode.classList.contains("w-" + a.#modalSizes[i])) {
|
||||
childNode.classList.remove("w-" + a.#modalSizes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
childNode.classList.add("w-" + size);
|
||||
}
|
||||
|
||||
async Update(options) {
|
||||
const a = this;
|
||||
const newOptions = a.#loadOptions(options);
|
||||
|
||||
const modal = a.Find(newOptions.Id);
|
||||
if (modal == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#isNullOrWhitespace(newOptions.Title)) {
|
||||
modal.Title.innerHTML = newOptions.Title;
|
||||
}
|
||||
|
||||
if (!this.#isNullOrWhitespace(newOptions.Content)) {
|
||||
modal.Body.innerHTML = newOptions.Content;
|
||||
}
|
||||
|
||||
if (newOptions.ShowFooter) {
|
||||
modal.Footer.classList.remove('hidden');
|
||||
} else {
|
||||
modal.Footer.classList.add('hidden');
|
||||
}
|
||||
|
||||
if (!this.#isNullOrWhitespace(newOptions.Size)) {
|
||||
a.SetSize(newOptions.Id, newOptions.Size);
|
||||
}
|
||||
|
||||
// Load content from url
|
||||
if (!this.#isNullOrWhitespace(newOptions.Url)) {
|
||||
const html = await a.#fetchHtml(newOptions.Url);
|
||||
|
||||
modal.Body.innerHTML = html ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#addBackdrop() {
|
||||
const a = this;
|
||||
|
||||
// Don't allow multiple backdrops
|
||||
if (a.#findNode("." + a.#elName.container) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const html = `
|
||||
<div class="` + a.#elName.container + ` fixed inset-0 z-50 hidden">
|
||||
<!-- Backdrop -->
|
||||
<div class="` + a.#elName.backdrop + ` absolute inset-0 bg-black/60"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
a.#appendHtml(a.#documentBody, html);
|
||||
|
||||
// const backdrop = a.#findNode("." + a.#elName.backdrop);
|
||||
// if (backdrop == null) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Attach close event
|
||||
// backdrop.addEventListener("click", function(e){
|
||||
// e.stopPropagation();
|
||||
// e.preventDefault();
|
||||
|
||||
// a.Clear();
|
||||
// });
|
||||
}
|
||||
|
||||
async #addModal(options) {
|
||||
const a = this;
|
||||
const modalId = a.#elName.prefix + "-" + options.Id;
|
||||
|
||||
const container = a.#findNode("." + a.#elName.container);
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't allow duplicates
|
||||
let modal = a.Find(options.Id);
|
||||
if (modal != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let html = `
|
||||
<div id="` + modalId + `" class="` + a.#elName.modal + ` absolute z-10 flex items-start justify-center h-full w-screen p-8">
|
||||
<div class="bg-white max-h-[90vh] max-w-[90vw] w-` + options.Size + ` rounded-lg shadow-xl overflow-y-auto">
|
||||
` + a.#generateModalHeader(options) + `
|
||||
<div class="` + a.#elName.body + ` p-4">
|
||||
` + options.Content + `
|
||||
</div>
|
||||
` + a.#generateModalFooter(options) + `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
a.#appendHtml(container, html);
|
||||
|
||||
// Fix viewport
|
||||
container.classList.remove('hidden');
|
||||
a.#documentBody.classList.add('overflow-hidden');
|
||||
|
||||
modal = a.Find(options.Id);
|
||||
if (modal === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle close
|
||||
modal.Modal.querySelectorAll("[aria-label='Close']").forEach(e => {
|
||||
e.addEventListener("click", function(e2){
|
||||
e2.stopPropagation();
|
||||
e2.preventDefault();
|
||||
|
||||
a.Close(options.Id);
|
||||
});
|
||||
});
|
||||
|
||||
// Handle other options
|
||||
if (options.EnableResize) {
|
||||
a.#attachResize(modal.Modal, modal.Title);
|
||||
}
|
||||
|
||||
if (options.EnableMovement) {
|
||||
a.#attachMovable(modal.Modal, modal.Title);
|
||||
}
|
||||
|
||||
// Load content from url
|
||||
if (!this.#isNullOrWhitespace(options.Url)) {
|
||||
const html = await a.#fetchHtml(options.Url);
|
||||
|
||||
modal.Body.innerHTML = html ?? "";
|
||||
}
|
||||
}
|
||||
|
||||
#generateModalHeader(options) {
|
||||
const a = this;
|
||||
|
||||
const html = `
|
||||
<div class="` + a.#elName.header + ` border-b border-solid border-base-300 py-3 px-4 flex items-center justify-between">
|
||||
<span class="` + a.#elName.title + ` text-xl font-semibold select-none">` + options.Title + `</span>
|
||||
<a class="text-gray-600 hover:text-gray-900 ml-4 cursor-pointer" aria-label="Close">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/>
|
||||
<line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
#generateModalFooter(options) {
|
||||
const a = this;
|
||||
|
||||
const html = `
|
||||
<div class="` + a.#elName.footer + ` ` + (options.ShowFooter ? "" : "hidden") + ` border-t border-solid border-base-300 py-3 px-4 items-center text-right bg-base-100">
|
||||
<button class="px-4 py-2 text-sm font-medium ` + options.CloseButton.colour + ` border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer" aria-label="Close">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
#attachResize(panel, el) {
|
||||
const a = this;
|
||||
|
||||
el.addEventListener("dblclick", function(e) {
|
||||
const childNode = panel.firstElementChild;
|
||||
|
||||
let n = 0;
|
||||
for (let i=0; i<a.#modalSizes.length; i++) {
|
||||
n = i;
|
||||
|
||||
if (childNode.classList.contains("w-" + a.#modalSizes[i])) {
|
||||
childNode.classList.remove("w-" + a.#modalSizes[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
n++;
|
||||
|
||||
if (n >= a.#modalSizes.length) {
|
||||
n = 0;
|
||||
}
|
||||
|
||||
childNode.classList.add("w-" + a.#modalSizes[n]);
|
||||
});
|
||||
}
|
||||
|
||||
#attachMovable(panel, el) {
|
||||
const a = this;
|
||||
|
||||
let isMovable = false;
|
||||
let currentPos = { X: 0, Y: 0 };
|
||||
let movePos = { X: 0, Y: 0 };
|
||||
|
||||
el.addEventListener("mousedown", function(e) {
|
||||
if ((e.buttons == 1) && !isMovable) {
|
||||
movePos = { X: e.clientX, Y: e.clientY };
|
||||
currentPos = {
|
||||
X: a.#parsePixels(panel.style.left),
|
||||
Y: a.#parsePixels(panel.style.top)
|
||||
};
|
||||
isMovable = true;
|
||||
|
||||
el.style.cursor = "move";
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mouseup", function(e) {
|
||||
if (isMovable) {
|
||||
isMovable = false;
|
||||
el.style.cursor = "default";
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("mousemove", function(e) {
|
||||
if ((e.buttons == 1) && isMovable) {
|
||||
let x = (e.clientX - movePos.X) + currentPos.X;
|
||||
let y = (e.clientY - movePos.Y) + currentPos.Y;
|
||||
|
||||
if (x <= 0) x = 0;
|
||||
if (y <= 0) y = 0;
|
||||
|
||||
panel.style.left = x + 'px';
|
||||
panel.style.top = y + 'px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#loadOptions(options) {
|
||||
const a = this;
|
||||
const newOptions = Object.assign(a.DefaultOptions, options);
|
||||
|
||||
if (a.#isNullOrWhitespace(newOptions.Id)) {
|
||||
newOptions.Id = a.#randomN(30001, 99999);
|
||||
}
|
||||
|
||||
switch (newOptions.Size) {
|
||||
case "xs":
|
||||
case "sm":
|
||||
newOptions.Size = "md";
|
||||
break;
|
||||
case "md":
|
||||
newOptions.Size = "xl";
|
||||
break;
|
||||
case "lg":
|
||||
newOptions.Size = "4xl";
|
||||
break;
|
||||
case "xl":
|
||||
case "xxl":
|
||||
case "xxxl":
|
||||
newOptions.Size = "screen";
|
||||
break;
|
||||
default:
|
||||
newOptions.Size = "xl";
|
||||
break;
|
||||
}
|
||||
|
||||
return newOptions;
|
||||
}
|
||||
|
||||
|
||||
#appendHtml(el, html) {
|
||||
let node = document.createElement('template');
|
||||
node.innerHTML = html.trim();
|
||||
|
||||
node = node.content.firstChild;
|
||||
|
||||
el.appendChild(node);
|
||||
}
|
||||
|
||||
async #fetchHtml(url) {
|
||||
let html = "";
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
|
||||
html = await response.text();
|
||||
} catch (err) {
|
||||
html = err;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
#findNode(selector) {
|
||||
return this.#documentBody.querySelector(selector);
|
||||
}
|
||||
|
||||
#isNullOrWhitespace(e) {
|
||||
if (typeof (e) == "undefined") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (e == false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (e?.toString().trim()?.length <= 0);
|
||||
}
|
||||
|
||||
#parsePixels(value) {
|
||||
const a = this;
|
||||
|
||||
if (a.#isNullOrWhitespace(value)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const result = parseInt(value.replace("px", ""), 10);
|
||||
|
||||
return Number.isNaN(result) ? 0 : result;
|
||||
}
|
||||
|
||||
#randomN(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
#removeNode(node) {
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(node.parentNode) == "undefined") {
|
||||
return;
|
||||
}
|
||||
|
||||
node.parentNode.removeChild(node);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// window.TW4Dialog = new TWDialog4();
|
||||
export default TWDialog4;
|
||||
98
src/twmessagebox4.js
Normal file
98
src/twmessagebox4.js
Normal file
@ -0,0 +1,98 @@
|
||||
import TWDialog4 from "./twdialog4.js"
|
||||
|
||||
|
||||
class TWMessageBox4 {
|
||||
#defaultButtonColour = "bg-white hover:bg-gray-50 text-gray-700";
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
get DefaultOptions() {
|
||||
const a = this;
|
||||
|
||||
return {
|
||||
Id: a.#randomN(30001, 99999),
|
||||
Title: "",
|
||||
Content: "",
|
||||
// Url: null,
|
||||
Size: "md",
|
||||
// ShowFooter: true,
|
||||
// EasyClose: true,
|
||||
EnableMovement: true,
|
||||
EnableResize: true,
|
||||
Buttons: [
|
||||
{ Label: "Yes", Value: "Yes", Colour: a.#defaultButtonColour },
|
||||
{ Label: "No", Value: "No", Colour: a.#defaultButtonColour },
|
||||
{ Label: "Cancel", Value: "Cancel", Colour: a.#defaultButtonColour }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async Show(options) {
|
||||
const a = this;
|
||||
const newOptions = Object.assign(a.DefaultOptions, options);
|
||||
|
||||
let html = "";
|
||||
newOptions.Buttons.forEach(el => {
|
||||
html += a.#generateButton(el.Label, el.Value, el.Colour);
|
||||
});
|
||||
|
||||
await TW4Dialog.Show(newOptions);
|
||||
|
||||
const modal = TW4Dialog.Find(newOptions.Id);
|
||||
modal.Footer.innerHTML = html;
|
||||
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
modal.Footer.querySelectorAll("button").forEach(el => {
|
||||
el.addEventListener("click", (e) => {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
|
||||
const value = el.getAttribute("data-prompt-value");
|
||||
|
||||
TW4Dialog.Close(newOptions.Id);
|
||||
|
||||
resolve(value);
|
||||
});
|
||||
});
|
||||
|
||||
modal.Modal.querySelectorAll("[aria-label='Close']").forEach(el => {
|
||||
el.addEventListener("click", (e) => {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
|
||||
TW4Dialog.Close(newOptions.Id);
|
||||
|
||||
resolve("");
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#generateButton(label, value, colour) {
|
||||
const html = `
|
||||
<button class="px-4 py-2 text-sm font-medium ` + colour + ` border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer" data-prompt-value="` + value + `">
|
||||
` + label + `
|
||||
</button>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
#randomN(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// window.TW4MessageBox = new TWMessageBox4();
|
||||
export default TWMessageBox4;
|
||||
123
src/twpromptbox4.js
Normal file
123
src/twpromptbox4.js
Normal file
@ -0,0 +1,123 @@
|
||||
import TWDialog4 from "./twdialog4.js"
|
||||
|
||||
|
||||
class TWPromptBox4 {
|
||||
#defaultButtonColour = "bg-white hover:bg-gray-50 text-gray-700";
|
||||
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
|
||||
get DefaultOptions() {
|
||||
const a = this;
|
||||
|
||||
return {
|
||||
Id: a.#randomN(30001, 99999),
|
||||
Title: "",
|
||||
Content: "",
|
||||
Size: "sm",
|
||||
// ShowFooter: true,
|
||||
// EasyClose: true,
|
||||
EnableMovement: true,
|
||||
EnableResize: true,
|
||||
Placeholder: "",
|
||||
Value: "",
|
||||
Style: {
|
||||
Label: "mb-1 inline-block",
|
||||
Textbox: "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
|
||||
BoxType: "text"
|
||||
},
|
||||
Buttons: [
|
||||
{ Label: "OK", Value: "OK", Colour: a.#defaultButtonColour },
|
||||
{ Label: "Cancel", Value: "Cancel", Colour: a.#defaultButtonColour }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
async Show(options) {
|
||||
const a = this;
|
||||
const newOptions = Object.assign(a.DefaultOptions, options);
|
||||
newOptions.Url = null;
|
||||
|
||||
let html = "";
|
||||
newOptions.Buttons.forEach(el => {
|
||||
html += a.#generateButton(el.Label, el.Value, el.Colour);
|
||||
});
|
||||
|
||||
await TW4Dialog.Show(newOptions);
|
||||
|
||||
const modal = TW4Dialog.Find(newOptions.Id);
|
||||
modal.Body.innerHTML = a.#generateFormGroup(newOptions);
|
||||
modal.Footer.innerHTML = html;
|
||||
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
modal.Footer.querySelectorAll("button").forEach(el => {
|
||||
el.addEventListener("click", (e) => {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
|
||||
const text = modal.Body.querySelector("input")?.value ?? "";
|
||||
const value = el.getAttribute("data-prompt-value");
|
||||
|
||||
TW4Dialog.Close(newOptions.Id);
|
||||
|
||||
resolve({ Value: text, Button: value });
|
||||
});
|
||||
});
|
||||
|
||||
modal.Modal.querySelectorAll("[aria-label='Close']").forEach(el => {
|
||||
el.addEventListener("click", (e) => {
|
||||
e?.stopPropagation();
|
||||
e?.preventDefault();
|
||||
|
||||
TW4Dialog.Close(newOptions.Id);
|
||||
|
||||
resolve({ Value: "", Button: "" });
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
#generateFormGroup(options) {
|
||||
const a = this;
|
||||
const id = "prompt-" + a.#randomN(30001, 99999);
|
||||
|
||||
const html = `
|
||||
<div class="mb-4">
|
||||
<label for="` + id + `" class="` + options.Style.Label + `">
|
||||
` + options.Content + `
|
||||
</label>
|
||||
<input id="` + id + `" type="` + options.Style.BoxType + `" placeholder="` + options.Placeholder + `" value="` + options.Value + `" class="` + options.Style.Textbox + `" />
|
||||
</div>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
#generateButton(label, value, colour) {
|
||||
const html = `
|
||||
<button class="px-4 py-2 text-sm font-medium ` + colour + ` border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 cursor-pointer" data-prompt-value="` + value + `">
|
||||
` + label + `
|
||||
</button>
|
||||
`;
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
#randomN(min, max) {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
|
||||
return Math.floor(Math.random() * (max - min) + min);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// window.TW4PromptBox = new TWPromptBox4();
|
||||
export default TWPromptBox4;
|
||||
59
webpack.config.js
Normal file
59
webpack.config.js
Normal file
@ -0,0 +1,59 @@
|
||||
const path = require('path');
|
||||
const { version } = require('./package.json');
|
||||
const webpack = require('webpack');
|
||||
|
||||
|
||||
class PrependVersionPlugin {
|
||||
apply(compiler) {
|
||||
compiler.hooks.emit.tapAsync('PrependVersionPlugin', (compilation, callback) => {
|
||||
Object.keys(compilation.assets).forEach((filename) => {
|
||||
if (filename.endsWith('.js')) {
|
||||
const asset = compilation.assets[filename];
|
||||
|
||||
const headerText = `/*!\n * TWDialog4 v${version}\n * Copyright 2025-2025 Ray Lam (https://www.hiimray.co.uk/bootstrap-js-bstoast4)\n *\n */\n`;
|
||||
|
||||
const newContent = headerText + asset.source();
|
||||
|
||||
compilation.assets[filename] = {
|
||||
source: () => newContent,
|
||||
size: () => newContent.length,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
tw4dialog: './src/index.js'
|
||||
},
|
||||
output: {
|
||||
filename: `[name].dist.js`,
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
clean: true,
|
||||
libraryTarget: 'global'
|
||||
},
|
||||
mode: 'production',
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env.VERSION': JSON.stringify(version)
|
||||
}),
|
||||
new PrependVersionPlugin()
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.scss$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user