@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.
Files changed (35) hide show
  1. package/README.md +55 -13
  2. package/dist/css/main.css +554 -7
  3. package/package.json +3 -1
  4. package/snippets/buttons.html +51 -0
  5. package/snippets/cards.html +132 -47
  6. package/snippets/comparison.html +26 -22
  7. package/snippets/manifest.json +180 -115
  8. package/snippets/range-group.html +125 -0
  9. package/snippets/splitter.html +44 -38
  10. package/snippets/statistics.html +31 -0
  11. package/src/js/btn-split-auto-absorb.js +327 -0
  12. package/src/js/command-palette.js +472 -0
  13. package/src/js/file-selector.js +1275 -0
  14. package/src/js/internal/logging.js +121 -0
  15. package/src/js/logic-tree-renderer.js +303 -0
  16. package/src/js/modal-dialogs.js +460 -0
  17. package/src/js/overflow.js +371 -0
  18. package/src/js/pa-stat-fit.js +184 -0
  19. package/src/js/range-group.js +663 -0
  20. package/src/js/search-autocomplete-v2.js +907 -0
  21. package/src/js/search-autocomplete.js +434 -0
  22. package/src/js/settings-panel.js +245 -0
  23. package/src/js/split-button.js +141 -0
  24. package/src/js/splitter.js +1323 -0
  25. package/src/js/toast-service.js +302 -0
  26. package/src/js/tooltips-popovers.js +275 -0
  27. package/src/js/virtual-scroll.js +143 -0
  28. package/src/js/virtual-textbox.js +803 -0
  29. package/src/scss/_core.scss +7 -0
  30. package/src/scss/core-components/_buttons.scss +44 -0
  31. package/src/scss/core-components/_cards.scss +95 -6
  32. package/src/scss/core-components/_overflow.scss +50 -0
  33. package/src/scss/core-components/_range-group.scss +474 -0
  34. package/src/scss/core-components/_statistics.scss +163 -0
  35. 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
+ })();