@modernman00/shared-js-lib 1.2.88 → 1.2.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  // createFormHandler.js
2
2
  'use strict';
3
+ import { id } from '../UtilityHtml.js';
3
4
  import { bindEvent } from '../Utility.js';
4
5
  import { recaptchaToken } from './recaptchaToken.js';
5
6
  import { loginSubmission } from './processAll.js';
@@ -17,7 +18,9 @@ export function createFormHandler({
17
18
  recaptchaAction = 'SUBMIT'
18
19
  }) {
19
20
  const handler = async (e) => {
20
- e.preventDefault();
21
+ if (e && typeof e.preventDefault === 'function') {
22
+ e.preventDefault();
23
+ }
21
24
 
22
25
  try {
23
26
  const token = await recaptchaToken(recaptchaAction);
@@ -43,5 +46,11 @@ export function createFormHandler({
43
46
 
44
47
  // 🔹 Click binding with defensive execution
45
48
  bindEvent({ id: buttonId, handler });
49
+
50
+ // 🔹 Also bind form submit event so Enter key triggers submission
51
+ const formEl = id(formId);
52
+ if (formEl) {
53
+ formEl.addEventListener('submit', handler);
54
+ }
46
55
  }
47
56
 
package/AcctMgt/login.js CHANGED
@@ -42,7 +42,7 @@ export function createAdminLoginHandler({
42
42
  redirect,
43
43
  theme,
44
44
  lengthLimit: lengthLimitArray,
45
- optionalFields: [...optionalFields],
45
+ optionalFields: [...optionalFields, buttonId, 'button', 'loginBtn', 'submit', 'token', 'redirect'],
46
46
  recaptchaAction
47
47
  });
48
48
  }
@@ -49,13 +49,25 @@ export const loginSubmission = async ({
49
49
  formData.realTimeCheckLen(lengthLimit.id, lengthLimit.max);
50
50
  }
51
51
 
52
- formData.massValidate(optionalFields, { email: 'email', password: 'password' });
52
+ // On login, validate email format and non-empty password (do not enforce registration complexity regex)
53
+ formData.massValidate(optionalFields, { email: 'email', password: 'general' });
53
54
 
54
55
  if (formData.result !== 1) {
55
56
  const firstError = document.querySelector('[id$="_error"]:not(:empty)');
56
57
  if (firstError) {
57
58
  firstError.scrollIntoView({ behavior: 'smooth', block: 'center' });
58
59
  }
60
+ const errorMsg = (formData.error && formData.error.length > 0)
61
+ ? formData.error.join('<br>')
62
+ : 'Please enter a valid email and password.';
63
+ swalError(errorMsg, true);
64
+ const notificationElement = id(`${formId}_notification`);
65
+ if (notificationElement) {
66
+ notificationElement.style.display = 'block';
67
+ notificationElement.className = 'alert alert-danger mb-3';
68
+ notificationElement.innerHTML = errorMsg;
69
+ notificationElement.scrollIntoView({ behavior: 'smooth' });
70
+ }
59
71
  return;
60
72
  }
61
73
 
@@ -80,6 +92,9 @@ export const loginSubmission = async ({
80
92
  // Guaranteed cleanup: remove button spinner & external loader
81
93
  if (targetButton) {
82
94
  clearButtonLoading(targetButton);
95
+ if (typeof window !== 'undefined' && window.ScoreTenantSpinner && typeof window.ScoreTenantSpinner.stop === 'function') {
96
+ window.ScoreTenantSpinner.stop(targetButton);
97
+ }
83
98
  }
84
99
  clearLoader('setLoader', loaderClasses);
85
100
  }
package/Alert.js CHANGED
@@ -139,7 +139,7 @@ export const swalConfirm = async (
139
139
  try {
140
140
  const result = await Swal.fire({
141
141
  title,
142
- text,
142
+ text: safeString(text),
143
143
  icon: 'warning',
144
144
  showCancelButton: true,
145
145
  confirmButtonColor: '#3085d6',
@@ -155,4 +155,118 @@ export const swalConfirm = async (
155
155
  }
156
156
  };
157
157
 
158
+ /**
159
+ * Defensive string converter that prevents [object Object] rendering
160
+ * and neutralizes XSS risks by preparing plain text for Swal.
161
+ */
162
+ export const safeString = (val, fallback = 'Notification') => {
163
+ if (val === null || val === undefined) return fallback;
164
+ if (typeof val === 'string') {
165
+ const trimmed = val.trim();
166
+ return trimmed && trimmed !== '[object Object]' ? trimmed : fallback;
167
+ }
168
+ if (typeof val === 'object') {
169
+ if (typeof val.message === 'string' && val.message.trim()) return val.message.trim();
170
+ if (typeof val.error === 'string' && val.error.trim()) return val.error.trim();
171
+ if (val.response && val.response.data) {
172
+ const data = val.response.data;
173
+ if (typeof data === 'string' && data.trim()) return data.trim();
174
+ if (typeof data.message === 'string' && data.message.trim()) return data.message.trim();
175
+ if (typeof data.error === 'string' && data.error.trim()) return data.error.trim();
176
+ }
177
+ try {
178
+ const str = JSON.stringify(val);
179
+ if (str && str !== '{}' && str.length < 300) return str;
180
+ } catch (_) {}
181
+ }
182
+ return String(val) || fallback;
183
+ };
184
+
185
+ /**
186
+ * Universal polymorphic alert dispatcher.
187
+ *
188
+ * @param {string|Error|object} message - Message or Error object
189
+ * @param {'info'|'success'|'error'|'warning'|'question'} [type='info'] - Alert type
190
+ * @param {string|null} [title=null] - Optional title
191
+ * @param {boolean} [asToast=false] - Show as toast or prominent modal
192
+ * @returns {Promise<any>}
193
+ */
194
+ export const showAlert = async (message, type = 'info', title = null, asToast = false) => {
195
+ if (!isBrowser()) return null;
196
+ const cleanMessage = safeString(message);
197
+
198
+ if (type === 'success') {
199
+ return await swalSuccess(cleanMessage, asToast, title || 'Success');
200
+ }
201
+ if (type === 'error') {
202
+ return await swalError(cleanMessage, !asToast, title || 'Error');
203
+ }
204
+ if (type === 'warning') {
205
+ return await swalWarning(cleanMessage, title || 'Warning');
206
+ }
207
+
208
+ if (asToast) {
209
+ return await swalToast(cleanMessage, type);
210
+ }
211
+
212
+ return await Swal.fire({
213
+ icon: type,
214
+ title: title || (type.charAt(0).toUpperCase() + type.slice(1)),
215
+ text: cleanMessage,
216
+ confirmButtonColor: '#1d4ed8',
217
+ confirmButtonText: 'OK'
218
+ });
219
+ };
220
+
221
+ /**
222
+ * Automatically bind SweetAlert2 globally to window.Swal and window.swal,
223
+ * and seamlessly intercept native window.alert so that any legacy, inline,
224
+ * or unbundled alert calls in any portfolio app are cleanly upgraded to
225
+ * modern, accessible SweetAlert2 dialogs without blocking the JavaScript thread.
226
+ */
227
+ export const initGlobalSwalAlerts = (options = {}) => {
228
+ if (!isBrowser()) return;
229
+
230
+ // 1. Universal window binding
231
+ window.Swal = Swal;
232
+ window.swal = Swal;
233
+ window.swalToast = swalToast;
234
+ window.swalSuccess = swalSuccess;
235
+ window.swalError = swalError;
236
+ window.swalWarning = swalWarning;
237
+ window.swalConfirm = swalConfirm;
238
+ window.showAlert = showAlert;
239
+
240
+ // 2. Intercept native window.alert
241
+ if (options.interceptNativeAlert !== false && !window._swalAlertIntercepted) {
242
+ const originalAlert = window.alert;
243
+ window._nativeAlert = originalAlert;
244
+ window._swalAlertIntercepted = true;
245
+
246
+ window.alert = function (message) {
247
+ try {
248
+ const text = safeString(message);
249
+ return Swal.fire({
250
+ text,
251
+ icon: 'info',
252
+ confirmButtonColor: '#1d4ed8',
253
+ confirmButtonText: 'OK'
254
+ });
255
+ } catch (e) {
256
+ if (typeof originalAlert === 'function') {
257
+ originalAlert(message);
258
+ } else {
259
+ console.warn('SweetAlert fallback error:', e);
260
+ }
261
+ }
262
+ };
263
+ }
264
+ };
265
+
266
+ // Automatic initialization upon module evaluation in browser environments
267
+ if (isBrowser()) {
268
+ initGlobalSwalAlerts();
269
+ }
270
+
158
271
  export { Swal };
272
+
package/ButtonLoader.js CHANGED
@@ -92,6 +92,10 @@ export const clearButtonLoading = (btn) => {
92
92
  const el = resolveButton(btn);
93
93
  if (!el) return false;
94
94
 
95
+ if (typeof window !== 'undefined' && window.ScoreTenantSpinner && typeof window.ScoreTenantSpinner.stop === 'function') {
96
+ window.ScoreTenantSpinner.stop(el);
97
+ }
98
+
95
99
  if (el.hasAttribute('data-mmlib-loading')) {
96
100
  const originalHtml = el.getAttribute('data-original-html');
97
101
  const wasDisabledBefore = el.getAttribute('data-original-disabled') === 'true';
package/FormHelper.js CHANGED
@@ -2,7 +2,14 @@
2
2
  export default class FormHelper {
3
3
  constructor(data) {
4
4
  if (!Array.isArray(data)) throw new Error('data must be an array of form elements');
5
- this.data = data;
5
+ this.data = data.filter(el => {
6
+ if (!el || !el.tagName) return false;
7
+ const tag = el.tagName.toUpperCase();
8
+ if (tag === 'BUTTON') return false;
9
+ const type = (el.type || '').toLowerCase();
10
+ if (['submit', 'reset', 'button', 'hidden'].includes(type)) return false;
11
+ return true;
12
+ });
6
13
  this.error = [];
7
14
  this.result = 0;
8
15
  }
@@ -114,7 +121,9 @@ export default class FormHelper {
114
121
  !id ||
115
122
  ['submit', 'button', 'g-recaptcha-response', 'cancel'].includes(name) ||
116
123
  ['button', 'showPassword', 'token', 'g-recaptcha-response'].includes(id) ||
117
- type === 'button' || optionalFields.includes(id)
124
+ ['button', 'submit', 'reset', 'hidden'].includes(type) ||
125
+ (input.tagName && input.tagName.toUpperCase() === 'BUTTON') ||
126
+ optionalFields.includes(id)
118
127
  ) continue;
119
128
 
120
129
  // Skip hidden elements (if the user can't see it, they can't fill it)
@@ -123,10 +132,7 @@ export default class FormHelper {
123
132
  continue;
124
133
  }
125
134
 
126
-
127
-
128
-
129
- const errorEl = this.id(`${id}_error`);
135
+ const errorEl = this.id(`${id}_error`);
130
136
  let val = value.trim();
131
137
 
132
138
  // Handle optional fields - If optional but empty → auto‑fill “Not Provided”
@@ -136,7 +142,8 @@ export default class FormHelper {
136
142
  }
137
143
 
138
144
  // Required field check
139
- if (val === '' || val === 'select' || (type === 'checkbox' && !input.checked)) {
145
+ const isUncheckedRequiredCheckbox = type === 'checkbox' && !input.checked && (input.required || !optionalFields.includes(id));
146
+ if (val === '' || val === 'select' || isUncheckedRequiredCheckbox) {
140
147
  if (errorEl) errorEl.innerHTML = `<li style="color:red;">${id} cannot be left empty.</li>`;
141
148
  this.error.push(`${id.toUpperCase()} cannot be left empty.`);
142
149
  continue;
package/README.md CHANGED
@@ -34,7 +34,10 @@ console.log(cleanInput); // Sanitized output
34
34
 
35
35
  ### Example: SweetAlert2 Notifications & Modals
36
36
  ```javascript
37
- import { swalToast, swalSuccess, swalError, swalConfirm } from '@modernman00/shared-js-lib';
37
+ import { swalToast, swalSuccess, swalError, swalConfirm, showAlert, initGlobalSwalAlerts } from '@modernman00/shared-js-lib';
38
+
39
+ // Auto-initialize global SweetAlert bindings and intercept native window.alert()
40
+ initGlobalSwalAlerts();
38
41
 
39
42
  // Fast non-blocking toast at top-end
40
43
  await swalToast('Profile updated successfully!');
@@ -43,6 +46,9 @@ await swalToast('Profile updated successfully!');
43
46
  await swalSuccess('Your application has been submitted.', false);
44
47
  await swalError('Payment processing failed. Please check your card.');
45
48
 
49
+ // Universal polymorphic alert
50
+ await showAlert('System update scheduled at midnight', 'info');
51
+
46
52
  // Interactive confirmation dialog
47
53
  const proceed = await swalConfirm('Delete Account', 'Are you sure? This cannot be undone.');
48
54
  if (proceed) {
@@ -1,3 +1,3 @@
1
1
  <?php
2
- define('APP_VERSION', 'v1.2.88');
2
+ define('APP_VERSION', 'v1.2.89');
3
3
 
package/general.js CHANGED
@@ -52,9 +52,13 @@ export const parseErrorResponse = (error) => {
52
52
 
53
53
  if (Array.isArray(error?.response?.data?.message)) {
54
54
  errorMessage = `<ul>${error.response.data.message.map(e => `<li>${e}</li>`).join('')}</ul>`;
55
- } else if (typeof error?.response?.data?.message === 'string') {
56
- errorMessage = error.response.data.message;
57
- } else if (typeof error.message === 'string') {
55
+ } else if (typeof error?.response?.data?.message === 'string' && error.response.data.message.trim()) {
56
+ errorMessage = error.response.data.message.trim();
57
+ } else if (typeof error?.response?.data?.error === 'string' && error.response.data.error.trim()) {
58
+ errorMessage = error.response.data.error.trim();
59
+ } else if (Array.isArray(error?.response?.data?.errors) && error.response.data.errors.length > 0) {
60
+ errorMessage = `<ul>${error.response.data.errors.map(e => `<li>${e}</li>`).join('')}</ul>`;
61
+ } else if (typeof error?.message === 'string') {
58
62
  errorMessage = error.message;
59
63
  }
60
64
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modernman00/shared-js-lib",
3
- "version": "1.2.88",
3
+ "version": "1.2.89",
4
4
  "description": "Reusable JS utilities for numerous js problems",
5
5
  "homepage": "https://github.com/modernman00/shared-js-lib#readme",
6
6
  "keywords": [
@@ -80,16 +80,5 @@
80
80
  "jest": "^30.0.5",
81
81
  "jest-environment-jsdom": "^30.0.5",
82
82
  "semantic-release": "^25.0.2"
83
- },
84
- "release": {
85
- "branches": [
86
- "main"
87
- ],
88
- "plugins": [
89
- "@semantic-release/npm",
90
- "@semantic-release/changelog",
91
- "@semantic-release/git",
92
- "@semantic-release/github"
93
- ]
94
83
  }
95
84
  }
@@ -163,3 +163,12 @@
163
163
  ### Changelog for v1.2.88
164
164
 
165
165
  - 🧹 Auto-commit before release _(by modernman00)_
166
+
167
+ ## v1.2.89 — 2026-10-23 16:54
168
+ ### Changelog for v1.2.89
169
+
170
+ - test: compare FormHelper.getData() by value, constructor returns a filtered copy _(by modernman00)_
171
+ - ci: remove duplicate release workflow and unused semantic-release config _(by modernman00)_
172
+ - 🧹 Auto-commit before release _(by modernman00)_
173
+ - 🔖 Release v1.2.88 _(by modernman00)_
174
+ - 🧹 Auto-commit before release _(by modernman00)_