@keenmate/pure-admin-core 2.9.0-rc03 → 2.9.0-rc05
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.
- package/README.md +55 -13
- package/dist/css/main.css +554 -7
- package/package.json +3 -1
- package/snippets/buttons.html +51 -0
- package/snippets/cards.html +132 -47
- package/snippets/comparison.html +26 -22
- package/snippets/manifest.json +180 -115
- package/snippets/range-group.html +125 -0
- package/snippets/splitter.html +44 -38
- package/snippets/statistics.html +31 -0
- package/src/js/btn-split-auto-absorb.js +327 -0
- package/src/js/command-palette.js +472 -0
- package/src/js/file-selector.js +1275 -0
- package/src/js/internal/logging.js +121 -0
- package/src/js/logic-tree-renderer.js +303 -0
- package/src/js/modal-dialogs.js +460 -0
- package/src/js/overflow.js +371 -0
- package/src/js/pa-stat-fit.js +184 -0
- package/src/js/range-group.js +663 -0
- package/src/js/search-autocomplete-v2.js +907 -0
- package/src/js/search-autocomplete.js +434 -0
- package/src/js/settings-panel.js +245 -0
- package/src/js/split-button.js +141 -0
- package/src/js/splitter.js +1323 -0
- package/src/js/toast-service.js +302 -0
- package/src/js/tooltips-popovers.js +275 -0
- package/src/js/virtual-scroll.js +143 -0
- package/src/js/virtual-textbox.js +803 -0
- package/src/scss/_core.scss +7 -0
- package/src/scss/core-components/_buttons.scss +44 -0
- package/src/scss/core-components/_cards.scss +95 -6
- package/src/scss/core-components/_overflow.scss +50 -0
- package/src/scss/core-components/_range-group.scss +474 -0
- package/src/scss/core-components/_statistics.scss +163 -0
- package/src/scss/variables/_components.scss +41 -2
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure Admin Toast Service
|
|
3
|
+
* Programmatic toast notification system
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* PureAdmin.toast.success('Operation completed!');
|
|
7
|
+
* PureAdmin.toast.error('Something went wrong', { position: 'top-center' });
|
|
8
|
+
* PureAdmin.toast.show({ variant: 'warning', title: 'Warning', message: '...', persistent: true });
|
|
9
|
+
* PureAdmin.toast.dismiss(toastId);
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
(function(window) {
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
// Namespace
|
|
16
|
+
const PureAdmin = window.PureAdmin || {};
|
|
17
|
+
window.PureAdmin = PureAdmin;
|
|
18
|
+
|
|
19
|
+
// Toast counter for unique IDs
|
|
20
|
+
let toastCounter = 0;
|
|
21
|
+
|
|
22
|
+
// Default configuration
|
|
23
|
+
const defaults = {
|
|
24
|
+
position: 'top-right',
|
|
25
|
+
duration: 5000,
|
|
26
|
+
showProgress: false,
|
|
27
|
+
persistent: false,
|
|
28
|
+
closeOnBackdrop: false
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Icon mapping for variants
|
|
32
|
+
const icons = {
|
|
33
|
+
primary: 'ℹ️',
|
|
34
|
+
success: '✓',
|
|
35
|
+
danger: '✕',
|
|
36
|
+
warning: '⚠',
|
|
37
|
+
info: 'ℹ'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Title mapping for variants
|
|
41
|
+
const titles = {
|
|
42
|
+
primary: 'Primary',
|
|
43
|
+
success: 'Success',
|
|
44
|
+
danger: 'Error',
|
|
45
|
+
warning: 'Warning',
|
|
46
|
+
info: 'Information'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Escape HTML to prevent XSS
|
|
51
|
+
*/
|
|
52
|
+
function escapeHtml(text) {
|
|
53
|
+
const div = document.createElement('div');
|
|
54
|
+
div.textContent = text;
|
|
55
|
+
return div.innerHTML;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Ensure toast container exists for given position
|
|
60
|
+
*/
|
|
61
|
+
function ensureContainer(position) {
|
|
62
|
+
const containerId = `toast-container-${position}`;
|
|
63
|
+
let container = document.getElementById(containerId);
|
|
64
|
+
|
|
65
|
+
if (!container) {
|
|
66
|
+
container = document.createElement('div');
|
|
67
|
+
container.id = containerId;
|
|
68
|
+
container.className = `pa-toast-container pa-toast-container--${position}`;
|
|
69
|
+
document.body.appendChild(container);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return container;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Create and show a toast notification
|
|
77
|
+
*/
|
|
78
|
+
function createToast(options) {
|
|
79
|
+
const {
|
|
80
|
+
variant = 'info',
|
|
81
|
+
title = titles[variant] || 'Notification',
|
|
82
|
+
message = '',
|
|
83
|
+
position = defaults.position,
|
|
84
|
+
duration = defaults.duration,
|
|
85
|
+
showProgress = defaults.showProgress,
|
|
86
|
+
persistent = defaults.persistent
|
|
87
|
+
} = options;
|
|
88
|
+
|
|
89
|
+
// Generate unique ID
|
|
90
|
+
const toastId = `pa-toast-${++toastCounter}`;
|
|
91
|
+
|
|
92
|
+
// Ensure container exists
|
|
93
|
+
const container = ensureContainer(position);
|
|
94
|
+
|
|
95
|
+
// Create toast element
|
|
96
|
+
const toast = document.createElement('div');
|
|
97
|
+
toast.className = `pa-toast pa-toast--${variant}`;
|
|
98
|
+
toast.id = toastId;
|
|
99
|
+
toast.setAttribute('role', 'alert');
|
|
100
|
+
toast.setAttribute('aria-live', 'polite');
|
|
101
|
+
|
|
102
|
+
// Build toast HTML
|
|
103
|
+
const progressHtml = showProgress && !persistent
|
|
104
|
+
? '<div class="pa-toast__progress" style="width: 100%;"></div>'
|
|
105
|
+
: '';
|
|
106
|
+
|
|
107
|
+
toast.innerHTML = `
|
|
108
|
+
<div class="pa-toast__icon">${icons[variant] || 'ℹ'}</div>
|
|
109
|
+
<div class="pa-toast__content">
|
|
110
|
+
<div class="pa-toast__title">${escapeHtml(title)}</div>
|
|
111
|
+
<div class="pa-toast__message">${escapeHtml(message)}</div>
|
|
112
|
+
</div>
|
|
113
|
+
<button class="pa-toast__close" aria-label="Close">✕</button>
|
|
114
|
+
${progressHtml}
|
|
115
|
+
`;
|
|
116
|
+
|
|
117
|
+
// Append to container
|
|
118
|
+
container.appendChild(toast);
|
|
119
|
+
|
|
120
|
+
// Attach close button handler
|
|
121
|
+
const closeBtn = toast.querySelector('.pa-toast__close');
|
|
122
|
+
closeBtn.addEventListener('click', (e) => {
|
|
123
|
+
e.stopPropagation(); // Prevent toast click handler from firing
|
|
124
|
+
dismissToast(toastId);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Make all toasts clickable to dismiss
|
|
128
|
+
toast.style.cursor = 'pointer';
|
|
129
|
+
toast.addEventListener('click', () => dismissToast(toastId));
|
|
130
|
+
|
|
131
|
+
// Show toast with animation
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
toast.classList.add('pa-toast--show');
|
|
134
|
+
}, 10);
|
|
135
|
+
|
|
136
|
+
// Progress bar animation
|
|
137
|
+
if (showProgress && !persistent) {
|
|
138
|
+
const progress = toast.querySelector('.pa-toast__progress');
|
|
139
|
+
if (progress) {
|
|
140
|
+
progress.style.transition = `width ${duration}ms linear`;
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
progress.style.width = '0%';
|
|
143
|
+
}, 50);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Auto-dismiss (only if not persistent)
|
|
148
|
+
if (!persistent) {
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
dismissToast(toastId);
|
|
151
|
+
}, duration);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return toastId;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Dismiss a toast by ID
|
|
159
|
+
*/
|
|
160
|
+
function dismissToast(toastId) {
|
|
161
|
+
const toast = document.getElementById(toastId);
|
|
162
|
+
if (!toast) return;
|
|
163
|
+
|
|
164
|
+
toast.classList.remove('pa-toast--show');
|
|
165
|
+
toast.classList.add('pa-toast--hide');
|
|
166
|
+
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
if (toast.parentNode) {
|
|
169
|
+
toast.parentNode.removeChild(toast);
|
|
170
|
+
}
|
|
171
|
+
}, 300); // Match toast transition time
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Dismiss all toasts (optionally filtered by position)
|
|
176
|
+
*/
|
|
177
|
+
function dismissAll(position = null) {
|
|
178
|
+
const selector = position
|
|
179
|
+
? `#toast-container-${position} .pa-toast`
|
|
180
|
+
: '.pa-toast';
|
|
181
|
+
|
|
182
|
+
const toasts = document.querySelectorAll(selector);
|
|
183
|
+
toasts.forEach(toast => {
|
|
184
|
+
if (toast.id) {
|
|
185
|
+
dismissToast(toast.id);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ============================================================
|
|
191
|
+
// PUBLIC API
|
|
192
|
+
// ============================================================
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Toast namespace
|
|
196
|
+
*/
|
|
197
|
+
PureAdmin.toast = {
|
|
198
|
+
/**
|
|
199
|
+
* Show a toast with full control over options
|
|
200
|
+
* @param {Object} options - Toast configuration
|
|
201
|
+
* @returns {string} - Toast ID for programmatic dismissal
|
|
202
|
+
*/
|
|
203
|
+
show: function(options = {}) {
|
|
204
|
+
return createToast(options);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Show a success toast
|
|
209
|
+
* @param {string} message - Toast message
|
|
210
|
+
* @param {Object} options - Additional options (position, duration, etc.)
|
|
211
|
+
* @returns {string} - Toast ID
|
|
212
|
+
*/
|
|
213
|
+
success: function(message, options = {}) {
|
|
214
|
+
return createToast({
|
|
215
|
+
variant: 'success',
|
|
216
|
+
title: options.title || titles.success,
|
|
217
|
+
message,
|
|
218
|
+
...options
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Show an error toast
|
|
224
|
+
* @param {string} message - Toast message
|
|
225
|
+
* @param {Object} options - Additional options
|
|
226
|
+
* @returns {string} - Toast ID
|
|
227
|
+
*/
|
|
228
|
+
error: function(message, options = {}) {
|
|
229
|
+
return createToast({
|
|
230
|
+
variant: 'danger',
|
|
231
|
+
title: options.title || titles.danger,
|
|
232
|
+
message,
|
|
233
|
+
...options
|
|
234
|
+
});
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Show a warning toast
|
|
239
|
+
* @param {string} message - Toast message
|
|
240
|
+
* @param {Object} options - Additional options
|
|
241
|
+
* @returns {string} - Toast ID
|
|
242
|
+
*/
|
|
243
|
+
warning: function(message, options = {}) {
|
|
244
|
+
return createToast({
|
|
245
|
+
variant: 'warning',
|
|
246
|
+
title: options.title || titles.warning,
|
|
247
|
+
message,
|
|
248
|
+
...options
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Show an info toast
|
|
254
|
+
* @param {string} message - Toast message
|
|
255
|
+
* @param {Object} options - Additional options
|
|
256
|
+
* @returns {string} - Toast ID
|
|
257
|
+
*/
|
|
258
|
+
info: function(message, options = {}) {
|
|
259
|
+
return createToast({
|
|
260
|
+
variant: 'info',
|
|
261
|
+
title: options.title || titles.info,
|
|
262
|
+
message,
|
|
263
|
+
...options
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Show a primary toast
|
|
269
|
+
* @param {string} message - Toast message
|
|
270
|
+
* @param {Object} options - Additional options
|
|
271
|
+
* @returns {string} - Toast ID
|
|
272
|
+
*/
|
|
273
|
+
primary: function(message, options = {}) {
|
|
274
|
+
return createToast({
|
|
275
|
+
variant: 'primary',
|
|
276
|
+
title: options.title || titles.primary,
|
|
277
|
+
message,
|
|
278
|
+
...options
|
|
279
|
+
});
|
|
280
|
+
},
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Dismiss a specific toast by ID
|
|
284
|
+
* @param {string} toastId - Toast ID returned from show/success/error/etc
|
|
285
|
+
*/
|
|
286
|
+
dismiss: function(toastId) {
|
|
287
|
+
dismissToast(toastId);
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Dismiss all toasts (optionally filtered by position)
|
|
292
|
+
* @param {string} position - Optional position filter (e.g., 'top-right')
|
|
293
|
+
*/
|
|
294
|
+
dismissAll: function(position = null) {
|
|
295
|
+
dismissAll(position);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
console.log('✅ PureAdmin Toast Service loaded');
|
|
300
|
+
console.log('Available methods:', Object.keys(PureAdmin.toast));
|
|
301
|
+
|
|
302
|
+
})(window);
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tooltips & Popovers with Floating UI
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Tooltips: Auto-positioned hover tooltips using Floating UI
|
|
6
|
+
* - Popovers: Click-triggered rich content popovers using Floating UI
|
|
7
|
+
* - Smart collision detection and auto-flipping
|
|
8
|
+
* - Multiple color variants and positions
|
|
9
|
+
*
|
|
10
|
+
* Dependencies: @floating-ui/dom
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
(function() {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
// Wait for Floating UI to load
|
|
17
|
+
if (typeof window.FloatingUIDOM === 'undefined') {
|
|
18
|
+
console.error('Floating UI is not loaded. Please include @floating-ui/dom.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const { computePosition, flip, shift, offset, arrow } = window.FloatingUIDOM;
|
|
23
|
+
|
|
24
|
+
// ====================================
|
|
25
|
+
// TOOLTIP SYSTEM (Floating UI)
|
|
26
|
+
// ====================================
|
|
27
|
+
|
|
28
|
+
let tooltipEl = null;
|
|
29
|
+
let currentTooltipTarget = null;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create tooltip element (singleton)
|
|
33
|
+
*/
|
|
34
|
+
function createTooltip() {
|
|
35
|
+
if (tooltipEl) return;
|
|
36
|
+
|
|
37
|
+
tooltipEl = document.createElement('div');
|
|
38
|
+
tooltipEl.className = 'pa-tooltip-floating';
|
|
39
|
+
tooltipEl.style.position = 'absolute';
|
|
40
|
+
tooltipEl.style.top = '0';
|
|
41
|
+
tooltipEl.style.left = '0';
|
|
42
|
+
tooltipEl.style.visibility = 'hidden';
|
|
43
|
+
tooltipEl.style.zIndex = '9000';
|
|
44
|
+
document.body.appendChild(tooltipEl);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Show tooltip with Floating UI positioning
|
|
49
|
+
*/
|
|
50
|
+
async function showTooltip(element) {
|
|
51
|
+
if (!tooltipEl) createTooltip();
|
|
52
|
+
|
|
53
|
+
currentTooltipTarget = element;
|
|
54
|
+
|
|
55
|
+
// Get tooltip text from data-tooltip attribute
|
|
56
|
+
const text = element.dataset.tooltip || element.getAttribute('aria-label') || '';
|
|
57
|
+
if (!text) return;
|
|
58
|
+
|
|
59
|
+
// Determine placement from class
|
|
60
|
+
let placement = 'top';
|
|
61
|
+
if (element.classList.contains('pa-tooltip--bottom')) placement = 'bottom';
|
|
62
|
+
else if (element.classList.contains('pa-tooltip--start')) {
|
|
63
|
+
placement = document.documentElement.dir === 'rtl' ? 'right' : 'left';
|
|
64
|
+
}
|
|
65
|
+
else if (element.classList.contains('pa-tooltip--end')) {
|
|
66
|
+
placement = document.documentElement.dir === 'rtl' ? 'left' : 'right';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Copy variant classes to floating tooltip
|
|
70
|
+
tooltipEl.className = 'pa-tooltip-floating';
|
|
71
|
+
if (element.classList.contains('pa-tooltip--primary')) tooltipEl.classList.add('pa-tooltip--primary');
|
|
72
|
+
if (element.classList.contains('pa-tooltip--success')) tooltipEl.classList.add('pa-tooltip--success');
|
|
73
|
+
if (element.classList.contains('pa-tooltip--warning')) tooltipEl.classList.add('pa-tooltip--warning');
|
|
74
|
+
if (element.classList.contains('pa-tooltip--danger')) tooltipEl.classList.add('pa-tooltip--danger');
|
|
75
|
+
if (element.classList.contains('pa-tooltip--multiline')) tooltipEl.classList.add('pa-tooltip--multiline');
|
|
76
|
+
|
|
77
|
+
// Set content and show
|
|
78
|
+
tooltipEl.textContent = text;
|
|
79
|
+
tooltipEl.style.visibility = 'visible';
|
|
80
|
+
|
|
81
|
+
// Use Floating UI to position tooltip with collision detection
|
|
82
|
+
try {
|
|
83
|
+
const { x, y } = await computePosition(element, tooltipEl, {
|
|
84
|
+
placement,
|
|
85
|
+
middleware: [
|
|
86
|
+
offset(8),
|
|
87
|
+
flip(),
|
|
88
|
+
shift({ padding: 8 })
|
|
89
|
+
]
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
Object.assign(tooltipEl.style, {
|
|
93
|
+
left: `${x}px`,
|
|
94
|
+
top: `${y}px`
|
|
95
|
+
});
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Error positioning tooltip:', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Hide tooltip
|
|
103
|
+
*/
|
|
104
|
+
function hideTooltip() {
|
|
105
|
+
if (!tooltipEl) return;
|
|
106
|
+
tooltipEl.style.visibility = 'hidden';
|
|
107
|
+
currentTooltipTarget = null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Initialize all tooltips
|
|
112
|
+
*/
|
|
113
|
+
function initTooltips() {
|
|
114
|
+
createTooltip();
|
|
115
|
+
|
|
116
|
+
document.querySelectorAll('[class*="pa-tooltip"]').forEach(element => {
|
|
117
|
+
// Skip if already initialized
|
|
118
|
+
if (element.dataset.tooltipInit) return;
|
|
119
|
+
element.dataset.tooltipInit = 'true';
|
|
120
|
+
|
|
121
|
+
// Mark as floating tooltip to disable CSS pseudo-element tooltips
|
|
122
|
+
element.classList.add('pa-tooltip--floating');
|
|
123
|
+
|
|
124
|
+
// Store tooltip text in data attribute if it's in aria-label
|
|
125
|
+
if (!element.dataset.tooltip && element.getAttribute('aria-label')) {
|
|
126
|
+
element.dataset.tooltip = element.getAttribute('aria-label');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Event listeners
|
|
130
|
+
element.addEventListener('mouseenter', () => showTooltip(element));
|
|
131
|
+
element.addEventListener('mouseleave', hideTooltip);
|
|
132
|
+
element.addEventListener('focus', () => showTooltip(element));
|
|
133
|
+
element.addEventListener('blur', hideTooltip);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ====================================
|
|
138
|
+
// POPOVER SYSTEM (Floating UI with autoUpdate)
|
|
139
|
+
// ====================================
|
|
140
|
+
|
|
141
|
+
const { autoUpdate } = window.FloatingUIDOM;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Initialize a single popover
|
|
145
|
+
*/
|
|
146
|
+
function createPopover(popoverEl) {
|
|
147
|
+
const trigger = popoverEl.querySelector('.pa-popover__trigger');
|
|
148
|
+
const content = popoverEl.querySelector('.pa-popover__content');
|
|
149
|
+
const closeBtn = popoverEl.querySelector('.pa-popover__close');
|
|
150
|
+
|
|
151
|
+
if (!trigger || !content) return;
|
|
152
|
+
|
|
153
|
+
const rawPlacement = popoverEl.dataset.placement || 'top';
|
|
154
|
+
const isRtl = document.documentElement.dir === 'rtl';
|
|
155
|
+
const placement = rawPlacement === 'start' ? (isRtl ? 'right' : 'left')
|
|
156
|
+
: rawPlacement === 'end' ? (isRtl ? 'left' : 'right')
|
|
157
|
+
: rawPlacement;
|
|
158
|
+
let cleanup = null;
|
|
159
|
+
|
|
160
|
+
// Show popover
|
|
161
|
+
function show() {
|
|
162
|
+
content.setAttribute('data-show', '');
|
|
163
|
+
|
|
164
|
+
// Update position and setup auto-update
|
|
165
|
+
cleanup = autoUpdate(trigger, content, () => {
|
|
166
|
+
computePosition(trigger, content, {
|
|
167
|
+
placement: placement,
|
|
168
|
+
middleware: [
|
|
169
|
+
offset(8),
|
|
170
|
+
flip(),
|
|
171
|
+
shift({ padding: 8 })
|
|
172
|
+
]
|
|
173
|
+
}).then(({ x, y }) => {
|
|
174
|
+
Object.assign(content.style, {
|
|
175
|
+
left: `${x}px`,
|
|
176
|
+
top: `${y}px`
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Hide popover
|
|
183
|
+
function hide() {
|
|
184
|
+
content.removeAttribute('data-show');
|
|
185
|
+
if (cleanup) {
|
|
186
|
+
cleanup();
|
|
187
|
+
cleanup = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Toggle popover
|
|
192
|
+
function toggle() {
|
|
193
|
+
if (content.hasAttribute('data-show')) {
|
|
194
|
+
hide();
|
|
195
|
+
} else {
|
|
196
|
+
// Close other popovers first
|
|
197
|
+
document.querySelectorAll('.pa-popover__content[data-show]').forEach(other => {
|
|
198
|
+
if (other !== content) {
|
|
199
|
+
other.removeAttribute('data-show');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
show();
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Event listeners
|
|
207
|
+
trigger.addEventListener('click', (e) => {
|
|
208
|
+
e.stopPropagation();
|
|
209
|
+
toggle();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
if (closeBtn) {
|
|
213
|
+
closeBtn.addEventListener('click', (e) => {
|
|
214
|
+
e.stopPropagation();
|
|
215
|
+
hide();
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Prevent closing when clicking inside content
|
|
220
|
+
content.addEventListener('click', (e) => {
|
|
221
|
+
e.stopPropagation();
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Close on outside click
|
|
225
|
+
document.addEventListener('click', (e) => {
|
|
226
|
+
if (!popoverEl.contains(e.target) && content.hasAttribute('data-show')) {
|
|
227
|
+
hide();
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Initialize all popovers
|
|
234
|
+
*/
|
|
235
|
+
function initPopovers() {
|
|
236
|
+
document.querySelectorAll('.pa-popover').forEach(popoverEl => {
|
|
237
|
+
if (!popoverEl.dataset.initialized) {
|
|
238
|
+
popoverEl.dataset.initialized = 'true';
|
|
239
|
+
createPopover(popoverEl);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ====================================
|
|
245
|
+
// INITIALIZATION
|
|
246
|
+
// ====================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Initialize everything
|
|
250
|
+
*/
|
|
251
|
+
function init() {
|
|
252
|
+
initTooltips();
|
|
253
|
+
initPopovers();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Initialize on DOM ready
|
|
257
|
+
if (document.readyState === 'loading') {
|
|
258
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
259
|
+
} else {
|
|
260
|
+
init();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Reinitialize when dynamic content is loaded
|
|
264
|
+
window.addEventListener('content-loaded', init);
|
|
265
|
+
|
|
266
|
+
// Expose public API for manual initialization
|
|
267
|
+
window.PureAdminTooltips = {
|
|
268
|
+
init,
|
|
269
|
+
initTooltips,
|
|
270
|
+
initPopovers,
|
|
271
|
+
showTooltip,
|
|
272
|
+
hideTooltip
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
})();
|