V. 2.1.0 - Yohns Fork

This commit is contained in:
Yohn 2024-11-27 00:38:59 -05:00
parent b611b528bc
commit 42b62b10a6
10 changed files with 66 additions and 38 deletions

1
demo/Moved2Docs.txt Normal file
View file

@ -0,0 +1 @@
Moved to the docs directory so I can put the updated features on github pages.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

View file

@ -1,403 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="light dark" />
<title>Preview • Pico CSS</title>
<meta name="description" content="A pure HTML example, without dependencies." />
<!-- Pico.css -->
<link rel="stylesheet" href="./../css/pico.lime.min.css" />
<link rel="stylesheet" href="./../css/pico.colors.min.css" />
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="col-12 col-md-3 col-lg-2">
<!-- Header -->
<header>
<hgroup>
<h1>Pico</h1>
<p>A Pico CSS + example with addition to the standard Pico CSS library.</p>
<p>Demo page from <a href="https://picocss.com/docs/modal#utilities">picocss.com model docs</a></p>
</hgroup>
<details class="dropdown">
<summary role="button" class="secondary">Theme</summary>
<ul>
<li><a href="#" data-theme-switcher="auto">Auto</a></li>
<li><a href="#" data-theme-switcher="light">Light</a></li>
<li><a href="#" data-theme-switcher="dark">Dark</a></li>
</ul>
</details>
<aside>
<nav>
<ul>
<li><a href="#validation">Validation</a></li>
<li><a href="#group">Group</a></li>
<li><a href="#rows">Row</a></li>
<li><a href="#row-offsets">Row Offset</a></li>
<li><a href="#row-alignments">Row Alignments</a></li>
<li><a href="#row-breakpoints">Row Breakpoints</a></li>
<li><a href="#modal">Modals</a></li>
<li><a href="#notifications">Notifications</a></li>
<li><a href="#accordions">Accorddions</a></li>
<li><a href="#timeline">Timeline</a></li>
</ul>
</nav>
</aside>
</header>
<!-- /Header -->
</div>
<main class="col-12 col-md-9 col-lg-10">
<!-- Validation -->
<form action="javascript:void(0);">
<article id="group">
<header><h2>Validation</h2></header>
<p>
Form :user-valid and :user-invalid
</p>
<input placeholder="name" type="text" name="test" minlength="4" maxlength="30" pattern="[a-z]{4,30}" required>
<small data-valid="this is good!" data-invalid="Min of 4 characters, max of 39, only a-z no caps."></small>
<input placeholder="Url" type="url" name="test" required>
<small data-valid="this is good!" data-invalid="Must be a url."></small>
<input placeholder="Email" type="email" name="test" required>
<small data-valid="this is good!" data-invalid="Must be a valid email."></small>
<select name="favorite-cuisine" required>
<option selected disabled value="">Select your favorite cuisine...</option>
<option>Italian</option>
<option>Japanese</option>
<option>Indian</option>
<option>Thai</option>
<option>French</option>
</select>
<small data-valid="this is good!" data-invalid="Error.."></small>
<input type="date" name="date" required>
<small data-valid="this is good!" data-invalid="Error.."></small>
<input type="datetime-local" name="datetime-local" required>
<small data-valid="this is good!" data-invalid="Error.."></small>
<input type="month" name="month" min="2018-03" max="2024-12" placeholder="2024-12" step="1" pattern="[0-9]{4}-[0-9]{2}" required>
<small data-valid="this is good!" data-invalid="Format: YYYY-MM"></small>
<input type="time" name="time" required>
<small data-valid="this is good!" data-invalid="Error.."></small>
<header>Check JS for FileValidator class</header>
<input type="file" data-max-size="1048576" accept=".jpg,.svg,.png,.gif,.webp" id="checkFile" multiple />
<small data-invalid="Only .jpg,.svg,.png,.gif,.webp image types allowed" data-valid="Good!"></small>
<textarea minlength="5" placeholder="its always valid?" required></textarea>
<small data-valid="this is good!" data-invalid="Min of 5 characters!"></small>
<br>
<label><input type="checkbox" name="english" required />English </label>
<footer>
<input type="submit" value="submit" class="btn btn-primary">
</footer>
</article>
</form>
<!-- /Validation -->
<!-- Group -->
<article id="group">
<header><h2>Group</h2></header>
<form action="javascript:void(0);">
<fieldset role="group">
<label for="email">Email:</label>
<input name="email" type="email" placeholder="Enter your email" autocomplete="email" />
<input type="submit" value="Subscribe" />
</fieldset>
</form>
<form action="javascript:void(0);">
<fieldset role="group">
<label for="ex-browser">Browser:</label>
<select id="ex-browser" name="ex-browser">
<option value="Firefox">Firefox</option>
<option value="Chrome">Chrome</option>
<option value="Opera">Opera</option>
<option value="Safari">Safari</option>
</select>
<input type="submit" value="Save" />
</fieldset>
</form>
</article>
<!-- ./ Group -->
<hr>
<!-- rows -->
<article id="rows">
<header><h2>Rows</h2></header>
<p>
Similar to <a href="https://getbootstrap.com/docs/5.3/layout/columns/" target="_blank">Bootstrap 5.3.3</a>, with the responsive <code>.col-*COL#*</code>, <code>.col-*BREAKPOINT*-*COL#*</code>, <code>.offset-*COL#*</code>, <code>.offset-*BREAKPOINT*-*COL#*</code>.
</p>
<p>
<code>.row</code> has a max width set to the viewport of your <code>xl</code> viewport (1300px by default)</p>
<code>.row-fluid</code> max width is 100%.
</p>
<div class="row-fluid">
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
<div class="col-6"><article class="pico-background-lime-750">col-6</article></div>
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
</div>
<div class="row-fluid">
<div class="col-3 offset-1"><article class="pico-background-lime-750">col-3 offset-1</article></div>
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
</div>
<div class="row-fluid">
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
</div>
<div class="row-fluid">
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
<div class="col-8"><article class="pico-background-lime-750">col-8</article></div>
<div class="col-2"><article class="pico-background-lime-750">col-2</article></div>
</div>
<div class="row-fluid">
<div class="col-5"><article class="pico-background-lime-750">col-5</article></div>
<div class="col-7"><article class="pico-background-lime-750">col-7</article></div>
</div>
<div class="row-fluid">
<div class="col-9"><article class="pico-background-lime-750">col-9</article></div>
<div class="col-3"><article class="pico-background-lime-750">col-3</article></div>
</div>
</article>
<!-- /rows -->
<hr>
<!-- offset -->
<article id="row-offsets">
<header><h2>Columns with offsets defined by breakpoints</h2></header>
<p>When defining multiple <code>.col-*-*</code> on the same element, the order your put the classes on the element matter.</p>
<div class="row-fluid">
<div class="col-2 col-md-4"><article class="pico-background-lime-750">col-2, col-md-4</article></div>
<div class="col-4 offset-8 col-md-6 offset-md-6"><article class="pico-background-lime-750">col-4, offset-8, col-md-6, offset-md-6</article></div>
</div>
<div class="row-fluid">
<div class="col-5 offset-1 col-md-11"><article class="pico-background-lime-550">col-5, offset-1, col-md-11</article></div>
<div class="col-2 col-md-6"><article class="pico-background-lime-550">col-2, col-md-6</article></div>
<div class="col-3 offset-9 col-md-6 offset-md-6"><article class="pico-background-lime-550">col-3, offset-9, col-md-6, offset-md-6</article></div>
</div>
<div class="row-fluid align-center">
<div class="col-3 col-md-12 col-lg-6"><article class="pico-background-lime-850">col-3 col-md-12 col-lg-6</article></div>
<div class="col-3 col-md-12 col-lg-6"><article class="pico-background-lime-850">col-3 col-md-12 col-lg-6</article></div>
<div class="col-4 col-md-6 col-lg-3"><article class="pico-background-lime-850">col-4 col-md-6 col-lg-3</article></div>
<div class="col-6 offset-6 col-md-3 offset-md-9 col-lg-2 offset-lg-10"><article class="pico-background-lime-850">col-6 offset-6 col-md-3 offset-md-9 col-lg-2 offset-lg-10</article></div>
</div>
</article>
<!-- /row-offsets -->
<hr>
<!-- row-alignments -->
<article id="row-alignments">
<header><h2>Alignment</h2></header>
<p>
You can add <code>.align-start</code> (default), <code>.align-center</code> or <code>.align-end</code> to your <code>.row</code> or <code>.row-fluid</code> element to align the containing elements to the top, middle, or bottom.</p>
<div class="row-fluid align-center" id="row-align-example">
<div class="col-4"><article class="pico-background-lime-850">col-4</article></div>
<div class="col-4">
<article class="pico-background-lime-850">
<input type="button" class="change-row-align" data-align="start" value=".align-start">
<input type="button" class="change-row-align" data-align="center" value=".align-center">
<input type="button" class="change-row-align" data-align="end" value=".align-end">
</article>
</div>
<div class="col-4"><article class="pico-background-lime-850">col-4</article></div>
</div>
</article>
<!-- /row-alignments -->
<hr>
<!-- row-breakpoints -->
<article id="row-breakpoints">
<header><h2><code> -md- </code> Breakpoints</h2></header>
<div class="row-fluid">
<div class="col-12 col-md-4"><article class="pico-background-lime-650">col-12, col-md-4</article></div>
<div class="col-12 col-md-4"><article class="pico-background-lime-650">col-12, col-md-4</article></div>
<div class="col-12 col-md-4"><article class="pico-background-lime-650">col-12, col-md-4</article></div>
</div>
</article>
<!-- ./ row-breakpoints -->
<hr>
<!-- Modal -->
<article id="modal">
<header><h2>Modal</h2></header>
<button class="contrast" data-target="modal-example" onclick="toggleModal(event)">
Launch demo modal
</button>
</article>
<!-- ./ Modal -->
<hr>
<!-- notifications -->
<article id="notifications">
<header><h2>Notificaton</h2></header>
<button onclick="showNotification('Accessible short notice.')">
Short Notificaton
</button>
<button onclick="showNotification({ html: `
<section>
<h2>Many news!</h2>
<p>This is fantastic!</p>
<button onclick='closeNotification()'>OK</button>
</section>
`, delay: 60 * 1000 })">
Big Notificaton
</button>
<!-- accessible dialog based notification element -->
<dialog role="alert" data-backdrop="false"></dialog>
</article>
<!-- /notifications -->
<hr>
<!-- Accordions -->
<article id="accordions">
<header><h2>Accordions</h2></header>
<details name="example-accordion">
<summary>Accordion 1</summary>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque urna diam,
tincidunt nec porta sed, auctor id velit. Etiam venenatis nisl ut orci consequat, vitae
tempus quam commodo. Nulla non mauris ipsum. Aliquam eu posuere orci. Nulla convallis
lectus rutrum quam hendrerit, in facilisis elit sollicitudin. Mauris pulvinar pulvinar
mi, dictum tristique elit auctor quis. Maecenas ac ipsum ultrices, porta turpis sit
amet, congue turpis.
</p>
</details>
<details name="example-accordion" open>
<summary>Accordion 2</summary>
<ul>
<li>Vestibulum id elit quis massa interdum sodales.</li>
<li>Nunc quis eros vel odio pretium tincidunt nec quis neque.</li>
<li>Quisque sed eros non eros ornare elementum.</li>
<li>Cras sed libero aliquet, porta dolor quis, dapibus ipsum.</li>
</ul>
</details>
</article>
<!-- ./ Accordions -->
<hr>
<article id="timeline" style="background-color: transparent;">
<header>
<h2>Timeline</h2>
<br>
<details>
<summary>View Custom CSS</summary>
<div style="background-color: var(--pico-muted-border-color); padding: .5rem;">
--pico-timeline-line-color: var(--pico-primary-background);<br>
--pico-timeline-dot-background-color: var(--pico-primary-inverse);<br>
--pico-timeline-dot-border-color: var(--pico-primary-background);<br>
--pico-timeline-arrow-color: var(--pico-card-sectioning-background-color);
</div>
</details>
</header>
<div class="timeline">
<section class="point left">
<article>
<header><h3>2017</h3></header>
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
</article>
</section>
<section class="point right">
<article>
<header><h3>2016</h3></header>
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
</article>
</section>
<section class="point left">
<article>
<header><h3>2015</h3></header>
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
</article>
</section>
<section class="point right">
<article>
<header><h3>2012</h3></header>
<p>Lorem ipsum dolor sit amet, quo ei simul congue exerci, ad nec admodum perfecto mnesarchum, vim ea mazim fierent detracto. Ea quis iuvaret expetendis his, te elit voluptua dignissim per, habeo iusto primis ea eam.</p>
</article>
</section>
</div>
</article>
</main>
</div>
</div>
<!-- Footer -->
<footer class="container">
<small>
Built with <a href="https://picocss.com">Pico</a>
<a href="https://github.com/Yohn/PicoCSS">
Source code
</a>
</small>
</footer>
<!-- ./ Footer -->
<!-- Modal example -->
<dialog id="modal-example">
<article>
<header>
<button aria-label="Close" rel="prev" data-target="modal-example" onclick="toggleModal(event)"></button>
<h3>Confirm your action!</h3>
</header>
<p>
Cras sit amet maximus risus. Pellentesque sodales odio sit amet augue finibus
pellentesque. Nullam finibus risus non semper euismod.
</p>
<footer>
<button role="button" class="secondary" data-target="modal-example" onclick="toggleModal(event)">Cancel</button>
<button autofocus data-target="modal-example" onclick="toggleModal(event)">
Confirm
</button>
</footer>
</article>
</dialog>
<!-- ./ Modal example -->
<!-- Minimal theme switcher -->
<script src="js/minimal-theme-switcher.js"></script>
<!-- Modal -->
<script src="js/modal.js"></script>
<!-- alert notifications -->
<script src="js/notifications.js"></script>
<script src="js/FileValidator.js"></script>
<script src="js/accordion.js"></script>
<script>
document.addEventListener("DOMContentLoaded", () => {
const checkFile = document.getElementById("checkFile");
new FileValidator(checkFile);
const alignButtons = document.querySelectorAll(".change-row-align");
const alignRow = document.getElementById("row-align-example");
alignButtons.forEach((button) => {
button.addEventListener("click", () => {
let dir = button.getAttribute('data-align');
console.log(dir)
if(dir == 'start'){
alignRow.classList.remove('align-center', 'align-end');
alignRow.classList.add('align-start');
} else if(dir == 'center'){
alignRow.classList.remove('align-start', 'align-end');
alignRow.classList.add('align-center');
} else {
alignRow.classList.remove('align-start', 'align-center');
alignRow.classList.add('align-end');
}
});
});
});
</script>
</body>
</html>

View file

@ -1,157 +0,0 @@
class FileValidator {
/**
* Initializes the FileValidator with a file input element.
* @param {HTMLInputElement} fileInput - File input element to validate.
* @param {boolean} displayList - Whether to display the file list (default: true).
* @param {HTMLElement} customListContainer - Optional custom container for the file list.
*/
constructor(fileInput, displayList = true, customListContainer = null) {
if (!(fileInput instanceof HTMLInputElement) || fileInput.type !== "file") {
throw new Error("FileValidator requires an input element of type 'file'.");
}
this.fileInput = fileInput;
this.displayList = displayList;
this.customListContainer = customListContainer;
this.fileInput.addEventListener("change", () => this.validateFiles());
// Create a container for the file list
if (this.displayList) {
this.fileListContainer = document.createElement("ul");
this.fileListContainer.className = "file-list";
if (this.customListContainer) {
this.customListContainer.append(this.fileListContainer);
} else {
this.fileInput.insertAdjacentElement("afterend", this.fileListContainer);
}
}
}
/**
* Validates files based on the accept and size attributes of the input element.
* Displays a file list if files are valid.
* @returns {boolean} - True if all files are valid, otherwise false.
*/
validateFiles() {
const accept = this.fileInput.getAttribute("accept") || "";
const maxSize = parseInt(this.fileInput.getAttribute("data-max-size"), 10) || Infinity;
const files = Array.from(this.fileInput.files);
// Clear any existing list
if (this.displayList) {
this.fileListContainer.innerHTML = "";
}
if (files.length === 0) {
console.log("No files selected.");
return false;
}
for (const file of files) {
if (accept && !this.isFileTypeValid(file, accept)) {
console.error(`Invalid file type: ${file.name}`);
this.fileInput.setCustomValidity(`Invalid file type: ${file.name}`);
return false;
}
if (file.size > maxSize) {
console.error(`File too large: ${file.name} (${file.size} bytes)`);
this.fileInput.setCustomValidity(`File too large: ${file.name}`);
return false;
}
// Add file to the list if valid
if (this.displayList) {
this.addFileToList(file);
}
}
this.fileInput.setCustomValidity("");
console.log("All files are valid.");
return true;
}
/**
* Adds a file to the displayed list, including an image preview if the file is an image.
* @param {File} file - The file to add to the list.
*/
addFileToList(file) {
const listItem = document.createElement("li");
listItem.textContent = `${file.name} (${(file.size / 1024).toFixed(2)} KB)`;
// Add an image preview if the file is an image
if (file.type.startsWith("image/")) {
const imagePreview = document.createElement("img");
imagePreview.src = URL.createObjectURL(file);
imagePreview.style.maxWidth = "50px";
imagePreview.style.maxHeight = "50px";
imagePreview.onload = () => URL.revokeObjectURL(imagePreview.src); // Release memory
listItem.prepend(imagePreview);
}
// Add a remove button
const removeButton = document.createElement("button");
removeButton.textContent = " ";
removeButton.className = "btn-file-rm";
removeButton.addEventListener("click", () => this.removeFile(file, listItem));
listItem.append(removeButton);
if (this.customListContainer) {
this.customListContainer.append(listItem);
} else {
this.fileListContainer.append(listItem);
}
}
/**
* Removes a file from the list and updates the input file element.
* @param {File} file - The file to remove.
* @param {HTMLElement} listItem - The list item element to remove.
*/
removeFile(file, listItem) {
const filesArray = Array.from(this.fileInput.files);
const updatedFiles = filesArray.filter(f => f !== file);
// Update the input element with the new FileList
const dataTransfer = new DataTransfer();
updatedFiles.forEach(f => dataTransfer.items.add(f));
this.fileInput.files = dataTransfer.files;
// Remove the list item from the display
listItem.remove();
}
/**
* Checks if a file's type matches any of the allowed types in the accept attribute.
* @param {File} file - File object to check.
* @param {string} accept - Accept attribute value (e.g., ".jpg, .png, image/*").
* @returns {boolean} - True if the file type is valid, otherwise false.
*/
isFileTypeValid(file, accept) {
const acceptedTypes = accept.split(",").map(type => type.trim());
return acceptedTypes.some(type => {
if (type.includes("/") && file.type === type) return true;
if (type.startsWith(".") && file.name.endsWith(type)) return true;
if (type.endsWith("/*")) return file.type.startsWith(type.split("/")[0] + "/");
return false;
});
}
}
// Usage example:
// <input type="file" id="fileInput" accept=".jpg, .png, .gif, .svg" data-max-size="1048576" multiple>
// const fileInput = document.getElementById("fileInput");
// new FileValidator(fileInput, false); // Hide display list
//
// Alternatively, with custom list container:
// const customListContainer = document.getElementById("customListContainer");
// new FileValidator(fileInput, true, customListContainer);
// Usage example2:
// <input type="file" id="checkfile" accept=".jpg, .png, .gif, .svg" data-max-size="1048576" multiple>
// 1048576 = 1MB
// const checkfile = document.getElementById("checkfile");
// new FileValidator(checkfile);

View file

@ -1,35 +0,0 @@
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('details').forEach(el => {
let anim = null, opening = false, closing = false
let summ = el.querySelector('summary')
const runAnim = (height, targetHeight, callback) => {
anim?.cancel()
anim = el.animate({ height: [`${height}px`,`${targetHeight}px`] },{
duration: 400,
easing: 'ease-out'
})
anim.onfinish = () => {
anim = null
el.style.height = el.style.overflow = ''
opening = closing = false
callback?.()
}
}
summ.addEventListener('click', ev => {
ev.preventDefault()
el.style.overflow = 'hidden'
if(!el.open || closing) {
el.style.height = `${el.offsetHeight}px`
el.open = opening = true
runAnim(el.offsetHeight, [...el.children].reduce((a,c) => a+c.offsetHeight, 0))
anim.oncancel = () => opening = false
} else if(el.open || opening) {
closing = true
runAnim(el.offsetHeight, summ.offsetHeight, () => el.open = false)
anim.oncancel = () => closing = false
}
})
})
})

View file

@ -1,79 +0,0 @@
/*!
* Minimal theme switcher
*
* Pico.css - https://picocss.com
* Copyright 2019-2024 - Licensed under MIT
*/
const themeSwitcher = {
// Config
_scheme: "auto",
menuTarget: "details.dropdown",
buttonsTarget: "a[data-theme-switcher]",
buttonAttribute: "data-theme-switcher",
rootAttribute: "data-theme",
localStorageKey: "picoPreferredColorScheme",
// Init
init() {
this.scheme = this.schemeFromLocalStorage;
this.initSwitchers();
},
// Get color scheme from local storage
get schemeFromLocalStorage() {
return window.localStorage?.getItem(this.localStorageKey) ?? this._scheme;
},
// Preferred color scheme
get preferredColorScheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
},
// Init switchers
initSwitchers() {
const buttons = document.querySelectorAll(this.buttonsTarget);
buttons.forEach((button) => {
button.addEventListener(
"click",
(event) => {
event.preventDefault();
// Set scheme
this.scheme = button.getAttribute(this.buttonAttribute);
// Close dropdown
document.querySelector(this.menuTarget)?.removeAttribute("open");
},
false
);
});
},
// Set scheme
set scheme(scheme) {
if (scheme == "auto") {
this._scheme = this.preferredColorScheme;
} else if (scheme == "dark" || scheme == "light") {
this._scheme = scheme;
}
this.applyScheme();
this.schemeToLocalStorage();
},
// Get scheme
get scheme() {
return this._scheme;
},
// Apply scheme
applyScheme() {
document.querySelector("html")?.setAttribute(this.rootAttribute, this.scheme);
},
// Store scheme to local storage
schemeToLocalStorage() {
window.localStorage?.setItem(this.localStorageKey, this.scheme);
},
};
// Init
themeSwitcher.init();

View file

@ -1,76 +0,0 @@
/*
* Modal
*
* Pico.css - https://picocss.com
* Copyright 2019-2024 - Licensed under MIT
*/
//document.addEventListener("DOMContentLoaded", () => {
// Config
const isOpenClass = "modal-is-open";
const openingClass = "modal-is-opening";
const closingClass = "modal-is-closing";
const scrollbarWidthCssVar = "--pico-scrollbar-width";
const animationDuration = 400; // ms
let visibleModal = null;
// Toggle modal
const toggleModal = (event) => {
event.preventDefault();
const modal = document.getElementById(event.currentTarget.dataset.target);
if (!modal) return;
modal && (modal.open ? closeModal(modal) : openModal(modal));
};
// Open modal
const openModal = (modal) => {
const { documentElement: html } = document;
const scrollbarWidth = getScrollbarWidth();
if (scrollbarWidth) {
html.style.setProperty(scrollbarWidthCssVar, `${scrollbarWidth}px`);
}
html.classList.add(isOpenClass, openingClass);
setTimeout(() => {
visibleModal = modal;
html.classList.remove(openingClass);
}, animationDuration);
modal.showModal();
};
// Close modal
const closeModal = (modal) => {
visibleModal = null;
const { documentElement: html } = document;
html.classList.add(closingClass);
setTimeout(() => {
html.classList.remove(closingClass, isOpenClass);
html.style.removeProperty(scrollbarWidthCssVar);
modal.close();
}, animationDuration);
};
// Close with a click outside
document.addEventListener("click", (event) => {
if (visibleModal === null) return;
const modalContent = visibleModal.querySelector("article");
const isClickInside = modalContent.contains(event.target);
!isClickInside && closeModal(visibleModal);
});
// Close with Esc key
document.addEventListener("keydown", (event) => {
if (event.key === "Escape" && visibleModal) {
closeModal(visibleModal);
}
});
// Get scrollbar width
const getScrollbarWidth = () => {
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
return scrollbarWidth;
};
// Is scrollbar visible
const isScrollbarVisible = () => {
return document.body.scrollHeight > screen.height;
};
//})

View file

@ -1,20 +0,0 @@
function showNotification(options = {}) {
const dialog = document.querySelector("dialog[role='alert']");
if (options.text || typeof options === "string") {
dialog.innerText = options.text || options;
} else if (options.html) {
dialog.innerHTML = options.html;
}
dialog.showModal();
setTimeout(() => {
dialog.close();
}, options.delay || 3000);
}
function closeNotification() {
const dialog = document.querySelector("dialog[role='alert']");
dialog.close();
}