@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,472 @@
1
+ /**
2
+ * Command Palette - macOS Spotlight-style search
3
+ * Keyboard shortcut: Ctrl+K / Cmd+K
4
+ * Context switching: /p (products), /o (orders), /u (users), /i (invoices)
5
+ * Navigation: ↑↓ (items), ←→ (pages), Enter (select), Esc (close)
6
+ */
7
+
8
+ (function() {
9
+ 'use strict';
10
+
11
+ // Wait for DOM to be ready
12
+ if (document.readyState === 'loading') {
13
+ document.addEventListener('DOMContentLoaded', init);
14
+ } else {
15
+ init();
16
+ }
17
+
18
+ function init() {
19
+ // DOM elements
20
+ const palette = document.getElementById('commandPalette');
21
+ const backdrop = document.getElementById('commandPaletteBackdrop');
22
+ const input = document.getElementById('commandPaletteInput');
23
+ const contextLabel = document.getElementById('commandPaletteContext');
24
+ const results = document.getElementById('commandPaletteResults');
25
+
26
+ // Check if elements exist
27
+ if (!palette || !backdrop || !input || !contextLabel || !results) {
28
+ console.warn('Command palette elements not found');
29
+ return;
30
+ }
31
+
32
+ // State
33
+ let isOpen = false;
34
+ let currentContext = null;
35
+ let currentResults = [];
36
+ let activeIndex = -1;
37
+ let currentPage = 1;
38
+ let totalPages = 1;
39
+ const resultsPerPage = 8;
40
+
41
+ // Context definitions
42
+ const contexts = {
43
+ p: { name: 'Products', label: 'Searching in Products' },
44
+ o: { name: 'Orders', label: 'Searching in Orders' },
45
+ u: { name: 'Users', label: 'Searching in Users' },
46
+ i: { name: 'Invoices', label: 'Searching in Invoices' }
47
+ };
48
+
49
+ // Dummy data
50
+ const dummyData = {
51
+ products: [
52
+ { id: 1, title: 'MacBook Pro 16"', meta: 'SKU: MBP-16-001 • $2,499.00', icon: '💻', badge: 'In Stock' },
53
+ { id: 2, title: 'iPhone 15 Pro', meta: 'SKU: IP15P-256 • $999.00', icon: '📱', badge: 'New' },
54
+ { id: 3, title: 'AirPods Pro', meta: 'SKU: APP-GEN2 • $249.00', icon: '🎧', badge: 'Popular' },
55
+ { id: 4, title: 'iPad Air', meta: 'SKU: IPAD-AIR-5 • $599.00', icon: '📱', badge: 'In Stock' },
56
+ { id: 5, title: 'Apple Watch Ultra', meta: 'SKU: AW-ULTRA • $799.00', icon: '⌚', badge: 'Limited' },
57
+ { id: 6, title: 'Magic Keyboard', meta: 'SKU: MK-US • $99.00', icon: '⌨️', badge: 'In Stock' },
58
+ { id: 7, title: 'Magic Mouse', meta: 'SKU: MM-BLK • $79.00', icon: '🖱️', badge: 'In Stock' },
59
+ { id: 8, title: 'HomePod mini', meta: 'SKU: HPM-WHT • $99.00', icon: '🔊', badge: 'New' },
60
+ { id: 9, title: 'Apple TV 4K', meta: 'SKU: ATV-4K-128 • $149.00', icon: '📺', badge: 'In Stock' },
61
+ { id: 10, title: 'AirTag 4 Pack', meta: 'SKU: AT-4PK • $99.00', icon: '📍', badge: 'Popular' },
62
+ { id: 11, title: 'Studio Display', meta: 'SKU: SD-27-STD • $1,599.00', icon: '🖥️', badge: 'Premium' },
63
+ { id: 12, title: 'Mac Studio', meta: 'SKU: MS-M2-MAX • $1,999.00', icon: '💻', badge: 'Pro' }
64
+ ],
65
+ orders: [
66
+ { id: 1001, title: 'Order #1001', meta: 'John Doe • $1,234.56 • 2 items', icon: '📦', badge: 'Shipped' },
67
+ { id: 1002, title: 'Order #1002', meta: 'Jane Smith • $567.89 • 1 item', icon: '📦', badge: 'Processing' },
68
+ { id: 1003, title: 'Order #1003', meta: 'Bob Johnson • $2,345.67 • 5 items', icon: '📦', badge: 'Delivered' },
69
+ { id: 1004, title: 'Order #1004', meta: 'Alice Williams • $890.12 • 3 items', icon: '📦', badge: 'Pending' },
70
+ { id: 1005, title: 'Order #1005', meta: 'Charlie Brown • $456.78 • 2 items', icon: '📦', badge: 'Shipped' },
71
+ { id: 1006, title: 'Order #1006', meta: 'Diana Prince • $3,456.89 • 7 items', icon: '📦', badge: 'Processing' },
72
+ { id: 1007, title: 'Order #1007', meta: 'Eve Davis • $123.45 • 1 item', icon: '📦', badge: 'Cancelled' },
73
+ { id: 1008, title: 'Order #1008', meta: 'Frank Miller • $678.90 • 4 items', icon: '📦', badge: 'Delivered' },
74
+ { id: 1009, title: 'Order #1009', meta: 'Grace Lee • $1,890.23 • 6 items', icon: '📦', badge: 'Shipped' },
75
+ { id: 1010, title: 'Order #1010', meta: 'Henry Ford • $234.56 • 2 items', icon: '📦', badge: 'Pending' }
76
+ ],
77
+ users: [
78
+ { id: 1, title: 'John Doe', meta: 'john.doe@example.com • Customer', icon: '👤', badge: 'Active' },
79
+ { id: 2, title: 'Jane Smith', meta: 'jane.smith@example.com • Admin', icon: '👤', badge: 'Active' },
80
+ { id: 3, title: 'Bob Johnson', meta: 'bob.johnson@example.com • Customer', icon: '👤', badge: 'Inactive' },
81
+ { id: 4, title: 'Alice Williams', meta: 'alice.w@example.com • Manager', icon: '👤', badge: 'Active' },
82
+ { id: 5, title: 'Charlie Brown', meta: 'charlie.b@example.com • Customer', icon: '👤', badge: 'Active' },
83
+ { id: 6, title: 'Diana Prince', meta: 'diana.p@example.com • VIP', icon: '👤', badge: 'Premium' },
84
+ { id: 7, title: 'Eve Davis', meta: 'eve.davis@example.com • Customer', icon: '👤', badge: 'Active' },
85
+ { id: 8, title: 'Frank Miller', meta: 'frank.m@example.com • Support', icon: '👤', badge: 'Active' },
86
+ { id: 9, title: 'Grace Lee', meta: 'grace.lee@example.com • Customer', icon: '👤', badge: 'New' },
87
+ { id: 10, title: 'Henry Ford', meta: 'henry.f@example.com • Customer', icon: '👤', badge: 'Active' }
88
+ ],
89
+ invoices: [
90
+ { id: 501, title: 'Invoice #INV-501', meta: 'Order #1001 • $1,234.56 • Due in 5 days', icon: '📄', badge: 'Unpaid' },
91
+ { id: 502, title: 'Invoice #INV-502', meta: 'Order #1002 • $567.89 • Paid 2 days ago', icon: '📄', badge: 'Paid' },
92
+ { id: 503, title: 'Invoice #INV-503', meta: 'Order #1003 • $2,345.67 • Paid 1 week ago', icon: '📄', badge: 'Paid' },
93
+ { id: 504, title: 'Invoice #INV-504', meta: 'Order #1004 • $890.12 • Due today', icon: '📄', badge: 'Overdue' },
94
+ { id: 505, title: 'Invoice #INV-505', meta: 'Order #1005 • $456.78 • Due in 3 days', icon: '📄', badge: 'Unpaid' },
95
+ { id: 506, title: 'Invoice #INV-506', meta: 'Order #1006 • $3,456.89 • Due in 7 days', icon: '📄', badge: 'Unpaid' },
96
+ { id: 507, title: 'Invoice #INV-507', meta: 'Order #1007 • $123.45 • Cancelled', icon: '📄', badge: 'Void' },
97
+ { id: 508, title: 'Invoice #INV-508', meta: 'Order #1008 • $678.90 • Paid 3 days ago', icon: '📄', badge: 'Paid' },
98
+ { id: 509, title: 'Invoice #INV-509', meta: 'Order #1009 • $1,890.23 • Due in 10 days', icon: '📄', badge: 'Unpaid' },
99
+ { id: 510, title: 'Invoice #INV-510', meta: 'Order #1010 • $234.56 • Due in 2 days', icon: '📄', badge: 'Unpaid' }
100
+ ]
101
+ };
102
+
103
+ /**
104
+ * Open command palette
105
+ */
106
+ function openPalette() {
107
+ isOpen = true;
108
+ palette.classList.add('pa-command-palette--active');
109
+ input.focus();
110
+ document.body.style.overflow = 'hidden';
111
+ }
112
+
113
+ /**
114
+ * Close command palette
115
+ */
116
+ function closePalette() {
117
+ isOpen = false;
118
+ palette.classList.remove('pa-command-palette--active');
119
+ input.value = '';
120
+ contextLabel.textContent = '';
121
+ contextLabel.classList.remove('pa-command-palette__context--visible');
122
+ currentContext = null;
123
+ currentResults = [];
124
+ activeIndex = -1;
125
+ currentPage = 1;
126
+ document.body.style.overflow = '';
127
+ renderEmptyState();
128
+ }
129
+
130
+ /**
131
+ * Detect context from search query
132
+ */
133
+ function detectContext(query) {
134
+ const match = query.match(/^\/([poui])\s*/);
135
+ if (match) {
136
+ const contextKey = match[1];
137
+ return contexts[contextKey] || null;
138
+ }
139
+ return null;
140
+ }
141
+
142
+ /**
143
+ * Get search query without context prefix
144
+ */
145
+ function getSearchQuery(query) {
146
+ return query.replace(/^\/[poui]\s*/, '').trim();
147
+ }
148
+
149
+ /**
150
+ * Filter results based on search query
151
+ */
152
+ function filterResults(data, query) {
153
+ if (!query) return data;
154
+
155
+ const lowerQuery = query.toLowerCase();
156
+ return data.filter(item => {
157
+ return item.title.toLowerCase().includes(lowerQuery) ||
158
+ item.meta.toLowerCase().includes(lowerQuery);
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Highlight matching text
164
+ */
165
+ function highlightText(text, query) {
166
+ if (!query) return text;
167
+
168
+ const regex = new RegExp(`(${query})`, 'gi');
169
+ return text.replace(regex, '<mark>$1</mark>');
170
+ }
171
+
172
+ /**
173
+ * Render empty state
174
+ */
175
+ function renderEmptyState() {
176
+ results.innerHTML = '<div class="pa-command-palette__empty">Type to search or use /p for products, /o for orders, /u for users, /i for invoices</div>';
177
+ }
178
+
179
+ /**
180
+ * Show loader (inline in results area)
181
+ */
182
+ function showLoader() {
183
+ // Add loading class to results container
184
+ results.classList.add('pa-command-palette__results--loading');
185
+
186
+ // If there are no existing results, show loader message
187
+ if (currentResults.length === 0) {
188
+ results.innerHTML = `
189
+ <div class="pa-command-palette__loader">
190
+ <div class="pa-spinner pa-spinner--sm pa-spinner--primary"></div>
191
+ <span>Searching...</span>
192
+ </div>
193
+ `;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Hide loader
199
+ */
200
+ function hideLoader() {
201
+ results.classList.remove('pa-command-palette__results--loading');
202
+ }
203
+
204
+ /**
205
+ * Render results
206
+ */
207
+ function renderResults(items, query, page = 1) {
208
+ const startIndex = (page - 1) * resultsPerPage;
209
+ const endIndex = startIndex + resultsPerPage;
210
+ const pageItems = items.slice(startIndex, endIndex);
211
+
212
+ totalPages = Math.ceil(items.length / resultsPerPage);
213
+
214
+ if (pageItems.length === 0) {
215
+ results.innerHTML = '<div class="pa-command-palette__empty">No results found</div>';
216
+ return;
217
+ }
218
+
219
+ let html = '';
220
+
221
+ pageItems.forEach((item, index) => {
222
+ const globalIndex = startIndex + index;
223
+ const isActive = globalIndex === activeIndex;
224
+ const highlightedTitle = highlightText(item.title, query);
225
+
226
+ html += `
227
+ <div class="pa-command-palette__item ${isActive ? 'pa-command-palette__item--active' : ''}" data-index="${globalIndex}">
228
+ <div class="pa-command-palette__item-icon">${item.icon}</div>
229
+ <div class="pa-command-palette__item-content">
230
+ <div class="pa-command-palette__item-title">${highlightedTitle}</div>
231
+ <div class="pa-command-palette__item-meta">${item.meta}</div>
232
+ </div>
233
+ <div class="pa-command-palette__item-badge">${item.badge}</div>
234
+ </div>
235
+ `;
236
+ });
237
+
238
+ // Add pagination indicator if multiple pages
239
+ if (totalPages > 1) {
240
+ html += `
241
+ <div class="pa-command-palette__pagination">
242
+ Page ${page} of ${totalPages} • ${items.length} results
243
+ </div>
244
+ `;
245
+ }
246
+
247
+ results.innerHTML = html;
248
+
249
+ // Add click handlers to items
250
+ document.querySelectorAll('.pa-command-palette__item').forEach(item => {
251
+ item.addEventListener('click', () => {
252
+ const index = parseInt(item.dataset.index);
253
+ selectItem(currentResults[index]);
254
+ });
255
+ });
256
+ }
257
+
258
+ /**
259
+ * Perform search
260
+ */
261
+ function performSearch(query) {
262
+ const context = detectContext(query);
263
+ const searchQuery = getSearchQuery(query);
264
+
265
+ // Update context label
266
+ if (context) {
267
+ currentContext = context;
268
+ contextLabel.textContent = context.label;
269
+ contextLabel.classList.add('pa-command-palette__context--visible');
270
+ } else {
271
+ currentContext = null;
272
+ contextLabel.classList.remove('pa-command-palette__context--visible');
273
+ }
274
+
275
+ // If no query, show empty state
276
+ if (!searchQuery && !context) {
277
+ renderEmptyState();
278
+ currentResults = [];
279
+ activeIndex = -1;
280
+ hideLoader();
281
+ return;
282
+ }
283
+
284
+ // Show loader
285
+ showLoader();
286
+
287
+ // Simulate search delay
288
+ setTimeout(() => {
289
+ // Get data based on context
290
+ let data = [];
291
+ if (context) {
292
+ switch (context.name) {
293
+ case 'Products':
294
+ data = dummyData.products;
295
+ break;
296
+ case 'Orders':
297
+ data = dummyData.orders;
298
+ break;
299
+ case 'Users':
300
+ data = dummyData.users;
301
+ break;
302
+ case 'Invoices':
303
+ data = dummyData.invoices;
304
+ break;
305
+ }
306
+ } else {
307
+ // Search all categories
308
+ data = [
309
+ ...dummyData.products,
310
+ ...dummyData.orders,
311
+ ...dummyData.users,
312
+ ...dummyData.invoices
313
+ ];
314
+ }
315
+
316
+ // Filter results
317
+ currentResults = filterResults(data, searchQuery);
318
+ activeIndex = currentResults.length > 0 ? 0 : -1;
319
+ currentPage = 1;
320
+
321
+ // Hide loader and render results
322
+ hideLoader();
323
+ renderResults(currentResults, searchQuery, currentPage);
324
+ }, 300);
325
+ }
326
+
327
+ /**
328
+ * Navigate to previous item
329
+ */
330
+ function navigatePrevious() {
331
+ if (currentResults.length === 0) return;
332
+
333
+ const startIndex = (currentPage - 1) * resultsPerPage;
334
+
335
+ if (activeIndex > startIndex) {
336
+ activeIndex--;
337
+ } else if (currentPage > 1) {
338
+ // Go to previous page, last item
339
+ currentPage--;
340
+ activeIndex = Math.min((currentPage * resultsPerPage) - 1, currentResults.length - 1);
341
+ } else {
342
+ // Wrap to last page, last item
343
+ currentPage = totalPages;
344
+ activeIndex = currentResults.length - 1;
345
+ }
346
+
347
+ renderResults(currentResults, getSearchQuery(input.value), currentPage);
348
+ }
349
+
350
+ /**
351
+ * Navigate to next item
352
+ */
353
+ function navigateNext() {
354
+ if (currentResults.length === 0) return;
355
+
356
+ const endIndex = Math.min(currentPage * resultsPerPage, currentResults.length);
357
+
358
+ if (activeIndex < endIndex - 1) {
359
+ activeIndex++;
360
+ } else if (currentPage < totalPages) {
361
+ // Go to next page, first item
362
+ currentPage++;
363
+ activeIndex = (currentPage - 1) * resultsPerPage;
364
+ } else {
365
+ // Wrap to first page, first item
366
+ currentPage = 1;
367
+ activeIndex = 0;
368
+ }
369
+
370
+ renderResults(currentResults, getSearchQuery(input.value), currentPage);
371
+ }
372
+
373
+ /**
374
+ * Navigate to previous page
375
+ */
376
+ function navigatePreviousPage() {
377
+ if (currentResults.length === 0 || totalPages <= 1) return;
378
+
379
+ currentPage = currentPage > 1 ? currentPage - 1 : totalPages;
380
+ activeIndex = (currentPage - 1) * resultsPerPage;
381
+
382
+ renderResults(currentResults, getSearchQuery(input.value), currentPage);
383
+ }
384
+
385
+ /**
386
+ * Navigate to next page
387
+ */
388
+ function navigateNextPage() {
389
+ if (currentResults.length === 0 || totalPages <= 1) return;
390
+
391
+ currentPage = currentPage < totalPages ? currentPage + 1 : 1;
392
+ activeIndex = (currentPage - 1) * resultsPerPage;
393
+
394
+ renderResults(currentResults, getSearchQuery(input.value), currentPage);
395
+ }
396
+
397
+ /**
398
+ * Select item
399
+ */
400
+ function selectItem(item) {
401
+ console.log('Selected:', item);
402
+ // In real app, navigate to item or trigger action
403
+ alert(`Selected: ${item.title}\n${item.meta}`);
404
+ closePalette();
405
+ }
406
+
407
+ /**
408
+ * Global keyboard shortcut (Ctrl+K / Cmd+K)
409
+ */
410
+ document.addEventListener('keydown', (e) => {
411
+ // Ctrl+K or Cmd+K
412
+ if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
413
+ e.preventDefault();
414
+ if (isOpen) {
415
+ closePalette();
416
+ } else {
417
+ openPalette();
418
+ }
419
+ }
420
+ });
421
+
422
+ /**
423
+ * Backdrop click to close
424
+ */
425
+ backdrop.addEventListener('click', closePalette);
426
+
427
+ /**
428
+ * Input events
429
+ */
430
+ input.addEventListener('input', (e) => {
431
+ performSearch(e.target.value);
432
+ });
433
+
434
+ input.addEventListener('keydown', (e) => {
435
+ switch (e.key) {
436
+ case 'Escape':
437
+ e.preventDefault();
438
+ closePalette();
439
+ break;
440
+
441
+ case 'ArrowUp':
442
+ e.preventDefault();
443
+ navigatePrevious();
444
+ break;
445
+
446
+ case 'ArrowDown':
447
+ e.preventDefault();
448
+ navigateNext();
449
+ break;
450
+
451
+ case 'ArrowLeft':
452
+ e.preventDefault();
453
+ navigatePreviousPage();
454
+ break;
455
+
456
+ case 'ArrowRight':
457
+ e.preventDefault();
458
+ navigateNextPage();
459
+ break;
460
+
461
+ case 'Enter':
462
+ e.preventDefault();
463
+ if (activeIndex >= 0 && currentResults[activeIndex]) {
464
+ selectItem(currentResults[activeIndex]);
465
+ }
466
+ break;
467
+ }
468
+ });
469
+
470
+ } // end init()
471
+
472
+ })();