Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@byojs/modal

Package Overview
Dependencies
Maintainers
0
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@byojs/modal - npm Package Compare versions

Comparing version 0.0.0-20240903040200 to 0.0.9

dist/external/@byojs/toggler/external/@byojs/scheduler/scheduler.mjs

6

package.json
{
"name": "@byojs/modal",
"description": "Simple wrapper around SweetAlert2",
"version": "0.0.0-20240903040200",
"version": "0.0.9",
"exports": {

@@ -21,4 +21,4 @@ "./": "./dist/modal.mjs"

"dependencies": {
"@byojs/scheduler": "~0.0.0-20240903035700",
"sweetalert2": "~11.12.4"
"@byojs/toggler": "~0.1.0",
"sweetalert2": "~11.13.2"
},

@@ -25,0 +25,0 @@ "devDependencies": {

@@ -6,6 +6,8 @@ # Modal

**Modal** ... // TODO
**Modal** makes it easy to create nice, accessible modal dialogs (using the widely used and popular [SweetAlert 2](https://sweetalert2.github.io) library).
```js
// TODO
import { showNotice } from "..";
showNotice("Hello, friend.");
```

@@ -21,4 +23,6 @@

The main purpose of **Modal** is...
The main purpose of **Modal** is to provide a simple set of wrappers (and default behaviors) around [SweetAlert 2](https://sweetalert2.github.io).
In addition to standard modals and prompt-dialogs, **Modal** also provides time-limited *Toast* popups (non-modal) as well as a debounced (UX-optimized) spinnner (modal) for blocking asynchronous operations in your UI.
## Deployment / Import

@@ -38,6 +42,6 @@

Just `import` the adapter(s) of your choice, like so:
Just `import` the methods of your choice, like so:
```js
import { /* TODO */ } from "@byojs/modal";
import { showNotice, showError } from "@byojs/modal";
```

@@ -64,3 +68,3 @@

```js
import { /* TODO */ } from "modal";
import { showNotice, showError } from "modal";
```

@@ -72,10 +76,109 @@

The API provided by **Modal**...
The API provided by **Modal** includes a variety of methods for displaying different types of modals.
### Spinner
When asynchronous (but "blocking") behavior is happening on a page -- such as loading important data or behavior -- it can be useful to show a modal spinner to the user to indicate they shouldn't try to interact with the page temporarily.
But more importantly, spinners should be carefully managed to avoid detractive and distracting UX. For example, if an operation is going to complete pretty quickly, flashing up a spinner for only a fraction of a second might not be helpful. It's therefore preferable to [*debounce*](https://github.com/byojs/scheduler?tab=readme-ov-file#debouncing) (i.e., delaying briefly) the spinner being shown.
On the other hand, once a spinner has been shown, the underlying operation might finish a few milliseconds later, which would then hide the spinner quickly, causing the same unfortunate UX flash. Even worse, a series of async operations could cause the spinner to flicker on and off repeatedly. So the closing of an open spinner *also* needs to be briefly debounced, even though that *technically* delays the user slightly longer than strictly required.
**Note:** This extra *delay* is only UX perceptible, it's not actually a delay that the underlying code would experience. It's also possible to call another **Modal** method to popup another type of modal, which will immediately hide the spinner.
Since all this spinner timing is highly UX dependent, the amount of delay is configurable, for both the debounce on showing the spinner (default: `300`ms) and the debounce on hiding the spinner (default: `500`ms).
Importantly, **Modal** makes all this complicated spinner timing management easy.
```js
// .. TODO
import {
configSpinner,
startSpinner,
stopSpinner
} from "..";
// override the spinner timing defaults
configSpinner(
/*showDelay=*/150,
/*hideDelay=*/225
);
// schedule the spinner to show up
// (after its debounce delay)
startSpinner();
// later, schedule the spinner to hide
// (after its debounce delay)
stopSpinner();
```
// TODO
**Note:** Even though the `startSpinner()` and `stopSpinner()` functions fire off asynchronous behavior (spinner modal dependent on debounce delays), the function themselves are synchronous (not promise returning).
Both `startSpinner()` and `stopSpinner()` are idempotently safe, meaning you *could* call `startSpinner()` twice before calling `stopSpinner()`, or vice versa, and you still just get the one scheduled action (showing or hiding).
Also, if you call `stopSpinner()` after `startSpinner()` but *before* the show-debounce delay has transpired, the spinner showing will be canceled. Likewise, if you call `showSpinner()` after `stopSpinner()` but *before* the hide-debounce delay has transpired, the spinner hiding will be canceled.
### Toast
A *toast* is a briefly displayed notice, in the upper right corner of the page, that only displays for a period of time then auto-hides itself. Toasts do have a "X" close button to dismiss them early.
**Note:** Toasts are not *technically* "modal" -- they don't actually block the rest of the page. But they're included with this library since they're so closely related.
To display a toast:
```js
import { showToast } from "..";
// wait 5s (default)
showToast("Quick heads-up!");
// wait 15s
showToast("Longer message...",15000);
```
The minimum value for the delay is `250`ms (1/4 of a second).
### Notice
A *notice* is a standard modal that presents textual information. To display a notice modal:
```js
import { showNotice } from "..";
showNotice("This is important information.");
```
**Note:** This modal requires a user to click "OK", or dismiss the dialog with `<ESCAPE>` key or by clicking the "X" icon.
### Error
An *error* is a standard modal that presents textual information that represents something that went wrong. To display an error modal:
```js
import { showError } from "..";
showError("Oops, that request failed. Please try again.");
```
**Note:** This modal requires a user to click "OK", or dismiss the dialog with `<ESCAPE>` key or by clicking the "X" icon.
### Simple Prompt
An *simple prompt* is a standard modal asks the user to input one piece of information, such as an email address. To display a simple-prompt modal:
```js
import { promptSimple } from "..";
promptSimple({
title: "We need your information...",
text: "Please enter your email:",
input: "email",
inputPlaceholder: "someone@whatever.tld"
});
```
The options available to `promptSimple()` are [passed directly through to SweetAlert2](https://sweetalert2.github.io/#configuration). **Modal** provides a few sensible defaults, but pretty much everything can be overridden here, as you see fit.
**Note:** This modal requires a user to click the confirm button (default: "Submit"), or dismiss the dialog with `<ESCAPE>` key or by clicking the cancel button (default: "Cancel") or "X" icon.
## Re-building `dist/*`

@@ -82,0 +185,0 @@

@@ -17,4 +17,4 @@ #!/usr/bin/env node

const NODE_MODULES_DIR = path.join(PKG_ROOT_DIR,"node_modules");
const SCHEDULER_DIST = path.join(NODE_MODULES_DIR,"@byojs","scheduler","dist","scheduler.mjs");
const SWAL_DIST = path.join(NODE_MODULES_DIR,"sweetalert2","dist","sweetalert2.all.min.js");
const BYOJS_TOGGLER_DIST_DIR = path.join(NODE_MODULES_DIR,"@byojs","toggler","dist");
const SWAL_DIST = path.join(NODE_MODULES_DIR,"sweetalert2","dist","sweetalert2.esm.all.min.js");

@@ -24,3 +24,3 @@ const DIST_DIR = path.join(PKG_ROOT_DIR,"dist");

const DIST_EXTERNAL_BYOJS_DIR = path.join(DIST_EXTERNAL_DIR,"@byojs");
const DIST_EXTERNAL_BYOJS_SCHEDULER_DIR = path.join(DIST_EXTERNAL_BYOJS_DIR,"scheduler");
const DIST_EXTERNAL_BYOJS_TOGGLER_DIR = path.join(DIST_EXTERNAL_BYOJS_DIR,"toggler");

@@ -41,3 +41,3 @@

DIST_EXTERNAL_BYOJS_DIR,
DIST_EXTERNAL_BYOJS_SCHEDULER_DIR,
DIST_EXTERNAL_BYOJS_TOGGLER_DIR,
]) {

@@ -81,4 +81,3 @@ if (!(await safeMkdir(dir))) {

(contents,outputPath) => ({
// add default ESM export
contents: `${contents};export default globalThis.Sweetalert2;`,
contents,

@@ -90,5 +89,6 @@ // rename file

await buildFiles(
[ SCHEDULER_DIST, ],
path.dirname(SCHEDULER_DIST),
DIST_EXTERNAL_BYOJS_SCHEDULER_DIR,
recursiveReadDir(BYOJS_TOGGLER_DIST_DIR),
BYOJS_TOGGLER_DIST_DIR,
DIST_EXTERNAL_BYOJS_TOGGLER_DIR,
// simple copy as-is
(contents,outputPath) => ({ contents, outputPath, })

@@ -95,0 +95,0 @@ );

@@ -1,3 +0,3 @@

import Swal from "./external/esm.swal.mjs";
import Scheduler from "@byojs/scheduler";
import Swal from "sweetalert2";
import Toggler from "@byojs/toggler";

@@ -7,4 +7,4 @@

var spinnerStart = Scheduler(300,400);
var spinnerCancel;
var modalStatus = "closed";
var toggleSpinner = configSpinner(300,500);

@@ -15,14 +15,20 @@

export {
showToast,
showNotice,
showError,
showToast,
promptSimple,
configSpinner,
startSpinner,
stopSpinner,
close,
};
var publicAPI = {
showToast,
showNotice,
showError,
showToast,
promptSimple,
configSpinner,
startSpinner,
stopSpinner,
close,
};

@@ -34,12 +40,5 @@ export default publicAPI;

function showError(errMsg) {
return Swal.fire({
title: "Error!",
text: errMsg,
icon: "error",
confirmButtonText: "OK",
});
}
function showToast(toastMsg,hideDelay = 5000) {
modalStatus = "opening";
function showToast(toastMsg) {
return Swal.fire({

@@ -49,3 +48,3 @@ text: toastMsg,

showCloseButton: true,
timer: 5000,
timer: Math.max(Number(hideDelay) || 0,250),
toast: true,

@@ -56,5 +55,32 @@ position: "top-end",

},
didOpen: onModalOpen,
didDestroy: onModalClose,
});
}
function showNotice(noticeMsg) {
modalStatus = "opening";
return Swal.fire({
text: noticeMsg,
icon: "info",
confirmButtonText: "OK",
didOpen: onModalOpen,
didDestroy: onModalClose,
});
}
function showError(errMsg) {
modalStatus = "opening";
return Swal.fire({
title: "Error!",
text: errMsg,
icon: "error",
confirmButtonText: "OK",
didOpen: onModalOpen,
didDestroy: onModalClose,
});
}
async function promptSimple({

@@ -70,4 +96,8 @@ title = "Enter Info",

icon = "question",
didOpen = onModalOpen,
didDestroy = onModalClose,
...swalOptions
} = {}) {
modalStatus = "opening";
var result = await Swal.fire({

@@ -82,2 +112,13 @@ title,

allowEscapeKey,
icon,
didOpen: (
didOpen != onModalOpen ?
(...args) => (onModalOpen(...args), didOpen(...args)) :
didOpen
),
didDestroy: (
didDestroy != onModalClose ?
(...args) => (onModalClose(...args), didDestroy(...args)) :
didDestroy
),
...swalOptions

@@ -92,26 +133,71 @@ });

function configSpinner(startDelay = 300,stopDelay = 500) {
toggleSpinner = Toggler(startDelay,stopDelay);
}
function startSpinner() {
if (!spinnerCancel) {
spinnerCancel = spinnerStart(showSpinner);
if (![ "opening", "open", ].includes(modalStatus)) {
modalStatus = "opening";
toggleSpinner(showSpinner,hideSpinner);
}
}
function stopSpinner() {
if (![ "closing", "closed", ].includes(modalStatus)) {
modalStatus = "closing";
toggleSpinner(showSpinner,hideSpinner);
}
}
function showSpinner() {
Swal.fire({
position: "top",
showConfirmButton: false,
allowOutsideClick: false,
allowEscapeKey: false,
});
Swal.showLoading();
modalStatus = "open";
// ensure we don't "re-open" an already-open spinner modal,
// as this causes a flicker that is UX undesirable.
if (!(
Swal.isVisible() &&
Swal.getPopup().matches(".spinner-popup"))
) {
Swal.fire({
position: "top",
showConfirmButton: false,
allowOutsideClick: false,
allowEscapeKey: false,
customClass: {
// used purely for .matches(), not for CSS,
// although you *can* add your own CSS
// `.spinner-popup` class, to customize its
// styling
popup: "spinner-popup",
},
didOpen: onModalOpen,
didDestroy: onModalClose,
});
Swal.showLoading();
}
}
function stopSpinner() {
if (spinnerCancel) {
spinnerCancel();
spinnerCancel = null;
if (Swal.isVisible() && Swal.getPopup().matches(".spinner-popup")) {
return Swal.close();
}
function hideSpinner() {
modalStatus = "closed";
// ensure we only close an actually-open spinner
// modal (and not some other valid modal)
if (
Swal.isVisible() &&
Swal.getPopup().matches(".spinner-popup")
) {
close();
}
}
function close() {
Swal.close();
}
function onModalOpen() {
modalStatus = "open";
}
function onModalClose() {
modalStatus = "closed";
}

@@ -5,2 +5,3 @@ // note: this module specifier comes from the import-map

import Modal from "modal/src";
import Swal from "sweetalert2";

@@ -10,2 +11,4 @@

var testResultsEl;
if (document.readyState == "loading") {

@@ -22,5 +25,270 @@ document.addEventListener("DOMContentLoaded",ready,false);

async function ready() {
// TODO
var runAllTestsBtn = document.getElementById("run-all-tests-btn");
var runSpinnerModalTestsBtn = document.getElementById("run-spinner-tests-btn");
var runToastModalTestsBtn = document.getElementById("run-toast-modal-tests-btn");
var runNoticeModalTestsBtn = document.getElementById("run-notice-modal-tests-btn");
var runErrorModalTestsBtn = document.getElementById("run-error-modal-tests-btn");
var runSimplePromptModalTestsBtn = document.getElementById("run-simple-prompt-modal-tests-btn");
testResultsEl = document.getElementById("test-results");
runAllTestsBtn.addEventListener("click",runAllTests,false);
runSpinnerModalTestsBtn.addEventListener("click",runSpinnerModalTests,false);
runToastModalTestsBtn.addEventListener("click",runToastModalTests,false);
runNoticeModalTestsBtn.addEventListener("click",runNoticeModalTests,false);
runErrorModalTestsBtn.addEventListener("click",runErrorModalTests,false);
runSimplePromptModalTestsBtn.addEventListener("click",runSimplePromptModalTests,false);
}
async function runAllTests() {
testResultsEl.innerHTML = "";
for (let testFn of [
runSpinnerModalTests, runToastModalTests, runNoticeModalTests,
runErrorModalTests, runSimplePromptModalTests,
]) {
let result = await testFn();
if (result === false) {
break;
}
await timeout(500);
}
}
async function runSpinnerModalTests() {
var results = [];
var expected = [ "show", "hide", "show", "hide", "show", "hide", ];
testResultsEl.innerHTML += "Running spinner-modal tests... please wait.<br>";
try {
Modal.configSpinner(100,100);
var observer = new MutationObserver(mutationList => {
results.push(
...(
mutationList.filter(mutation => (
mutation.type == "attributes" &&
mutation.attributeName == "class" &&
mutation.target.matches(".spinner-popup.swal2-show, .spinner-popup.swal2-hide")
))
.map(mutation => (
mutation.target.className.match(/\bswal2-(show|hide)\b/)[1]
))
)
);
});
observer.observe(document.body,{ attributes: true, subtree: true, });
Modal.startSpinner();
await timeout(110);
Modal.stopSpinner();
await timeout(500);
Modal.startSpinner();
Modal.startSpinner();
Modal.startSpinner();
Modal.stopSpinner();
await timeout(110);
Modal.startSpinner();
Modal.stopSpinner();
Modal.stopSpinner();
Modal.stopSpinner();
await timeout(110);
Modal.startSpinner();
await timeout(50);
Modal.stopSpinner();
await timeout(50);
Modal.startSpinner();
await timeout(110);
Modal.stopSpinner();
await timeout(500);
Modal.startSpinner();
await timeout(110);
Modal.stopSpinner();
await timeout(50);
Modal.startSpinner();
await timeout(110);
Modal.stopSpinner();
await timeout(500);
// remove consecutive duplicates
results = results.reduce((list,v) => (
list.length == 0 || list[list.length - 1] != v ?
[ ...list, v ] :
list
),[]);
if (JSON.stringify(results) == JSON.stringify(expected)) {
testResultsEl.innerHTML += "(Spinner Modal) PASSED.<br>";
return true;
}
else {
testResultsEl.innerHTML += `(Spinner Modal) FAILED: expected '${expected.join(",")}', found '${results.join(",")}'<br>`;
}
}
catch (err) {
logError(err);
testResultsEl.innerHTML += "(Spinner Modal) FAILED -- see console<br>";
}
return false;
}
async function runToastModalTests() {
testResultsEl.innerHTML += "Running toast-modal tests... please wait.<br>";
try {
Modal.showToast("Testing toasts...",500);
await timeout(750);
if (!Swal.isVisible() && Swal.getContainer() == null) {
testResultsEl.innerHTML += "(Toast Modal) PASSED.<br>";
return true;
}
else {
testResultsEl.innerHTML += "(Toast Modal) FAILED: toast modal not closed<br>";
}
}
catch (err) {
logError(err);
testResultsEl.innerHTML = "(Toast Modal) FAILED -- see console"
}
return false;
}
async function runNoticeModalTests() {
var results = [];
var expected = [ true, true, false, ];
testResultsEl.innerHTML += "Running notice-modal tests... please wait.<br>";
try {
let noticeMsg = "Testing notice modal.";
Modal.showNotice(noticeMsg);
await timeout(250);
let popup = Swal.getPopup();
results.push(
Swal.isVisible(),
(
popup.querySelector(".swal2-icon.swal2-info.swal2-icon-show") != null &&
popup.querySelector(".swal2-html-container").innerText == noticeMsg
)
);
Swal.close();
await timeout(250);
results.push(
Swal.isVisible() || Swal.getContainer() != null
);
if (JSON.stringify(results) == JSON.stringify(expected)) {
testResultsEl.innerHTML += "(Notice Modal) PASSED.<br>";
return true;
}
else {
testResultsEl.innerHTML += `(Notice Modal) FAILED: expected '${expected.join(",")}', found '${results.join(",")}'<br>`;
}
}
catch (err) {
logError(err);
testResultsEl.innerHTML = "(Notice Modal) FAILED -- see console"
}
return false;
}
async function runErrorModalTests() {
var results = [];
var expected = [ true, true, false, ];
testResultsEl.innerHTML += "Running error-modal tests... please wait.<br>";
try {
let errMsg = "Testing error modal.";
Modal.showError(errMsg);
await timeout(250);
let popup = Swal.getPopup();
results.push(
Swal.isVisible(),
(
popup.querySelector(".swal2-icon.swal2-error.swal2-icon-show") != null &&
popup.querySelector(".swal2-html-container").innerText == errMsg
)
);
Swal.close();
await timeout(250);
results.push(
Swal.isVisible() || Swal.getContainer() != null
);
if (JSON.stringify(results) == JSON.stringify(expected)) {
testResultsEl.innerHTML += "(Error Modal) PASSED.<br>";
return true;
}
else {
testResultsEl.innerHTML += `(Error Modal) FAILED: expected '${expected.join(",")}', found '${results.join(",")}'<br>`;
}
}
catch (err) {
logError(err);
testResultsEl.innerHTML = "(Error Modal) FAILED -- see console"
}
return false;
}
async function runSimplePromptModalTests() {
var results = [];
var expected = [ true, true, false, ];
testResultsEl.innerHTML += "Running prompt-modal tests... please wait.<br>";
try {
let promptTitle = "Testing Prompt Modal";
let promptText = "Testing prompt modal.";
let promptInputLabel = "good label";
let promptConfirmButtonText = "Yep";
let promptCancelButtonText = "Nope";
let now = new Date();
let currentDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2,"0")}-${String(now.getDate()).padStart(2,"0")}`;
Modal.promptSimple({
title: promptTitle,
text: promptText,
input: "date",
inputLabel: promptInputLabel,
inputValue: currentDate,
confirmButtonText: promptConfirmButtonText,
cancelButtonText: promptCancelButtonText,
});
await timeout(250);
let popup = Swal.getPopup();
results.push(
Swal.isVisible(),
(
popup.querySelector(".swal2-title").innerText == promptTitle &&
popup.querySelector(".swal2-icon.swal2-question.swal2-icon-show") != null &&
popup.querySelector(".swal2-html-container").innerText == promptText &&
popup.querySelector(".swal2-input-label").innerText == promptInputLabel &&
popup.querySelector(".swal2-input[type=date]").value == currentDate &&
popup.querySelector(".swal2-confirm").innerText == promptConfirmButtonText &&
popup.querySelector(".swal2-cancel").innerText == promptCancelButtonText
)
);
Swal.close();
await timeout(250);
results.push(
Swal.isVisible() || Swal.getContainer() != null
);
if (JSON.stringify(results) == JSON.stringify(expected)) {
testResultsEl.innerHTML += "(Error Modal) PASSED.<br>";
return true;
}
else {
testResultsEl.innerHTML += `(Error Modal) FAILED: expected '${expected.join(",")}', found '${results.join(",")}'<br>`;
}
}
catch (err) {
logError(err);
testResultsEl.innerHTML = "(Error Modal) FAILED -- see console"
}
return false;
}
function timeout(ms) {
return new Promise(res => setTimeout(res,ms));
}
function logError(err,returnLog = false) {

@@ -27,0 +295,0 @@ var err = `${

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc