@modernman00/shared-js-lib 1.2.81 → 1.2.83

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.
@@ -0,0 +1,462 @@
1
+ /**
2
+ * ShareManager.js
3
+ * Universal Social Share Manager with native Web Share API and glassmorphic fallback modal.
4
+ * Part of @modernman00/shared-js-lib
5
+ */
6
+
7
+ import { CardCanvasGenerator } from './CardCanvasGenerator.js';
8
+
9
+ export class ShareManager {
10
+ /**
11
+ * Smart share dispatcher: attempts native share if supported, otherwise opens the universal modal.
12
+ * @param {Object} options
13
+ * @param {string} [options.title] - Share title
14
+ * @param {string} [options.text] - Share description / caption
15
+ * @param {string} [options.url] - URL to share (defaults to window.location.href)
16
+ * @param {Blob|File} [options.file] - Optional image file or blob to attach to native share
17
+ * @param {string} [options.imageUrl] - Preview image URL for the fallback modal
18
+ * @param {Object} [options.cardOptions] - Options to pass to CardCanvasGenerator if generating dynamically
19
+ * @param {boolean} [options.forceModal=false] - Force opening the modal even if native share is supported
20
+ * @returns {Promise<boolean>} True if shared or modal opened successfully
21
+ */
22
+ static async share(options = {}) {
23
+ const url = options.url || (typeof window !== 'undefined' ? window.location.href : '');
24
+ const title = options.title || (typeof document !== 'undefined' ? document.title : 'Share');
25
+ const text = options.text || '';
26
+
27
+ // If forced modal or running in an environment without navigator.share
28
+ if (!options.forceModal && typeof navigator !== 'undefined' && typeof navigator.share === 'function') {
29
+ try {
30
+ const shareData = { title, text, url };
31
+
32
+ // Attach file if provided and supported
33
+ if (options.file && typeof navigator.canShare === 'function') {
34
+ const testFile = options.file instanceof File
35
+ ? options.file
36
+ : new File([options.file], 'share-card.png', { type: options.file.type || 'image/png' });
37
+
38
+ if (navigator.canShare({ files: [testFile] })) {
39
+ shareData.files = [testFile];
40
+ }
41
+ }
42
+
43
+ await navigator.share(shareData);
44
+ return true;
45
+ } catch (err) {
46
+ // If user cancelled, don't force modal; return false
47
+ if (err && (err.name === 'AbortError' || err.message?.includes('abort') || err.message?.includes('cancel'))) {
48
+ return false;
49
+ }
50
+ // If native share failed for another reason, fallback to modal
51
+ console.warn('[ShareManager] Native share failed, falling back to modal:', err);
52
+ }
53
+ }
54
+
55
+ return this.openModal({ ...options, url, title, text });
56
+ }
57
+
58
+ /**
59
+ * Opens the glassmorphic Universal Share Modal
60
+ * @param {Object} options
61
+ * @returns {HTMLElement} The modal DOM element
62
+ */
63
+ static openModal(options = {}) {
64
+ if (typeof document === 'undefined') {
65
+ return null;
66
+ }
67
+
68
+ const modalId = 'shared-lib-universal-share-modal';
69
+ let modal = document.getElementById(modalId);
70
+ if (!modal) {
71
+ modal = this._createModalElement(modalId);
72
+ document.body.appendChild(modal);
73
+ }
74
+
75
+ this._populateModal(modal, options);
76
+
77
+ // Show modal with smooth fade
78
+ modal.style.display = 'flex';
79
+ requestAnimationFrame(() => {
80
+ modal.style.opacity = '1';
81
+ const dialog = modal.querySelector('.share-dialog');
82
+ if (dialog) {
83
+ dialog.style.transform = 'scale(1) translateY(0)';
84
+ }
85
+ });
86
+
87
+ return modal;
88
+ }
89
+
90
+ /**
91
+ * Closes the active share modal
92
+ */
93
+ static closeModal() {
94
+ if (typeof document === 'undefined') return;
95
+ const modal = document.getElementById('shared-lib-universal-share-modal');
96
+ if (!modal) return;
97
+
98
+ modal.style.opacity = '0';
99
+ const dialog = modal.querySelector('.share-dialog');
100
+ if (dialog) {
101
+ dialog.style.transform = 'scale(0.96) translateY(12px)';
102
+ }
103
+
104
+ setTimeout(() => {
105
+ modal.style.display = 'none';
106
+ }, 200);
107
+ }
108
+
109
+ /**
110
+ * Copies an image Blob to the user's system clipboard using ClipboardItem
111
+ * @param {Blob} imageBlob
112
+ * @returns {Promise<boolean>}
113
+ */
114
+ static async copyImageToClipboard(imageBlob) {
115
+ if (typeof navigator === 'undefined' || !navigator.clipboard || !window.ClipboardItem) {
116
+ throw new Error('ClipboardItem image copying is not supported in this browser.');
117
+ }
118
+
119
+ const item = new window.ClipboardItem({ [imageBlob.type || 'image/png']: imageBlob });
120
+ await navigator.clipboard.write([item]);
121
+ return true;
122
+ }
123
+
124
+ /**
125
+ * Downloads a Blob or DataURL as a file
126
+ * @param {Blob|string} source
127
+ * @param {string} [filename='share-card.png']
128
+ */
129
+ static downloadFile(source, filename = 'share-card.png') {
130
+ if (typeof document === 'undefined') return;
131
+ const link = document.createElement('a');
132
+ let url = '';
133
+
134
+ if (source instanceof Blob) {
135
+ url = typeof URL.createObjectURL === 'function' ? URL.createObjectURL(source) : '';
136
+ } else {
137
+ url = source;
138
+ }
139
+
140
+ link.href = url;
141
+ link.download = filename;
142
+ document.body.appendChild(link);
143
+ link.click();
144
+ document.body.removeChild(link);
145
+
146
+ if (source instanceof Blob && typeof URL.revokeObjectURL === 'function') {
147
+ setTimeout(() => URL.revokeObjectURL(url), 5000);
148
+ }
149
+ }
150
+
151
+ // --- Internal DOM & Modal Construction ---
152
+
153
+ static _createModalElement(id) {
154
+ const modal = document.createElement('div');
155
+ modal.id = id;
156
+ modal.setAttribute('role', 'dialog');
157
+ modal.setAttribute('aria-modal', 'true');
158
+ modal.setAttribute('aria-label', 'Share');
159
+
160
+ // Overlay styles
161
+ modal.style.position = 'fixed';
162
+ modal.style.top = '0';
163
+ modal.style.left = '0';
164
+ modal.style.width = '100vw';
165
+ modal.style.height = '100vh';
166
+ modal.style.backgroundColor = 'rgba(5, 8, 16, 0.75)';
167
+ modal.style.backdropFilter = 'blur(10px)';
168
+ modal.style.webkitBackdropFilter = 'blur(10px)';
169
+ modal.style.zIndex = '99999';
170
+ modal.style.display = 'none';
171
+ modal.style.alignItems = 'center';
172
+ modal.style.justifyContent = 'center';
173
+ modal.style.opacity = '0';
174
+ modal.style.transition = 'opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1)';
175
+ modal.style.padding = '16px';
176
+ modal.style.boxSizing = 'border-box';
177
+
178
+ // Click backdrop to close
179
+ modal.addEventListener('click', (e) => {
180
+ if (e.target === modal) {
181
+ this.closeModal();
182
+ }
183
+ });
184
+
185
+ // ESC key to close
186
+ document.addEventListener('keydown', (e) => {
187
+ if (e.key === 'Escape' && modal.style.display !== 'none') {
188
+ this.closeModal();
189
+ }
190
+ });
191
+
192
+ return modal;
193
+ }
194
+
195
+ static _populateModal(modal, options) {
196
+ const url = options.url || window.location.href;
197
+ const title = options.title || document.title;
198
+ const encodedUrl = encodeURIComponent(url);
199
+ const encodedTitle = encodeURIComponent(title);
200
+
201
+ // Escape user-supplied strings for safety
202
+ const safeTitle = this._escapeHtml(title);
203
+
204
+ modal.innerHTML = `
205
+ <div class="share-dialog" style="
206
+ background: #0f172a;
207
+ color: #ffffff;
208
+ border: 1px solid rgba(255, 255, 255, 0.12);
209
+ border-radius: 20px;
210
+ max-width: 580px;
211
+ width: 100%;
212
+ max-height: 90vh;
213
+ overflow-y: auto;
214
+ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7);
215
+ padding: 24px;
216
+ box-sizing: border-box;
217
+ transform: scale(0.96) translateY(12px);
218
+ transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1);
219
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
220
+ ">
221
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
222
+ <div>
223
+ <h3 style="margin: 0; font-size: 1.25rem; font-weight: 700; color: #ffffff;">Share</h3>
224
+ <p style="margin: 4px 0 0 0; font-size: 0.85rem; color: #94a3b8;">Distribute graphics & link with your network</p>
225
+ </div>
226
+ <button type="button" class="btn-close-modal" aria-label="Close" style="
227
+ background: rgba(255, 255, 255, 0.08);
228
+ border: none;
229
+ color: #94a3b8;
230
+ width: 44px;
231
+ height: 44px;
232
+ border-radius: 50%;
233
+ cursor: pointer;
234
+ font-size: 1.25rem;
235
+ display: flex;
236
+ align-items: center;
237
+ justify-content: center;
238
+ transition: all 0.15s ease;
239
+ ">&times;</button>
240
+ </div>
241
+
242
+ <div class="card-preview-area" style="
243
+ border-radius: 12px;
244
+ overflow: hidden;
245
+ margin-bottom: 20px;
246
+ background: #090d16;
247
+ border: 1px solid rgba(255, 255, 255, 0.08);
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ min-height: 180px;
252
+ ">
253
+ <!-- Live Canvas or Image Preview rendered below -->
254
+ </div>
255
+
256
+ <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin-bottom: 20px;">
257
+ <button type="button" class="btn-action-download" style="
258
+ background: rgba(255, 255, 255, 0.05);
259
+ border: 1px solid rgba(255, 255, 255, 0.1);
260
+ border-radius: 10px;
261
+ color: #ffffff;
262
+ padding: 10px 8px;
263
+ font-size: 0.8rem;
264
+ font-weight: 600;
265
+ cursor: pointer;
266
+ min-height: 44px;
267
+ display: flex;
268
+ align-items: center;
269
+ justify-content: center;
270
+ gap: 6px;
271
+ ">
272
+ <span>⬇ Download Card</span>
273
+ </button>
274
+ <button type="button" class="btn-action-copy-img" style="
275
+ background: rgba(255, 255, 255, 0.05);
276
+ border: 1px solid rgba(255, 255, 255, 0.1);
277
+ border-radius: 10px;
278
+ color: #ffffff;
279
+ padding: 10px 8px;
280
+ font-size: 0.8rem;
281
+ font-weight: 600;
282
+ cursor: pointer;
283
+ min-height: 44px;
284
+ display: flex;
285
+ align-items: center;
286
+ justify-content: center;
287
+ gap: 6px;
288
+ ">
289
+ <span>🖼 Copy Image</span>
290
+ </button>
291
+ <button type="button" class="btn-action-copy-link" style="
292
+ background: rgba(16, 185, 129, 0.15);
293
+ border: 1px solid rgba(16, 185, 129, 0.4);
294
+ border-radius: 10px;
295
+ color: #10b981;
296
+ padding: 10px 8px;
297
+ font-size: 0.8rem;
298
+ font-weight: 600;
299
+ cursor: pointer;
300
+ min-height: 44px;
301
+ display: flex;
302
+ align-items: center;
303
+ justify-content: center;
304
+ gap: 6px;
305
+ ">
306
+ <span class="copy-link-text">🔗 Copy Link</span>
307
+ </button>
308
+ </div>
309
+
310
+ <div style="font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.05em; color: #64748b; font-weight: 700; margin-bottom: 10px;">
311
+ Share directly to platforms
312
+ </div>
313
+
314
+ <div style="display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px;">
315
+ <a href="https://www.linkedin.com/sharing/share-offsite/?url=${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
316
+ background: #0a66c2; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
317
+ display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
318
+ ">LinkedIn</a>
319
+
320
+ <a href="https://twitter.com/intent/tweet?text=${encodedTitle}&url=${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
321
+ background: #000000; color: #fff; text-decoration: none; border: 1px solid rgba(255,255,255,0.2); border-radius: 10px; min-height: 48px;
322
+ display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
323
+ ">X / Twitter</a>
324
+
325
+ <a href="https://api.whatsapp.com/send?text=${encodedTitle}%20${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
326
+ background: #25d366; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
327
+ display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
328
+ ">WhatsApp</a>
329
+
330
+ <a href="https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}" target="_blank" rel="noopener noreferrer" style="
331
+ background: #1877f2; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
332
+ display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
333
+ ">Facebook</a>
334
+
335
+ <a href="viber://forward?text=${encodedTitle}%20${encodedUrl}" style="
336
+ background: #7360f2; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
337
+ display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
338
+ ">Viber</a>
339
+
340
+ <a href="sms:?&body=${encodedTitle}%20${encodedUrl}" style="
341
+ background: #334155; color: #fff; text-decoration: none; border-radius: 10px; min-height: 48px;
342
+ display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: 0.72rem; font-weight: 600;
343
+ ">SMS</a>
344
+ </div>
345
+ </div>
346
+ `;
347
+
348
+ // Hook Close Button
349
+ const closeBtn = modal.querySelector('.btn-close-modal');
350
+ if (closeBtn) {
351
+ closeBtn.onclick = () => this.closeModal();
352
+ }
353
+
354
+ // Hook Preview & Actions
355
+ const previewContainer = modal.querySelector('.card-preview-area');
356
+ let activeBlob = options.file instanceof Blob ? options.file : null;
357
+
358
+ if (options.cardOptions) {
359
+ // Live dynamic canvas rendering
360
+ const canvas = document.createElement('canvas');
361
+ canvas.style.width = '100%';
362
+ canvas.style.height = 'auto';
363
+ canvas.style.display = 'block';
364
+ previewContainer.innerHTML = '';
365
+ previewContainer.appendChild(canvas);
366
+ CardCanvasGenerator.renderToCanvas(canvas, options.cardOptions);
367
+ canvas.toBlob((b) => {
368
+ activeBlob = b;
369
+ }, 'image/png');
370
+ } else if (options.imageUrl) {
371
+ const img = document.createElement('img');
372
+ img.src = options.imageUrl;
373
+ img.alt = safeTitle;
374
+ img.style.maxWidth = '100%';
375
+ img.style.height = 'auto';
376
+ img.style.display = 'block';
377
+ previewContainer.innerHTML = '';
378
+ previewContainer.appendChild(img);
379
+ } else {
380
+ previewContainer.innerHTML = `<div style="padding: 24px; text-align: center; color: #94a3b8; font-size: 0.85rem;">Ready to share: <strong>${safeTitle}</strong></div>`;
381
+ }
382
+
383
+ // Hook Download Action
384
+ const dlBtn = modal.querySelector('.btn-action-download');
385
+ if (dlBtn) {
386
+ dlBtn.onclick = async () => {
387
+ if (activeBlob) {
388
+ this.downloadFile(activeBlob, 'share-card.png');
389
+ } else if (options.imageUrl) {
390
+ this.downloadFile(options.imageUrl, 'share-card.png');
391
+ } else if (options.cardOptions) {
392
+ const b = await CardCanvasGenerator.generateBlob(options.cardOptions);
393
+ this.downloadFile(b, 'share-card.png');
394
+ }
395
+ };
396
+ }
397
+
398
+ // Hook Copy Image Action
399
+ const copyImgBtn = modal.querySelector('.btn-action-copy-img');
400
+ if (copyImgBtn) {
401
+ copyImgBtn.onclick = async () => {
402
+ try {
403
+ let b = activeBlob;
404
+ if (!b && options.cardOptions) {
405
+ b = await CardCanvasGenerator.generateBlob(options.cardOptions);
406
+ }
407
+ if (b) {
408
+ await this.copyImageToClipboard(b);
409
+ copyImgBtn.innerHTML = '<span>✅ Copied!</span>';
410
+ setTimeout(() => {
411
+ copyImgBtn.innerHTML = '<span>🖼 Copy Image</span>';
412
+ }, 2000);
413
+ } else {
414
+ alert('Image preview not ready to copy.');
415
+ }
416
+ } catch (err) {
417
+ console.warn('[ShareManager] Copy image failed:', err);
418
+ alert('Copy image is not supported in this browser. Please use Download Card instead.');
419
+ }
420
+ };
421
+ }
422
+
423
+ // Hook Copy Link Action
424
+ const copyLinkBtn = modal.querySelector('.btn-action-copy-link');
425
+ const copyLinkText = modal.querySelector('.copy-link-text');
426
+ if (copyLinkBtn) {
427
+ copyLinkBtn.onclick = async () => {
428
+ try {
429
+ if (navigator.clipboard && navigator.clipboard.writeText) {
430
+ await navigator.clipboard.writeText(url);
431
+ } else {
432
+ const input = document.createElement('input');
433
+ input.value = url;
434
+ document.body.appendChild(input);
435
+ input.select();
436
+ document.execCommand('copy');
437
+ document.body.removeChild(input);
438
+ }
439
+
440
+ if (copyLinkText) {
441
+ copyLinkText.textContent = '✅ Copied!';
442
+ setTimeout(() => {
443
+ copyLinkText.textContent = '🔗 Copy Link';
444
+ }, 2000);
445
+ }
446
+ } catch (err) {
447
+ console.warn('[ShareManager] Copy link failed:', err);
448
+ }
449
+ };
450
+ }
451
+ }
452
+
453
+ static _escapeHtml(str) {
454
+ if (!str) return '';
455
+ return String(str)
456
+ .replace(/&/g, '&amp;')
457
+ .replace(/</g, '&lt;')
458
+ .replace(/>/g, '&gt;')
459
+ .replace(/"/g, '&quot;')
460
+ .replace(/'/g, '&#39;');
461
+ }
462
+ }
package/ShowResponse.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { id, qSel } from './UtilityHtml.js';
2
+ import { swalToast, swalSuccess, swalError } from './Alert.js';
2
3
 
3
4
  export const showError = (e) =>
4
5
  console.error(
@@ -9,6 +10,23 @@ export const msgException = (errorMessage) => {
9
10
  throw new Error(errorMessage);
10
11
  };
11
12
 
13
+ /**
14
+ * Drop-in SweetAlert-backed notification.
15
+ * If status is true or 'green', shows success toast; otherwise shows error toast/modal.
16
+ *
17
+ * @param {string} message - Message to display
18
+ * @param {boolean|string} [status=true] - true/'green' for success, false/'red' for error
19
+ * @param {boolean} [asToast=true] - true to show as sleek toast, false for modal
20
+ * @returns {Promise<any>}
21
+ */
22
+ export const swalNotification = async (message, status = true, asToast = true) => {
23
+ const isSuccess = status === true || status === 'green' || status === 'is-success' || status === 'w3-green';
24
+ if (isSuccess) {
25
+ return await swalSuccess(message, asToast);
26
+ }
27
+ return await swalError(message, !asToast);
28
+ };
29
+
12
30
  /**
13
31
  *
14
32
  * @param {*} elementId - element id
@@ -55,3 +73,105 @@ export const showResponse = (theId, message, status) => {
55
73
  responseEl.innerHTML = '';
56
74
  }, 5000); // Disappear after 5 seconds
57
75
  };
76
+
77
+
78
+ /**
79
+ * Defensive Error Unwrapping Utility
80
+ *
81
+ * Extracts a human-readable string message from any error shape
82
+ * (string, Error object, Axios/Fetch HTTP response, validation map).
83
+ * Mathematically guarantees that [object Object] is NEVER returned.
84
+ *
85
+ * @param {any} err - The error object, string, or HTTP response error
86
+ * @param {string} defaultMessage - Fallback message if extraction yields empty
87
+ * @returns {string} Clean human-readable error text
88
+ */
89
+ export const extractErrorMessage = (err, defaultMessage = "An unexpected error occurred.") => {
90
+ if (err === null || err === undefined) {
91
+ return defaultMessage;
92
+ }
93
+
94
+ if (typeof err === "string") {
95
+ const trimmed = err.trim();
96
+ return trimmed && trimmed !== "[object Object]" ? trimmed : defaultMessage;
97
+ }
98
+
99
+ // 1. Check Axios / HTTP response envelope (err.response.data)
100
+ if (err && typeof err === "object") {
101
+ const resData = err.response ? err.response.data : (err.data || null);
102
+
103
+ if (resData) {
104
+ if (typeof resData === "string") {
105
+ const trimmed = resData.trim();
106
+ if (trimmed && trimmed !== "[object Object]") return trimmed;
107
+ } else if (typeof resData === "object") {
108
+ // Check standard envelope fields: error, message, msg
109
+ if (typeof resData.error === "string" && resData.error.trim() && resData.error.trim() !== "[object Object]") {
110
+ return resData.error.trim();
111
+ }
112
+ if (typeof resData.message === "string" && resData.message.trim() && resData.message.trim() !== "[object Object]") {
113
+ return resData.message.trim();
114
+ }
115
+ if (typeof resData.msg === "string" && resData.msg.trim() && resData.msg.trim() !== "[object Object]") {
116
+ return resData.msg.trim();
117
+ }
118
+
119
+ // Check validation errors map (e.g. { errors: { email: ["Invalid"], password: ["Too short"] } })
120
+ const errorsSource = resData.errors || resData.error;
121
+ if (errorsSource && typeof errorsSource === "object") {
122
+ if (Array.isArray(errorsSource)) {
123
+ const msgs = errorsSource.map(m => typeof m === "string" ? m : (m?.message || JSON.stringify(m))).filter(Boolean);
124
+ if (msgs.length > 0) return msgs.join(", ");
125
+ } else {
126
+ const values = Object.values(errorsSource).flat().map(v => typeof v === "string" ? v : (v?.message || JSON.stringify(v))).filter(Boolean);
127
+ if (values.length > 0) return values.join(", ");
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ // 2. Check standard Error instance message
134
+ if (typeof err.message === "string" && err.message.trim() && err.message.trim() !== "[object Object]") {
135
+ return err.message.trim();
136
+ }
137
+
138
+ // 3. Check direct error/msg properties on object
139
+ if (typeof err.error === "string" && err.error.trim() && err.error.trim() !== "[object Object]") {
140
+ return err.error.trim();
141
+ }
142
+ if (typeof err.msg === "string" && err.msg.trim() && err.msg.trim() !== "[object Object]") {
143
+ return err.msg.trim();
144
+ }
145
+
146
+ // 4. Check errors map directly on object
147
+ if (err.errors && typeof err.errors === "object") {
148
+ if (Array.isArray(err.errors)) {
149
+ const msgs = err.errors.map(m => typeof m === "string" ? m : (m?.message || JSON.stringify(m))).filter(Boolean);
150
+ if (msgs.length > 0) return msgs.join(", ");
151
+ } else {
152
+ const values = Object.values(err.errors).flat().map(v => typeof v === "string" ? v : (v?.message || JSON.stringify(v))).filter(Boolean);
153
+ if (values.length > 0) return values.join(", ");
154
+ }
155
+ }
156
+
157
+ // 5. Check custom toString if not default Object toString
158
+ try {
159
+ if (typeof err.toString === "function") {
160
+ const str = err.toString();
161
+ if (str && str !== "[object Object]" && typeof str === "string") {
162
+ return str;
163
+ }
164
+ }
165
+ } catch (_) {}
166
+
167
+ // 6. Safe JSON stringify fallback
168
+ try {
169
+ const jsonStr = JSON.stringify(err);
170
+ if (jsonStr && jsonStr !== "{}" && jsonStr.length < 200) {
171
+ return jsonStr;
172
+ }
173
+ } catch (_) {}
174
+ }
175
+
176
+ return defaultMessage;
177
+ };
package/axiosWrapper.js CHANGED
@@ -4,6 +4,9 @@ import axiosRetry from 'axios-retry';
4
4
 
5
5
  axiosRetry(axios, { retries: 3 });
6
6
 
7
+ // Ensure Axios always sends cookies (like the HttpOnly auth token) with every request
8
+ axios.defaults.withCredentials = true;
9
+
7
10
  axios.interceptors.request.use(config => {
8
11
  config.headers['X-Requested-With'] = 'XMLHttpRequest';
9
12
 
@@ -1,3 +1,3 @@
1
1
  <?php
2
- define('APP_VERSION', 'v1.2.75');
2
+ define('APP_VERSION', 'v1.2.83');
3
3
 
package/index.js CHANGED
@@ -1,7 +1,17 @@
1
+ export * from './OfflineQueue.js';
2
+ export * from './PwaManager.js';
3
+ export * from './PwaInstall.js';
1
4
  export * from './Http.js';
2
5
  export * from './UtilityHtml.js';
3
6
  export * from './Utility.js';
4
7
  export * from './Loader.js';
8
+ export * from './Alert.js';
9
+ export * from './ButtonLoader.js';
10
+ export * from './FormLock.js';
11
+ export * from './NetworkGuard.js';
12
+ export * from './InlineValidation.js';
13
+ export * from './Clipboard.js';
14
+ export * from './AuthInterceptor.js';
5
15
  export * from './Cookie.js';
6
16
  export * from './FormHelper.js'
7
17
  export * from './ShowResponse.js';
@@ -15,4 +25,7 @@ export * from './AcctMgt/processAll.js';
15
25
  export * from './AcctMgt/update.js';
16
26
  export * from './AcctMgt/registerHandler.js';
17
27
  export * from './AcctMgt/recaptchaToken.js';
18
- export * from './general.js';
28
+ export * from './AcctMgt/AdminAuthForm.js';
29
+ export * from './general.js';
30
+ export * from './CardCanvasGenerator.js';
31
+ export * from './ShareManager.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modernman00/shared-js-lib",
3
- "version": "1.2.81",
3
+ "version": "1.2.83",
4
4
  "description": "Reusable JS utilities for numerous js problems",
5
5
  "homepage": "https://github.com/modernman00/shared-js-lib#readme",
6
6
  "keywords": [
@@ -25,7 +25,8 @@
25
25
  "dependencies": {
26
26
  "axios": "^1.13.2",
27
27
  "axios-retry": "^4.5.0",
28
- "settings": "^0.1.1"
28
+ "settings": "^0.1.1",
29
+ "sweetalert2": "^11.26.25"
29
30
  },
30
31
  "publishConfig": {
31
32
  "access": "public"
@@ -40,6 +41,13 @@
40
41
  "FormHelper.js",
41
42
  "Http.js",
42
43
  "Loader.js",
44
+ "Alert.js",
45
+ "ButtonLoader.js",
46
+ "FormLock.js",
47
+ "NetworkGuard.js",
48
+ "InlineValidation.js",
49
+ "Clipboard.js",
50
+ "AuthInterceptor.js",
43
51
  "ShowResponse.js",
44
52
  "Utility.js",
45
53
  "UtilityHtml.js",
@@ -48,11 +56,18 @@
48
56
  "general.js",
49
57
  "config/",
50
58
  "README.md",
51
- "release-history.md"
59
+ "release-history.md",
60
+ "OfflineQueue.js",
61
+ "PwaManager.js",
62
+ "PwaInstall.js",
63
+ "CardCanvasGenerator.js",
64
+ "ShareManager.js",
65
+ "templates/"
52
66
  ],
53
67
  "devDependencies": {
54
68
  "@babel/core": "^7.28.3",
55
69
  "@babel/preset-env": "^7.28.3",
70
+ "@babel/runtime": "^8.0.5",
56
71
  "@semantic-release/changelog": "^6.0.3",
57
72
  "@semantic-release/git": "^10.0.1",
58
73
  "@semantic-release/github": "^11.0.4",