@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,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
|
+
})();
|