diff --git a/examples/generic.html b/examples/generic.html
index 1a27404..b45ae82 100644
--- a/examples/generic.html
+++ b/examples/generic.html
@@ -33,7 +33,10 @@
var alertInstance = new y2kAlert(
"Storage Warning",
Y2K.TYPE_WARNING,
- "Disk space is running low."
+ "Disk space is running low.",
+ {
+ closeOnBackdropClick: true
+ }
);
alertInstance.addEventListener("submit", function (event) {
diff --git a/js/y2k-alert.js b/js/y2k-alert.js
index 97782cc..8c445b2 100644
--- a/js/y2k-alert.js
+++ b/js/y2k-alert.js
@@ -10,7 +10,9 @@
this.title = title || "Alert";
this.type = type || Y2K.TYPE_INFO;
this.message = message || "";
- this.options = options || {};
+ this.options = Object.assign({
+ closeOnBackdropClick: false
+ }, options || {});
this.isVisible = false;
this.inputElement = null;
this.overlayElement = null;
@@ -101,6 +103,16 @@
self.submit();
});
+ this.overlayElement.addEventListener("click", function (event) {
+ if (!self.options.closeOnBackdropClick) {
+ return;
+ }
+
+ if (event.target === self.overlayElement) {
+ self.close("backdrop");
+ }
+ });
+
if (this.inputElement) {
this.inputElement.addEventListener("keydown", function (event) {
if (event.key === "Enter") {
diff --git a/package.json b/package.json
index a339048..9212698 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,13 @@
"style": "dist/y2k-alerts.css",
"scripts": {
"build": "node scripts/build.js",
+ "watch": "node scripts/watch.js",
"test": "npm run build"
+ },
+ "devDependencies": {
+ "browser-sync": "^3.0.3",
+ "chokidar": "^3.6.0",
+ "clean-css": "^5.3.3",
+ "terser": "^5.31.2"
}
}
diff --git a/scripts/build.js b/scripts/build.js
index 5ad8d9d..d446e49 100644
--- a/scripts/build.js
+++ b/scripts/build.js
@@ -1,5 +1,7 @@
const fs = require("fs");
const path = require("path");
+const CleanCSS = require("clean-css");
+const { minify } = require("terser");
const ROOT_DIR = path.resolve(__dirname, "..");
const DIST_DIR = path.join(ROOT_DIR, "dist");
@@ -20,24 +22,8 @@ function readFiles(folder, files) {
.join("\n\n");
}
-function minifyCss(css) {
- return css
- .replace(/\/\*[\s\S]*?\*\//g, "")
- .replace(/\s+/g, " ")
- .replace(/\s*([{}:;,])\s*/g, "$1")
- .replace(/;}/g, "}")
- .trim();
-}
-
-function minifyJs(js) {
- return js
- .replace(/\/\*[\s\S]*?\*\//g, "")
- .replace(/^\s*\/\/.*$/gm, "")
- .replace(/\s+/g, " ")
- .trim();
-}
-
function buildCss() {
+ const cssMinifier = new CleanCSS({ level: 2 });
const baseCss = readFiles(BASE_CSS_DIR, BASE_CSS_FILES);
const themeFolders = fs
.readdirSync(THEMES_DIR, { withFileTypes: true })
@@ -57,9 +43,14 @@ function buildCss() {
const distFile = path.join(DIST_DIR, `y2k-alerts-${themeName}.css`);
const distMinFile = path.join(DIST_DIR, `y2k-alerts-${themeName}.min.css`);
+ const minified = cssMinifier.minify(output);
+
+ if (minified.errors.length > 0) {
+ throw new Error(`CSS minification failed for ${themeName}: ${minified.errors.join(", ")}`);
+ }
fs.writeFileSync(distFile, output, "utf8");
- fs.writeFileSync(distMinFile, minifyCss(output), "utf8");
+ fs.writeFileSync(distMinFile, minified.styles, "utf8");
});
const genericFile = path.join(DIST_DIR, "y2k-alerts-generic.css");
@@ -74,20 +65,40 @@ function buildCss() {
}
}
-function buildJs() {
+async function buildJs() {
const jsBundle = readFiles(JS_DIR, JS_FILES);
const distFile = path.join(DIST_DIR, "y2k-alerts.js");
const distMinFile = path.join(DIST_DIR, "y2k-alerts.min.js");
+ const minified = await minify(jsBundle, {
+ compress: true,
+ mangle: true,
+ format: {
+ comments: false
+ }
+ });
+
+ if (!minified.code) {
+ throw new Error("JS minification failed: terser returned empty output.");
+ }
fs.writeFileSync(distFile, jsBundle, "utf8");
- fs.writeFileSync(distMinFile, minifyJs(jsBundle), "utf8");
+ fs.writeFileSync(distMinFile, minified.code, "utf8");
}
-function build() {
+async function build() {
ensureDir(DIST_DIR);
buildCss();
- buildJs();
+ await buildJs();
console.log("Built y2k-alerts assets in dist/");
}
-build();
+if (require.main === module) {
+ build().catch((error) => {
+ console.error(error);
+ process.exit(1);
+ });
+}
+
+module.exports = {
+ build
+};
diff --git a/scripts/watch.js b/scripts/watch.js
new file mode 100644
index 0000000..c581e39
--- /dev/null
+++ b/scripts/watch.js
@@ -0,0 +1,69 @@
+const path = require("path");
+const chokidar = require("chokidar");
+const browserSync = require("browser-sync").create();
+const { build } = require("./build");
+
+const ROOT_DIR = path.resolve(__dirname, "..");
+const SOURCE_GLOBS = [
+ path.join(ROOT_DIR, "css/**/*.css"),
+ path.join(ROOT_DIR, "js/**/*.js")
+];
+const EXAMPLE_GLOBS = [
+ path.join(ROOT_DIR, "examples/**/*.html")
+];
+
+let isBuilding = false;
+let hasPendingBuild = false;
+
+async function queueBuild(trigger) {
+ if (isBuilding) {
+ hasPendingBuild = true;
+ return;
+ }
+
+ isBuilding = true;
+
+ try {
+ await build();
+ console.log(`[watch] Built after ${trigger}`);
+ browserSync.reload();
+ } catch (error) {
+ console.error("[watch] Build failed");
+ console.error(error);
+ } finally {
+ isBuilding = false;
+ }
+
+ if (hasPendingBuild) {
+ hasPendingBuild = false;
+ await queueBuild("queued change");
+ }
+}
+
+async function start() {
+ await build();
+
+ browserSync.init({
+ server: ROOT_DIR,
+ startPath: "examples/generic.html",
+ notify: false,
+ open: false
+ });
+
+ chokidar.watch(SOURCE_GLOBS, { ignoreInitial: true }).on("all", (event, filePath) => {
+ const relativePath = path.relative(ROOT_DIR, filePath);
+ queueBuild(`${event}: ${relativePath}`);
+ });
+
+ chokidar.watch(EXAMPLE_GLOBS, { ignoreInitial: true }).on("all", () => {
+ browserSync.reload();
+ });
+
+ console.log("[watch] Watching css/, js/, and examples/.");
+}
+
+start().catch((error) => {
+ console.error("[watch] Failed to start");
+ console.error(error);
+ process.exit(1);
+});