@nice2dev/icons 1.3.14 → 1.3.16
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/CHANGELOG.md +15 -0
- package/dist/cjs/NiceDesktopIconExporter.js +688 -599
- package/dist/cjs/NiceFaviconGenerator.js +603 -417
- package/dist/cjs/NiceIconPicker.js +788 -508
- package/dist/cjs/NiceIconPreview.js +246 -188
- package/dist/cjs/NiceIconSetCreator.js +683 -552
- package/dist/cjs/NiceIconStacker.js +547 -171
- package/dist/cjs/NiceIconValue.js +27 -35
- package/dist/cjs/NtdIconBadge.js +384 -360
- package/dist/cjs/NtdIconMorph.js +419 -354
- package/dist/cjs/NtdStateMorph.js +150 -147
- package/dist/cjs/advancedIconSearch.js +373 -510
- package/dist/cjs/animationControls.js +518 -642
- package/dist/cjs/client.js +12 -15
- package/dist/cjs/createIcon.js +349 -251
- package/dist/cjs/design-tokens.js +324 -390
- package/dist/cjs/figmaExport.js +162 -257
- package/dist/cjs/figmaSync.js +234 -316
- package/dist/cjs/headless-ui.js +240 -368
- package/dist/cjs/iconAnalytics.js +337 -393
- package/dist/cjs/iconHtml.js +64 -86
- package/dist/cjs/iconMetadata.js +392 -472
- package/dist/cjs/iconParts.js +120 -90
- package/dist/cjs/iconStack.js +136 -155
- package/dist/cjs/iconStyles.js +1609 -504
- package/dist/cjs/iconValue.js +359 -430
- package/dist/cjs/icons/accessibility.js +1119 -134
- package/dist/cjs/icons/actions.js +1290 -330
- package/dist/cjs/icons/ai.js +1424 -144
- package/dist/cjs/icons/brand.js +1344 -172
- package/dist/cjs/icons/business.js +903 -193
- package/dist/cjs/icons/devtech.js +1656 -227
- package/dist/cjs/icons/ecommerce.js +802 -111
- package/dist/cjs/icons/education.js +1345 -199
- package/dist/cjs/icons/files.js +1597 -328
- package/dist/cjs/icons/fintech.js +793 -104
- package/dist/cjs/icons/food.js +1507 -153
- package/dist/cjs/icons/fun.js +786 -109
- package/dist/cjs/icons/gaming.js +916 -170
- package/dist/cjs/icons/health.js +623 -112
- package/dist/cjs/icons/index.js +34 -35
- package/dist/cjs/icons/input.js +282 -83
- package/dist/cjs/icons/legal.js +1065 -122
- package/dist/cjs/icons/media.js +1061 -267
- package/dist/cjs/icons/navigation.js +387 -122
- package/dist/cjs/icons/omniverk.js +747 -90
- package/dist/cjs/icons/realestate.js +1520 -193
- package/dist/cjs/icons/saas.js +867 -98
- package/dist/cjs/icons/science.js +1465 -165
- package/dist/cjs/icons/shapes.js +1316 -308
- package/dist/cjs/icons/smarthome.js +786 -90
- package/dist/cjs/icons/social.js +591 -59
- package/dist/cjs/icons/sports.js +1225 -133
- package/dist/cjs/icons/sustainability.js +1211 -139
- package/dist/cjs/icons/travel.js +1470 -148
- package/dist/cjs/icons/ui.js +1508 -315
- package/dist/cjs/icons/weather.js +1160 -173
- package/dist/cjs/index.js +566 -566
- package/dist/cjs/lottieIntegration.js +261 -266
- package/dist/cjs/nicetodev-icons.css +1 -1
- package/dist/cjs/particleEffects.js +270 -251
- package/dist/cjs/resolver.js +229 -284
- package/dist/cjs/rsc.js +175 -193
- package/dist/cjs/sharpen.js +73 -130
- package/dist/cjs/smilAnimations.js +371 -165
- package/dist/cjs/stacking.js +87 -122
- package/dist/cjs/tailwind-plugin.js +280 -320
- package/dist/cjs/types.js +46 -72
- package/dist/cjs/utilities.js +163 -324
- package/dist/cjs/variantProps.js +12 -52
- package/dist/esm/NiceDesktopIconExporter.js +671 -598
- package/dist/esm/NiceFaviconGenerator.js +583 -416
- package/dist/esm/NiceIconPicker.js +789 -507
- package/dist/esm/NiceIconPreview.js +247 -187
- package/dist/esm/NiceIconSetCreator.js +685 -551
- package/dist/esm/NiceIconStacker.js +549 -170
- package/dist/esm/NiceIconValue.js +29 -34
- package/dist/esm/NtdIconBadge.js +388 -359
- package/dist/esm/NtdIconMorph.js +423 -353
- package/dist/esm/NtdStateMorph.js +152 -146
- package/dist/esm/advancedIconSearch.js +391 -509
- package/dist/esm/animationControls.js +527 -641
- package/dist/esm/client.js +48 -10
- package/dist/esm/createIcon.js +350 -250
- package/dist/esm/design-tokens.js +334 -387
- package/dist/esm/figmaExport.js +170 -256
- package/dist/esm/figmaSync.js +237 -315
- package/dist/esm/headless-ui.js +254 -365
- package/dist/esm/iconAnalytics.js +342 -392
- package/dist/esm/iconHtml.js +67 -85
- package/dist/esm/iconMetadata.js +404 -471
- package/dist/esm/iconParts.js +125 -89
- package/dist/esm/iconStack.js +145 -154
- package/dist/esm/iconStyles.js +1620 -503
- package/dist/esm/iconValue.js +361 -429
- package/dist/esm/icons/accessibility.js +1133 -133
- package/dist/esm/icons/actions.js +1320 -329
- package/dist/esm/icons/ai.js +1441 -143
- package/dist/esm/icons/brand.js +1351 -171
- package/dist/esm/icons/business.js +931 -201
- package/dist/esm/icons/devtech.js +1687 -226
- package/dist/esm/icons/ecommerce.js +816 -110
- package/dist/esm/icons/education.js +1364 -198
- package/dist/esm/icons/files.js +1628 -327
- package/dist/esm/icons/fintech.js +807 -103
- package/dist/esm/icons/food.js +1524 -152
- package/dist/esm/icons/fun.js +801 -108
- package/dist/esm/icons/gaming.js +933 -169
- package/dist/esm/icons/health.js +636 -111
- package/dist/esm/icons/index.js +596 -32
- package/dist/esm/icons/input.js +326 -82
- package/dist/esm/icons/legal.js +1079 -121
- package/dist/esm/icons/media.js +1086 -266
- package/dist/esm/icons/navigation.js +401 -121
- package/dist/esm/icons/omniverk.js +759 -89
- package/dist/esm/icons/realestate.js +1544 -198
- package/dist/esm/icons/saas.js +880 -97
- package/dist/esm/icons/science.js +1482 -164
- package/dist/esm/icons/shapes.js +1343 -307
- package/dist/esm/icons/smarthome.js +799 -89
- package/dist/esm/icons/social.js +599 -58
- package/dist/esm/icons/sports.js +1240 -132
- package/dist/esm/icons/sustainability.js +1226 -138
- package/dist/esm/icons/travel.js +1486 -147
- package/dist/esm/icons/ui.js +1540 -314
- package/dist/esm/icons/weather.js +1181 -172
- package/dist/esm/index.js +756 -58
- package/dist/esm/lottieIntegration.js +264 -265
- package/dist/esm/nicetodev-icons.css +1 -1
- package/dist/esm/particleEffects.js +271 -250
- package/dist/esm/resolver.js +231 -283
- package/dist/esm/rsc.js +182 -192
- package/dist/esm/sharpen.js +78 -129
- package/dist/esm/smilAnimations.js +376 -164
- package/dist/esm/stacking.js +99 -121
- package/dist/esm/tailwind-plugin.js +282 -319
- package/dist/esm/types.js +50 -71
- package/dist/esm/utilities.js +177 -321
- package/dist/esm/variantProps.js +23 -52
- package/dist/types/icons/index.d.ts +1 -1
- package/dist/types/icons/science.d.ts +9 -0
- package/package.json +15 -22
|
@@ -1,563 +1,426 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @file advancedIconSearch.ts
|
|
7
|
-
* @module @nice2dev/icons
|
|
8
|
-
* @description PRO-28.3 — Advanced Icon Search with fuzzy matching,
|
|
9
|
-
* filters, semantic search support, and analytics.
|
|
10
|
-
*/
|
|
11
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
12
|
-
Fuzzy Match Utilities
|
|
13
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
14
|
-
/**
|
|
15
|
-
* Calculate Levenshtein distance between two strings.
|
|
16
|
-
*/
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const React = require("react");
|
|
17
4
|
function levenshteinDistance(a, b) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
5
|
+
const matrix = [];
|
|
6
|
+
if (a.length === 0) {
|
|
7
|
+
return b.length;
|
|
8
|
+
}
|
|
9
|
+
if (b.length === 0) {
|
|
10
|
+
return a.length;
|
|
11
|
+
}
|
|
12
|
+
for (let i = 0; i <= b.length; i++) {
|
|
13
|
+
matrix[i] = [i];
|
|
14
|
+
}
|
|
15
|
+
for (let j = 0; j <= a.length; j++) {
|
|
16
|
+
matrix[0][j] = j;
|
|
17
|
+
}
|
|
18
|
+
for (let i = 1; i <= b.length; i++) {
|
|
19
|
+
for (let j = 1; j <= a.length; j++) {
|
|
20
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
21
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
22
|
+
} else {
|
|
23
|
+
matrix[i][j] = Math.min(
|
|
24
|
+
matrix[i - 1][j - 1] + 1,
|
|
25
|
+
// substitution
|
|
26
|
+
matrix[i][j - 1] + 1,
|
|
27
|
+
// insertion
|
|
28
|
+
matrix[i - 1][j] + 1
|
|
29
|
+
// deletion
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return matrix[b.length][a.length];
|
|
47
35
|
}
|
|
48
|
-
/**
|
|
49
|
-
* Calculate fuzzy match score (0-100).
|
|
50
|
-
*/
|
|
51
36
|
function fuzzyMatchScore(query, target) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const distance = levenshteinDistance(q, t);
|
|
68
|
-
const maxLen = Math.max(q.length, t.length);
|
|
69
|
-
const similarity = ((maxLen - distance) / maxLen) * 100;
|
|
70
|
-
return Math.max(0, Math.round(similarity));
|
|
37
|
+
const q = query.toLowerCase();
|
|
38
|
+
const t = target.toLowerCase();
|
|
39
|
+
if (q === t) {
|
|
40
|
+
return 100;
|
|
41
|
+
}
|
|
42
|
+
if (t.includes(q)) {
|
|
43
|
+
return 90 - (t.length - q.length) / 2;
|
|
44
|
+
}
|
|
45
|
+
if (t.startsWith(q)) {
|
|
46
|
+
return 85;
|
|
47
|
+
}
|
|
48
|
+
const distance = levenshteinDistance(q, t);
|
|
49
|
+
const maxLen = Math.max(q.length, t.length);
|
|
50
|
+
const similarity = (maxLen - distance) / maxLen * 100;
|
|
51
|
+
return Math.max(0, Math.round(similarity));
|
|
71
52
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Tokenize a search query.
|
|
74
|
-
*/
|
|
75
53
|
function tokenizeQuery(query) {
|
|
76
|
-
|
|
77
|
-
.toLowerCase()
|
|
78
|
-
.replace(/[^\w\s-]/g, ' ')
|
|
79
|
-
.split(/\s+/)
|
|
80
|
-
.filter((token) => token.length > 1);
|
|
54
|
+
return query.toLowerCase().replace(/[^\w\s-]/g, " ").split(/\s+/).filter((token) => token.length > 1);
|
|
81
55
|
}
|
|
82
|
-
/*
|
|
83
|
-
|
|
84
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
85
|
-
const extendedRegistry = new Map();
|
|
86
|
-
const favoriteIcons = new Set();
|
|
56
|
+
const extendedRegistry = /* @__PURE__ */ new Map();
|
|
57
|
+
const favoriteIcons = /* @__PURE__ */ new Set();
|
|
87
58
|
const recentlyUsedIcons = [];
|
|
88
59
|
const searchHistory = [];
|
|
89
|
-
/**
|
|
90
|
-
* Register an icon with extended metadata.
|
|
91
|
-
*/
|
|
92
60
|
function registerExtendedIcon(name, metadata) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
extendedRegistry.set(name, extended);
|
|
61
|
+
const extended = {
|
|
62
|
+
name,
|
|
63
|
+
category: metadata.category || "ui",
|
|
64
|
+
primaryCategory: metadata.primaryCategory || metadata.category || "ui",
|
|
65
|
+
secondaryCategories: metadata.secondaryCategories || [],
|
|
66
|
+
domains: metadata.domains || ["general"],
|
|
67
|
+
useCases: metadata.useCases || ["action"],
|
|
68
|
+
styles: metadata.styles || ["solid", "outline"],
|
|
69
|
+
tags: metadata.tags || [],
|
|
70
|
+
aliases: metadata.aliases || [],
|
|
71
|
+
description: metadata.description || "",
|
|
72
|
+
related: metadata.related || [],
|
|
73
|
+
relatedIcons: metadata.relatedIcons || metadata.related || [],
|
|
74
|
+
recommendedSizes: metadata.recommendedSizes || [16, 48],
|
|
75
|
+
localizedNames: metadata.localizedNames || { en: name },
|
|
76
|
+
searchKeywords: metadata.searchKeywords || [],
|
|
77
|
+
dateAdded: metadata.dateAdded,
|
|
78
|
+
versionAdded: metadata.versionAdded,
|
|
79
|
+
deprecated: metadata.deprecated,
|
|
80
|
+
deprecatedMessage: metadata.deprecatedMessage
|
|
81
|
+
};
|
|
82
|
+
extendedRegistry.set(name, extended);
|
|
116
83
|
}
|
|
117
|
-
/**
|
|
118
|
-
* Get extended metadata for an icon.
|
|
119
|
-
*/
|
|
120
84
|
function getExtendedMetadata(name) {
|
|
121
|
-
|
|
85
|
+
return extendedRegistry.get(name);
|
|
122
86
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Mark an icon as favorite.
|
|
125
|
-
*/
|
|
126
87
|
function toggleFavorite(name) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
88
|
+
if (favoriteIcons.has(name)) {
|
|
89
|
+
favoriteIcons.delete(name);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
favoriteIcons.add(name);
|
|
93
|
+
return true;
|
|
133
94
|
}
|
|
134
|
-
/**
|
|
135
|
-
* Check if icon is favorite.
|
|
136
|
-
*/
|
|
137
95
|
function isFavorite(name) {
|
|
138
|
-
|
|
96
|
+
return favoriteIcons.has(name);
|
|
139
97
|
}
|
|
140
|
-
/**
|
|
141
|
-
* Get all favorite icons.
|
|
142
|
-
*/
|
|
143
98
|
function getFavorites() {
|
|
144
|
-
|
|
99
|
+
return Array.from(favoriteIcons);
|
|
145
100
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Record icon usage (for analytics).
|
|
148
|
-
*/
|
|
149
101
|
function recordIconUsage(name) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
102
|
+
const existing = recentlyUsedIcons.findIndex((item) => item.name === name);
|
|
103
|
+
if (existing >= 0) {
|
|
104
|
+
recentlyUsedIcons.splice(existing, 1);
|
|
105
|
+
}
|
|
106
|
+
recentlyUsedIcons.unshift({ name, timestamp: Date.now() });
|
|
107
|
+
if (recentlyUsedIcons.length > 100) {
|
|
108
|
+
recentlyUsedIcons.pop();
|
|
109
|
+
}
|
|
159
110
|
}
|
|
160
|
-
/**
|
|
161
|
-
* Get recently used icons.
|
|
162
|
-
*/
|
|
163
111
|
function getRecentlyUsed(limit = 20) {
|
|
164
|
-
|
|
112
|
+
return recentlyUsedIcons.slice(0, limit).map((item) => item.name);
|
|
165
113
|
}
|
|
166
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
167
|
-
Advanced Search Engine
|
|
168
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
169
|
-
/**
|
|
170
|
-
* Perform advanced search with fuzzy matching and filters.
|
|
171
|
-
*/
|
|
172
114
|
function advancedSearch(query, filters = {}, options = {}) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
115
|
+
const startTime = performance.now();
|
|
116
|
+
const { maxResults = 50, minScore = 30, locale = "en" } = options;
|
|
117
|
+
const tokens = tokenizeQuery(query);
|
|
118
|
+
const results = [];
|
|
119
|
+
for (const [name, metadata] of extendedRegistry) {
|
|
120
|
+
if (!passesFilters(metadata, filters)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const { score, matchedIn, highlights } = calculateMatchScore(
|
|
124
|
+
tokens,
|
|
125
|
+
query,
|
|
126
|
+
name,
|
|
127
|
+
metadata,
|
|
128
|
+
locale
|
|
129
|
+
);
|
|
130
|
+
if (score >= minScore) {
|
|
131
|
+
results.push({ name, metadata, score, matchedIn, highlights });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
results.sort((a, b) => b.score - a.score);
|
|
135
|
+
const duration = performance.now() - startTime;
|
|
136
|
+
searchHistory.push({
|
|
137
|
+
query,
|
|
138
|
+
duration,
|
|
139
|
+
totalResults: results.length,
|
|
140
|
+
filters,
|
|
141
|
+
timestamp: Date.now()
|
|
142
|
+
});
|
|
143
|
+
return results.slice(0, maxResults);
|
|
200
144
|
}
|
|
201
|
-
/**
|
|
202
|
-
* Check if metadata passes all filters.
|
|
203
|
-
*/
|
|
204
145
|
function passesFilters(metadata, filters) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
146
|
+
if (filters.categories?.length) {
|
|
147
|
+
const allCats = [metadata.primaryCategory, ...metadata.secondaryCategories];
|
|
148
|
+
if (!filters.categories.some((c) => allCats.includes(c))) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (filters.domains?.length) {
|
|
153
|
+
if (!filters.domains.some((d) => metadata.domains.includes(d))) {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (filters.useCases?.length) {
|
|
158
|
+
if (!filters.useCases.some((u) => metadata.useCases.includes(u))) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (filters.styles?.length) {
|
|
163
|
+
if (!filters.styles.some((s) => metadata.styles.includes(s))) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (filters.minSize !== void 0) {
|
|
168
|
+
if (metadata.recommendedSizes[1] < filters.minSize) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (filters.maxSize !== void 0) {
|
|
173
|
+
if (metadata.recommendedSizes[0] > filters.maxSize) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (filters.excludeDeprecated && metadata.deprecated) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
if (filters.favoritesOnly && !favoriteIcons.has(metadata.name)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (filters.recentlyUsedOnly) {
|
|
184
|
+
const isRecent = recentlyUsedIcons.some((r) => r.name === metadata.name);
|
|
185
|
+
if (!isRecent) {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (filters.recentDays !== void 0 && metadata.dateAdded) {
|
|
190
|
+
const addedDate = new Date(metadata.dateAdded).getTime();
|
|
191
|
+
const cutoff = Date.now() - filters.recentDays * 24 * 60 * 60 * 1e3;
|
|
192
|
+
if (addedDate < cutoff) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
function calculateMatchScore(tokens, fullQuery, name, metadata, locale) {
|
|
199
|
+
let totalScore = 0;
|
|
200
|
+
const matchedIn = [];
|
|
201
|
+
const highlights = [];
|
|
202
|
+
const nameScore = fuzzyMatchScore(fullQuery, name);
|
|
203
|
+
if (nameScore > 30) {
|
|
204
|
+
totalScore += nameScore * 2;
|
|
205
|
+
matchedIn.push("name");
|
|
206
|
+
highlights.push({ field: "name", text: name });
|
|
207
|
+
}
|
|
208
|
+
for (const alias of metadata.aliases) {
|
|
209
|
+
const aliasScore = fuzzyMatchScore(fullQuery, alias);
|
|
210
|
+
if (aliasScore > 30) {
|
|
211
|
+
totalScore += aliasScore * 1.5;
|
|
212
|
+
if (!matchedIn.includes("alias")) {
|
|
213
|
+
matchedIn.push("alias");
|
|
214
|
+
}
|
|
215
|
+
highlights.push({ field: "alias", text: alias });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const localizedName = metadata.localizedNames[locale] || metadata.localizedNames.en;
|
|
219
|
+
if (localizedName && localizedName !== name) {
|
|
220
|
+
const localScore = fuzzyMatchScore(fullQuery, localizedName);
|
|
221
|
+
if (localScore > 30) {
|
|
222
|
+
totalScore += localScore * 1.3;
|
|
223
|
+
if (!matchedIn.includes("localized")) {
|
|
224
|
+
matchedIn.push("localized");
|
|
225
|
+
}
|
|
226
|
+
highlights.push({ field: "localized", text: localizedName });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const tag of metadata.tags) {
|
|
230
|
+
for (const token of tokens) {
|
|
231
|
+
const tagScore = fuzzyMatchScore(token, tag);
|
|
232
|
+
if (tagScore > 50) {
|
|
233
|
+
totalScore += tagScore * 0.7;
|
|
234
|
+
if (!matchedIn.includes("tag")) {
|
|
235
|
+
matchedIn.push("tag");
|
|
240
236
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const isRecent = recentlyUsedIcons.some((r) => r.name === metadata.name);
|
|
253
|
-
if (!isRecent) {
|
|
254
|
-
return false;
|
|
237
|
+
highlights.push({ field: "tag", text: tag });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const keyword of metadata.searchKeywords) {
|
|
242
|
+
for (const token of tokens) {
|
|
243
|
+
const keywordScore = fuzzyMatchScore(token, keyword);
|
|
244
|
+
if (keywordScore > 50) {
|
|
245
|
+
totalScore += keywordScore * 0.8;
|
|
246
|
+
if (!matchedIn.includes("keyword")) {
|
|
247
|
+
matchedIn.push("keyword");
|
|
255
248
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
249
|
+
highlights.push({ field: "keyword", text: keyword });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (metadata.description) {
|
|
254
|
+
for (const token of tokens) {
|
|
255
|
+
if (metadata.description.toLowerCase().includes(token)) {
|
|
256
|
+
totalScore += 15;
|
|
257
|
+
if (!matchedIn.includes("description")) {
|
|
258
|
+
matchedIn.push("description");
|
|
263
259
|
}
|
|
260
|
+
highlights.push({ field: "description", text: metadata.description });
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
264
263
|
}
|
|
265
|
-
|
|
264
|
+
}
|
|
265
|
+
const normalizedScore = Math.min(100, Math.round(totalScore / (tokens.length || 1)));
|
|
266
|
+
return { score: normalizedScore, matchedIn, highlights };
|
|
266
267
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const localScore = fuzzyMatchScore(fullQuery, localizedName);
|
|
296
|
-
if (localScore > 30) {
|
|
297
|
-
totalScore += localScore * 1.3;
|
|
298
|
-
if (!matchedIn.includes('localized')) {
|
|
299
|
-
matchedIn.push('localized');
|
|
300
|
-
}
|
|
301
|
-
highlights.push({ field: 'localized', text: localizedName });
|
|
302
|
-
}
|
|
268
|
+
function findSimilarIcons(iconName, limit = 10) {
|
|
269
|
+
const source = extendedRegistry.get(iconName);
|
|
270
|
+
if (!source) {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
const similarities = [];
|
|
274
|
+
for (const [name, metadata] of extendedRegistry) {
|
|
275
|
+
if (name === iconName) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
let similarity = 0;
|
|
279
|
+
if (metadata.primaryCategory === source.primaryCategory) {
|
|
280
|
+
similarity += 30;
|
|
281
|
+
}
|
|
282
|
+
for (const cat of metadata.secondaryCategories) {
|
|
283
|
+
if (source.secondaryCategories.includes(cat)) {
|
|
284
|
+
similarity += 5;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for (const domain of metadata.domains) {
|
|
288
|
+
if (source.domains.includes(domain)) {
|
|
289
|
+
similarity += 10;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
for (const useCase of metadata.useCases) {
|
|
293
|
+
if (source.useCases.includes(useCase)) {
|
|
294
|
+
similarity += 5;
|
|
295
|
+
}
|
|
303
296
|
}
|
|
304
|
-
// Match against tags
|
|
305
297
|
for (const tag of metadata.tags) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
totalScore += tagScore * 0.7;
|
|
310
|
-
if (!matchedIn.includes('tag')) {
|
|
311
|
-
matchedIn.push('tag');
|
|
312
|
-
}
|
|
313
|
-
highlights.push({ field: 'tag', text: tag });
|
|
314
|
-
}
|
|
315
|
-
}
|
|
298
|
+
if (source.tags.includes(tag)) {
|
|
299
|
+
similarity += 3;
|
|
300
|
+
}
|
|
316
301
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
for (const token of tokens) {
|
|
320
|
-
const keywordScore = fuzzyMatchScore(token, keyword);
|
|
321
|
-
if (keywordScore > 50) {
|
|
322
|
-
totalScore += keywordScore * 0.8;
|
|
323
|
-
if (!matchedIn.includes('keyword')) {
|
|
324
|
-
matchedIn.push('keyword');
|
|
325
|
-
}
|
|
326
|
-
highlights.push({ field: 'keyword', text: keyword });
|
|
327
|
-
}
|
|
328
|
-
}
|
|
302
|
+
if (source.relatedIcons.includes(name)) {
|
|
303
|
+
similarity += 20;
|
|
329
304
|
}
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
if (metadata.description.toLowerCase().includes(token)) {
|
|
334
|
-
totalScore += 15;
|
|
335
|
-
if (!matchedIn.includes('description')) {
|
|
336
|
-
matchedIn.push('description');
|
|
337
|
-
}
|
|
338
|
-
highlights.push({ field: 'description', text: metadata.description });
|
|
339
|
-
break; // Only count once for description
|
|
340
|
-
}
|
|
341
|
-
}
|
|
305
|
+
similarity += fuzzyMatchScore(iconName, name) / 5;
|
|
306
|
+
if (similarity > 20) {
|
|
307
|
+
similarities.push({ name, similarity: Math.min(100, similarity) });
|
|
342
308
|
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
309
|
+
}
|
|
310
|
+
similarities.sort((a, b) => b.similarity - a.similarity);
|
|
311
|
+
return similarities.slice(0, limit);
|
|
346
312
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
*/
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return [];
|
|
313
|
+
function getSearchSuggestions(partial, limit = 10) {
|
|
314
|
+
if (partial.length < 2) {
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
const p = partial.toLowerCase();
|
|
318
|
+
const suggestions = /* @__PURE__ */ new Set();
|
|
319
|
+
for (const [name, metadata] of extendedRegistry) {
|
|
320
|
+
if (name.toLowerCase().startsWith(p)) {
|
|
321
|
+
suggestions.add(name);
|
|
357
322
|
}
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
let similarity = 0;
|
|
364
|
-
// Same primary category (+30)
|
|
365
|
-
if (metadata.primaryCategory === source.primaryCategory) {
|
|
366
|
-
similarity += 30;
|
|
367
|
-
}
|
|
368
|
-
// Shared secondary categories (+5 each)
|
|
369
|
-
for (const cat of metadata.secondaryCategories) {
|
|
370
|
-
if (source.secondaryCategories.includes(cat)) {
|
|
371
|
-
similarity += 5;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
// Shared domains (+10 each)
|
|
375
|
-
for (const domain of metadata.domains) {
|
|
376
|
-
if (source.domains.includes(domain)) {
|
|
377
|
-
similarity += 10;
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
// Shared use-cases (+5 each)
|
|
381
|
-
for (const useCase of metadata.useCases) {
|
|
382
|
-
if (source.useCases.includes(useCase)) {
|
|
383
|
-
similarity += 5;
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
// Shared tags (+3 each)
|
|
387
|
-
for (const tag of metadata.tags) {
|
|
388
|
-
if (source.tags.includes(tag)) {
|
|
389
|
-
similarity += 3;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
// In related icons list (+20)
|
|
393
|
-
if (source.relatedIcons.includes(name)) {
|
|
394
|
-
similarity += 20;
|
|
395
|
-
}
|
|
396
|
-
// Similar name (+fuzzy score / 5)
|
|
397
|
-
similarity += fuzzyMatchScore(iconName, name) / 5;
|
|
398
|
-
if (similarity > 20) {
|
|
399
|
-
similarities.push({ name, similarity: Math.min(100, similarity) });
|
|
400
|
-
}
|
|
323
|
+
for (const alias of metadata.aliases) {
|
|
324
|
+
if (alias.toLowerCase().startsWith(p)) {
|
|
325
|
+
suggestions.add(alias);
|
|
326
|
+
}
|
|
401
327
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
407
|
-
Search Suggestions / Autocomplete
|
|
408
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
409
|
-
/**
|
|
410
|
-
* Get search suggestions based on partial input.
|
|
411
|
-
*/
|
|
412
|
-
function getSearchSuggestions(partial, limit = 10) {
|
|
413
|
-
if (partial.length < 2) {
|
|
414
|
-
return [];
|
|
328
|
+
for (const tag of metadata.tags) {
|
|
329
|
+
if (tag.toLowerCase().startsWith(p)) {
|
|
330
|
+
suggestions.add(tag);
|
|
331
|
+
}
|
|
415
332
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
for (const [name, metadata] of extendedRegistry) {
|
|
419
|
-
// Name starts with partial
|
|
420
|
-
if (name.toLowerCase().startsWith(p)) {
|
|
421
|
-
suggestions.add(name);
|
|
422
|
-
}
|
|
423
|
-
// Alias starts with partial
|
|
424
|
-
for (const alias of metadata.aliases) {
|
|
425
|
-
if (alias.toLowerCase().startsWith(p)) {
|
|
426
|
-
suggestions.add(alias);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
// Tag starts with partial
|
|
430
|
-
for (const tag of metadata.tags) {
|
|
431
|
-
if (tag.toLowerCase().startsWith(p)) {
|
|
432
|
-
suggestions.add(tag);
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
if (suggestions.size >= limit * 2) {
|
|
436
|
-
break;
|
|
437
|
-
}
|
|
333
|
+
if (suggestions.size >= limit * 2) {
|
|
334
|
+
break;
|
|
438
335
|
}
|
|
439
|
-
|
|
336
|
+
}
|
|
337
|
+
return Array.from(suggestions).slice(0, limit);
|
|
440
338
|
}
|
|
441
|
-
/* ══════════════════════════════════════════════════════════════════════════════
|
|
442
|
-
Analytics & Insights
|
|
443
|
-
══════════════════════════════════════════════════════════════════════════════ */
|
|
444
|
-
/**
|
|
445
|
-
* Get search analytics summary.
|
|
446
|
-
*/
|
|
447
339
|
function getSearchAnalytics() {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
averageDuration: searchHistory.length > 0 ? totalDuration / searchHistory.length : 0,
|
|
467
|
-
topQueries,
|
|
468
|
-
zeroResultQueries: [...new Set(zeroResults)].slice(0, 20),
|
|
469
|
-
};
|
|
340
|
+
const queryCounts = /* @__PURE__ */ new Map();
|
|
341
|
+
const zeroResults = [];
|
|
342
|
+
let totalDuration = 0;
|
|
343
|
+
for (const search of searchHistory) {
|
|
344
|
+
totalDuration += search.duration;
|
|
345
|
+
const count = queryCounts.get(search.query) || 0;
|
|
346
|
+
queryCounts.set(search.query, count + 1);
|
|
347
|
+
if (search.totalResults === 0) {
|
|
348
|
+
zeroResults.push(search.query);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const topQueries = Array.from(queryCounts.entries()).map(([query, count]) => ({ query, count })).sort((a, b) => b.count - a.count).slice(0, 10);
|
|
352
|
+
return {
|
|
353
|
+
totalSearches: searchHistory.length,
|
|
354
|
+
averageDuration: searchHistory.length > 0 ? totalDuration / searchHistory.length : 0,
|
|
355
|
+
topQueries,
|
|
356
|
+
zeroResultQueries: [...new Set(zeroResults)].slice(0, 20)
|
|
357
|
+
};
|
|
470
358
|
}
|
|
471
|
-
/**
|
|
472
|
-
* Get icon usage statistics.
|
|
473
|
-
*/
|
|
474
359
|
function getIconUsageStats() {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
if (!usedNames.has(name)) {
|
|
491
|
-
neverUsed.push(name);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
// Least used (used once)
|
|
495
|
-
const leastUsed = Array.from(usageCounts.entries())
|
|
496
|
-
.filter(([_, count]) => count === 1)
|
|
497
|
-
.map(([name]) => name)
|
|
498
|
-
.slice(0, 20);
|
|
499
|
-
return { mostUsed, leastUsed, neverUsed };
|
|
360
|
+
const usageCounts = /* @__PURE__ */ new Map();
|
|
361
|
+
for (const item of recentlyUsedIcons) {
|
|
362
|
+
const count = usageCounts.get(item.name) || 0;
|
|
363
|
+
usageCounts.set(item.name, count + 1);
|
|
364
|
+
}
|
|
365
|
+
const mostUsed = Array.from(usageCounts.entries()).map(([name, count]) => ({ name, count })).sort((a, b) => b.count - a.count).slice(0, 20);
|
|
366
|
+
const usedNames = new Set(recentlyUsedIcons.map((i) => i.name));
|
|
367
|
+
const neverUsed = [];
|
|
368
|
+
for (const name of extendedRegistry.keys()) {
|
|
369
|
+
if (!usedNames.has(name)) {
|
|
370
|
+
neverUsed.push(name);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const leastUsed = Array.from(usageCounts.entries()).filter(([_, count]) => count === 1).map(([name]) => name).slice(0, 20);
|
|
374
|
+
return { mostUsed, leastUsed, neverUsed };
|
|
500
375
|
}
|
|
501
|
-
/**
|
|
502
|
-
* Clear search history.
|
|
503
|
-
*/
|
|
504
376
|
function clearSearchHistory() {
|
|
505
|
-
|
|
377
|
+
searchHistory.length = 0;
|
|
506
378
|
}
|
|
507
|
-
/**
|
|
508
|
-
* Clear recently used.
|
|
509
|
-
*/
|
|
510
379
|
function clearRecentlyUsed() {
|
|
511
|
-
|
|
380
|
+
recentlyUsedIcons.length = 0;
|
|
512
381
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
isSearching,
|
|
555
|
-
suggestions,
|
|
556
|
-
search,
|
|
557
|
-
clearFilters,
|
|
558
|
-
};
|
|
382
|
+
function useAdvancedIconSearch(debounceMs = 200, locale = "en") {
|
|
383
|
+
const [query, setQuery] = React.useState("");
|
|
384
|
+
const [filters, setFilters] = React.useState({});
|
|
385
|
+
const [results, setResults] = React.useState([]);
|
|
386
|
+
const [isSearching, setIsSearching] = React.useState(false);
|
|
387
|
+
const suggestions = React.useMemo(() => {
|
|
388
|
+
return query.length >= 2 ? getSearchSuggestions(query) : [];
|
|
389
|
+
}, [query]);
|
|
390
|
+
React.useEffect(() => {
|
|
391
|
+
if (!query && !Object.keys(filters).length) {
|
|
392
|
+
setResults([]);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
setIsSearching(true);
|
|
396
|
+
const timer = setTimeout(() => {
|
|
397
|
+
const searchResults = advancedSearch(query, filters, { locale });
|
|
398
|
+
setResults(searchResults);
|
|
399
|
+
setIsSearching(false);
|
|
400
|
+
}, debounceMs);
|
|
401
|
+
return () => clearTimeout(timer);
|
|
402
|
+
}, [query, filters, debounceMs, locale]);
|
|
403
|
+
const search = React.useCallback(() => {
|
|
404
|
+
setIsSearching(true);
|
|
405
|
+
const searchResults = advancedSearch(query, filters, { locale });
|
|
406
|
+
setResults(searchResults);
|
|
407
|
+
setIsSearching(false);
|
|
408
|
+
}, [query, filters, locale]);
|
|
409
|
+
const clearFilters = React.useCallback(() => {
|
|
410
|
+
setFilters({});
|
|
411
|
+
}, []);
|
|
412
|
+
return {
|
|
413
|
+
query,
|
|
414
|
+
setQuery,
|
|
415
|
+
filters,
|
|
416
|
+
setFilters,
|
|
417
|
+
results,
|
|
418
|
+
isSearching,
|
|
419
|
+
suggestions,
|
|
420
|
+
search,
|
|
421
|
+
clearFilters
|
|
422
|
+
};
|
|
559
423
|
}
|
|
560
|
-
|
|
561
424
|
exports.advancedSearch = advancedSearch;
|
|
562
425
|
exports.clearRecentlyUsed = clearRecentlyUsed;
|
|
563
426
|
exports.clearSearchHistory = clearSearchHistory;
|