sprintify-ui 0.10.62 → 0.10.64

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.
@@ -0,0 +1,14 @@
1
+ export interface SearchOpts {
2
+ maxDepth?: number;
3
+ maxCharsPerItem?: number;
4
+ maxArray?: number;
5
+ cache?: WeakMap<object, string>;
6
+ }
7
+ /**
8
+ * Recursively searches an item (object, array, primitive) for a case-insensitive match of the query string.
9
+ * Limits can be set to avoid excessive CPU usage on large/deep items.
10
+ * @param item The item to search within.
11
+ * @param queryRaw The query string to search for.
12
+ * @param opts Optional search options.
13
+ */
14
+ export declare function deepIncludes(item: any, queryRaw: string, opts?: SearchOpts): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.10.62",
3
+ "version": "0.10.64",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "rimraf dist && vue-tsc && vite build",
@@ -188,6 +188,7 @@ type Direction = 'asc' | 'desc';
188
188
  import { Size } from '@/utils/sizes';
189
189
  import BaseInput from './BaseInput.vue';
190
190
  import { useInputSize } from '@/composables/inputSize';
191
+ import { deepIncludes } from '@/utils/deepIncludes';
191
192
 
192
193
  const DEFAULT_QUERY = {
193
194
  page: 1,
@@ -785,22 +786,11 @@ function searchItems(items: Collection | undefined) {
785
786
  return [];
786
787
  }
787
788
 
788
- const searchWords = searchKeywords.value.toLowerCase();
789
-
790
789
  if (props.search) {
791
790
  return props.search(items, searchKeywords.value);
792
791
  }
793
792
 
794
- return items?.filter((item) => {
795
- return Object.values(item).some((value) => {
796
- if (typeof value === 'string') {
797
- return value.toLowerCase().includes(searchWords);
798
- }
799
- if (typeof value === 'object') {
800
- return JSON.stringify(value).toLowerCase().includes(searchWords);
801
- }
802
- });
803
- });
793
+ return items.filter((it) => deepIncludes(it, searchKeywords.value));
804
794
  }
805
795
 
806
796
  function sortItems(items: Collection | undefined) {
@@ -345,7 +345,7 @@ const decorationWrapClasses = computed(() => {
345
345
  });
346
346
 
347
347
  const decorationClasses = computed(() => {
348
- const base = `flex items-center justify-center rounded-md`;
348
+ const base = `flex items-center justify-center rounded-md whitespace-pre`;
349
349
  const textColor = hasErrorInternal.value ? 'text-red-800' : 'text-slate-500';
350
350
  const padding = {
351
351
  xs: 'p-1',
@@ -0,0 +1,77 @@
1
+ export interface SearchOpts {
2
+ maxDepth?: number; // stop descending after this depth
3
+ maxCharsPerItem?: number; // stop scanning item after reading this many chars
4
+ maxArray?: number; // cap how many array items to inspect per array
5
+ cache?: WeakMap<object, string>; // optional: lazy per-item cache of a “search blob”
6
+ }
7
+
8
+ /**
9
+ * Recursively searches an item (object, array, primitive) for a case-insensitive match of the query string.
10
+ * Limits can be set to avoid excessive CPU usage on large/deep items.
11
+ * @param item The item to search within.
12
+ * @param queryRaw The query string to search for.
13
+ * @param opts Optional search options.
14
+ */
15
+ export function deepIncludes(item: any, queryRaw: string, opts: SearchOpts = {}): boolean {
16
+ const q = queryRaw.toLowerCase();
17
+ const {
18
+ maxDepth = 3,
19
+ maxCharsPerItem = 3000,
20
+ maxArray = 64,
21
+ cache,
22
+ } = opts;
23
+
24
+ let consumed = 0;
25
+ const seen = new WeakSet<object>();
26
+
27
+ const feed = (s: string): boolean => {
28
+ // consume without allocating new strings repeatedly
29
+ consumed += s.length;
30
+ return s.toLowerCase().includes(q) || consumed >= maxCharsPerItem;
31
+ };
32
+
33
+ const visit = (val: any, depth: number): boolean => {
34
+ if (val == null) return false;
35
+ if (consumed >= maxCharsPerItem) return false;
36
+
37
+ const t = typeof val;
38
+
39
+ if (t === 'string') return feed(val);
40
+ if (t === 'number' || t === 'boolean') return feed(String(val));
41
+ if (t === 'bigint') return feed(val.toString());
42
+ if (t === 'symbol' || t === 'function') return false;
43
+
44
+ // objects & arrays
45
+ if (t === 'object') {
46
+ if (seen.has(val)) return false;
47
+ seen.add(val);
48
+
49
+ // Optional lazy cache: build once per object identity
50
+ if (cache && cache.has(val)) {
51
+ return feed(cache.get(val)!); // already lowercased when stored? not necessary since feed lowercases
52
+ }
53
+
54
+ if (depth >= maxDepth) return false;
55
+
56
+ if (Array.isArray(val)) {
57
+ const len = Math.min(val.length, maxArray);
58
+ for (let i = 0; i < len; i++) {
59
+ if (visit(val[i], depth + 1)) return true;
60
+ }
61
+ return false;
62
+ }
63
+
64
+ // plain object: iterate own enumerable keys only
65
+ for (const k in val) {
66
+ // match on key names too (often helpful if values are IDs)
67
+ if (feed(k)) return true;
68
+ if (visit(val[k], depth + 1)) return true;
69
+ }
70
+ return false;
71
+ }
72
+
73
+ return false;
74
+ };
75
+
76
+ return visit(item, 0);
77
+ }