@bysivin/jumpbar 0.1.0 → 0.2.0
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 +69 -11
- package/dist/core.js +148 -0
- package/dist/core.js.map +1 -0
- package/dist/index.js +583 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -8
- package/tailwind.cjs +22 -0
- package/theme.css +33 -0
- package/types/core.d.ts +76 -0
- package/types/index.d.ts +77 -0
- package/src/core/frecency.js +0 -41
- package/src/core/match.js +0 -71
- package/src/core/parse.js +0 -6
- package/src/core/rank.js +0 -70
- package/src/index.js +0 -6
- package/src/vue/CommandBar.vue +0 -173
- package/src/vue/CommandBarList.vue +0 -173
- package/src/vue/useCommandBar.js +0 -261
package/README.md
CHANGED
|
@@ -17,29 +17,86 @@ rendered with Tailwind utilities (about 5 KB gzipped with the core).
|
|
|
17
17
|
pnpm add @bysivin/jumpbar
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
The package ships compiled ES modules with TypeScript declarations; Vue
|
|
21
|
+
stays external. `vue` 3.4 or newer is needed for the shell.
|
|
22
|
+
|
|
23
|
+
### Any framework
|
|
24
|
+
|
|
25
|
+
The ranking core has no framework dependency. React, Svelte, Solid or plain
|
|
26
|
+
JavaScript projects import it from `@bysivin/jumpbar/core` and render their
|
|
27
|
+
own list:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { rank, groupBySection, createFrecency } from '@bysivin/jumpbar/core';
|
|
31
|
+
|
|
32
|
+
const ranked = rank(items, { text: query, scopes: ['conversation'] });
|
|
33
|
+
const sections = groupBySection(ranked);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The `CommandBar` shell and `useCommandBar` state are Vue 3.
|
|
22
37
|
|
|
23
38
|
## Styling
|
|
24
39
|
|
|
25
|
-
The shell is
|
|
26
|
-
|
|
40
|
+
The shell is built from Tailwind utilities and reads every colour from a
|
|
41
|
+
small set of semantic tokens, so it adapts to any design system.
|
|
27
42
|
|
|
28
|
-
1. Add the
|
|
29
|
-
preset that defines the open/close keyframes:
|
|
43
|
+
1. Add the preset and scan the package so its classes are generated:
|
|
30
44
|
|
|
31
45
|
```js
|
|
32
46
|
// tailwind.config.js
|
|
33
47
|
module.exports = {
|
|
34
48
|
presets: [require('@bysivin/jumpbar/tailwind')],
|
|
35
|
-
content: ['./node_modules/@bysivin/jumpbar/
|
|
49
|
+
content: ['./node_modules/@bysivin/jumpbar/dist/*.js'],
|
|
36
50
|
};
|
|
37
51
|
```
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
53
|
+
The preset registers the `jumpbar-*` colours as CSS variables and the
|
|
54
|
+
open and close keyframes.
|
|
55
|
+
|
|
56
|
+
2. Theme it. Either import the neutral defaults and override what you like
|
|
57
|
+
(dark values apply under a `dark` class on the root element):
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
import '@bysivin/jumpbar/theme.css';
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```css
|
|
64
|
+
:root {
|
|
65
|
+
--jumpbar-primary: #2781f6;
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
or skip the import and map the tokens onto your existing Tailwind colours,
|
|
70
|
+
which also carries your dark mode along:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
theme: {
|
|
74
|
+
extend: {
|
|
75
|
+
colors: {
|
|
76
|
+
jumpbar: {
|
|
77
|
+
background: colors.surface,
|
|
78
|
+
foreground: colors.text,
|
|
79
|
+
primary: colors.brand,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Token | Used for |
|
|
87
|
+
| ------------------- | ------------------------------------------------- |
|
|
88
|
+
| `background` | Panel |
|
|
89
|
+
| `foreground` | Titles and input text |
|
|
90
|
+
| `muted-foreground` | Subtitles, section labels, breadcrumbs, badges |
|
|
91
|
+
| `subtle-foreground` | Placeholder, icons, hints, chevrons |
|
|
92
|
+
| `muted` | Active row, chips, avatar fallback, skeleton rows |
|
|
93
|
+
| `subtle` | Key caps, lighter skeleton bar |
|
|
94
|
+
| `border` | Dividers and key cap outline |
|
|
95
|
+
| `ring` | Panel outline |
|
|
96
|
+
| `primary` | Active row marker, page chip text |
|
|
97
|
+
| `primary-soft` | Page chip background |
|
|
98
|
+
| `overlay` | Backdrop |
|
|
99
|
+
| `online` `busy` `offline` | Presence dot |
|
|
43
100
|
|
|
44
101
|
3. Provide the icon classes `i-lucide-search`, `i-lucide-loader-circle` and
|
|
45
102
|
`i-lucide-chevron-right` (for example through `@egoist/tailwindcss-icons`
|
|
@@ -181,4 +238,5 @@ so arrow keys keep working under a resting cursor.
|
|
|
181
238
|
```sh
|
|
182
239
|
pnpm install
|
|
183
240
|
pnpm test
|
|
241
|
+
pnpm build
|
|
184
242
|
```
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const COMBINING_MARKS = /[\u0300-\u036f]/g;
|
|
2
|
+
const EXACT = 1;
|
|
3
|
+
const PREFIX = 0.9;
|
|
4
|
+
const WORD_START = 0.8;
|
|
5
|
+
const ALL_WORDS = 0.7;
|
|
6
|
+
const SUBSEQUENCE_MAX = 0.6;
|
|
7
|
+
const SUBSEQUENCE_MIN = 0.2;
|
|
8
|
+
const NORMALIZED_CACHE_LIMIT = 5e3;
|
|
9
|
+
const normalized = /* @__PURE__ */ new Map();
|
|
10
|
+
const normalize = (value) => {
|
|
11
|
+
const raw = String(value ?? "");
|
|
12
|
+
const cached = normalized.get(raw);
|
|
13
|
+
if (cached !== void 0) return cached;
|
|
14
|
+
if (normalized.size >= NORMALIZED_CACHE_LIMIT) normalized.clear();
|
|
15
|
+
const result = raw.normalize("NFD").replace(COMBINING_MARKS, "").toLowerCase().trim();
|
|
16
|
+
normalized.set(raw, result);
|
|
17
|
+
return result;
|
|
18
|
+
};
|
|
19
|
+
const tightestSpan = (query, text) => {
|
|
20
|
+
let best = Infinity;
|
|
21
|
+
for (let start = text.indexOf(query[0]); start !== -1; start = text.indexOf(query[0], start + 1)) {
|
|
22
|
+
let cursor = start;
|
|
23
|
+
let matched = true;
|
|
24
|
+
for (let i = 1; i < query.length; i += 1) {
|
|
25
|
+
cursor = text.indexOf(query[i], cursor + 1);
|
|
26
|
+
if (cursor === -1) {
|
|
27
|
+
matched = false;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (matched) best = Math.min(best, cursor - start + 1);
|
|
32
|
+
}
|
|
33
|
+
return best;
|
|
34
|
+
};
|
|
35
|
+
const fuzzyScore = (query, text, { subsequence = true } = {}) => {
|
|
36
|
+
const needle = normalize(query);
|
|
37
|
+
if (!needle) return EXACT;
|
|
38
|
+
const haystack = normalize(text);
|
|
39
|
+
if (haystack === needle) return EXACT;
|
|
40
|
+
if (haystack.startsWith(needle)) return PREFIX;
|
|
41
|
+
const words = haystack.split(/\s+/);
|
|
42
|
+
if (words.some((word) => word.startsWith(needle))) return WORD_START;
|
|
43
|
+
const needleWords = needle.split(/\s+/);
|
|
44
|
+
if (needleWords.every((part) => words.some((word) => word.startsWith(part)))) {
|
|
45
|
+
return ALL_WORDS;
|
|
46
|
+
}
|
|
47
|
+
if (!subsequence) return 0;
|
|
48
|
+
const compact = needle.replace(/\s+/g, "");
|
|
49
|
+
const span = tightestSpan(compact, haystack);
|
|
50
|
+
if (span === Infinity) return 0;
|
|
51
|
+
return SUBSEQUENCE_MIN + (SUBSEQUENCE_MAX - SUBSEQUENCE_MIN) * (compact.length / span);
|
|
52
|
+
};
|
|
53
|
+
const parseQuery = (input, prefixes = []) => {
|
|
54
|
+
const trimmed = String(input ?? "").trim();
|
|
55
|
+
const prefix = prefixes.includes(trimmed[0]) ? trimmed[0] : null;
|
|
56
|
+
const text = prefix ? trimmed.slice(1).trim() : trimmed;
|
|
57
|
+
return { prefix, text };
|
|
58
|
+
};
|
|
59
|
+
const DAY_MS = 24 * 60 * 60 * 1e3;
|
|
60
|
+
const HALF_LIFE_DAYS = 7;
|
|
61
|
+
const USES_FOR_FULL_SCORE = 3;
|
|
62
|
+
const MAX_IDLE_DAYS = 30;
|
|
63
|
+
const load = (storage, key) => {
|
|
64
|
+
try {
|
|
65
|
+
return JSON.parse(storage.getItem(key)) ?? {};
|
|
66
|
+
} catch {
|
|
67
|
+
return {};
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const createFrecency = ({
|
|
71
|
+
storage,
|
|
72
|
+
key,
|
|
73
|
+
now = Date.now,
|
|
74
|
+
limit = 10
|
|
75
|
+
}) => {
|
|
76
|
+
const uses = load(storage, key);
|
|
77
|
+
const record = (id) => {
|
|
78
|
+
const at = now();
|
|
79
|
+
uses[id] = [...uses[id] ?? [], at].slice(-limit);
|
|
80
|
+
Object.keys(uses).forEach((other) => {
|
|
81
|
+
if (at - uses[other].at(-1) > MAX_IDLE_DAYS * DAY_MS) delete uses[other];
|
|
82
|
+
});
|
|
83
|
+
storage.setItem(key, JSON.stringify(uses));
|
|
84
|
+
};
|
|
85
|
+
const score = (id) => {
|
|
86
|
+
const total = (uses[id] ?? []).reduce(
|
|
87
|
+
(sum, usedAt) => sum + 0.5 ** ((now() - usedAt) / DAY_MS / HALF_LIFE_DAYS),
|
|
88
|
+
0
|
|
89
|
+
);
|
|
90
|
+
return Math.min(1, total / USES_FOR_FULL_SCORE);
|
|
91
|
+
};
|
|
92
|
+
return { record, score };
|
|
93
|
+
};
|
|
94
|
+
const SCOPE_BOOST = 0.3;
|
|
95
|
+
const FRECENCY_BOOST = 0.2;
|
|
96
|
+
const WEIGHT_BOOST = 0.1;
|
|
97
|
+
const NO_FRECENCY = { score: () => 0 };
|
|
98
|
+
const TIERS = { first: 0, last: 2 };
|
|
99
|
+
const tierOf = (item) => TIERS[item.place] ?? 1;
|
|
100
|
+
const visibleItems = (items, { text, prefix, page }) => {
|
|
101
|
+
if (page) return items.filter((item) => item.parent === page);
|
|
102
|
+
if (prefix) return items.filter((item) => item.prefix === prefix);
|
|
103
|
+
if (!text) return items.filter((item) => !item.parent);
|
|
104
|
+
return items.filter((item) => item.searchFromRoot !== false);
|
|
105
|
+
};
|
|
106
|
+
const detailText = (item) => [item.subtitle, ...item.keywords ?? []].filter(Boolean).join(" ");
|
|
107
|
+
const matchScore = (text, item, parentTitle) => Math.max(
|
|
108
|
+
fuzzyScore(text, item.title),
|
|
109
|
+
parentTitle ? fuzzyScore(text, `${parentTitle} ${item.title}`) : 0,
|
|
110
|
+
fuzzyScore(text, detailText(item), { subsequence: false })
|
|
111
|
+
);
|
|
112
|
+
const rank = (items, { text = "", prefix = null, page = null, scopes = [], frecency = NO_FRECENCY }) => {
|
|
113
|
+
const titles = new Map(items.map((item) => [item.id, item.title]));
|
|
114
|
+
return visibleItems(items, { text, prefix, page }).map((item) => {
|
|
115
|
+
var _a;
|
|
116
|
+
const parentTitle = item.parent && !page ? titles.get(item.parent) : void 0;
|
|
117
|
+
const match = text ? matchScore(text, item, parentTitle) : 1;
|
|
118
|
+
const inScope = ((_a = item.scopes) == null ? void 0 : _a.some((scope) => scopes.includes(scope))) ? 1 : 0;
|
|
119
|
+
const score = match + SCOPE_BOOST * inScope + FRECENCY_BOOST * frecency.score(item.id) + WEIGHT_BOOST * (item.weight ?? 0);
|
|
120
|
+
return {
|
|
121
|
+
item: parentTitle ? { ...item, parentTitle } : item,
|
|
122
|
+
match,
|
|
123
|
+
score
|
|
124
|
+
};
|
|
125
|
+
}).filter((entry) => entry.match > 0).sort((a, b) => {
|
|
126
|
+
const tier = tierOf(a.item) - tierOf(b.item);
|
|
127
|
+
if (tier) return tier;
|
|
128
|
+
return a.item.place ? 0 : b.score - a.score;
|
|
129
|
+
}).map((entry) => entry.item);
|
|
130
|
+
};
|
|
131
|
+
const groupBySection = (items) => {
|
|
132
|
+
const groups = /* @__PURE__ */ new Map();
|
|
133
|
+
items.forEach((item) => {
|
|
134
|
+
const section = item.section ?? null;
|
|
135
|
+
if (!groups.has(section)) groups.set(section, []);
|
|
136
|
+
groups.get(section).push(item);
|
|
137
|
+
});
|
|
138
|
+
return [...groups].map(([section, members]) => ({ section, items: members }));
|
|
139
|
+
};
|
|
140
|
+
export {
|
|
141
|
+
createFrecency,
|
|
142
|
+
fuzzyScore,
|
|
143
|
+
groupBySection,
|
|
144
|
+
normalize,
|
|
145
|
+
parseQuery,
|
|
146
|
+
rank
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=core.js.map
|
package/dist/core.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.js","sources":["../src/core/match.js","../src/core/parse.js","../src/core/frecency.js","../src/core/rank.js"],"sourcesContent":["const COMBINING_MARKS = /[\\u0300-\\u036f]/g;\n\nconst EXACT = 1;\nconst PREFIX = 0.9;\nconst WORD_START = 0.8;\nconst ALL_WORDS = 0.7;\nconst SUBSEQUENCE_MAX = 0.6;\nconst SUBSEQUENCE_MIN = 0.2;\n\nconst NORMALIZED_CACHE_LIMIT = 5000;\nconst normalized = new Map();\n\nexport const normalize = value => {\n const raw = String(value ?? '');\n const cached = normalized.get(raw);\n if (cached !== undefined) return cached;\n if (normalized.size >= NORMALIZED_CACHE_LIMIT) normalized.clear();\n const result = raw\n .normalize('NFD')\n .replace(COMBINING_MARKS, '')\n .toLowerCase()\n .trim();\n normalized.set(raw, result);\n return result;\n};\n\nconst tightestSpan = (query, text) => {\n let best = Infinity;\n for (\n let start = text.indexOf(query[0]);\n start !== -1;\n start = text.indexOf(query[0], start + 1)\n ) {\n let cursor = start;\n let matched = true;\n for (let i = 1; i < query.length; i += 1) {\n cursor = text.indexOf(query[i], cursor + 1);\n if (cursor === -1) {\n matched = false;\n break;\n }\n }\n if (matched) best = Math.min(best, cursor - start + 1);\n }\n return best;\n};\n\nexport const fuzzyScore = (query, text, { subsequence = true } = {}) => {\n const needle = normalize(query);\n if (!needle) return EXACT;\n const haystack = normalize(text);\n if (haystack === needle) return EXACT;\n if (haystack.startsWith(needle)) return PREFIX;\n\n const words = haystack.split(/\\s+/);\n if (words.some(word => word.startsWith(needle))) return WORD_START;\n\n const needleWords = needle.split(/\\s+/);\n if (needleWords.every(part => words.some(word => word.startsWith(part)))) {\n return ALL_WORDS;\n }\n\n if (!subsequence) return 0;\n const compact = needle.replace(/\\s+/g, '');\n const span = tightestSpan(compact, haystack);\n if (span === Infinity) return 0;\n return (\n SUBSEQUENCE_MIN +\n (SUBSEQUENCE_MAX - SUBSEQUENCE_MIN) * (compact.length / span)\n );\n};\n","export const parseQuery = (input, prefixes = []) => {\n const trimmed = String(input ?? '').trim();\n const prefix = prefixes.includes(trimmed[0]) ? trimmed[0] : null;\n const text = prefix ? trimmed.slice(1).trim() : trimmed;\n return { prefix, text };\n};\n","const DAY_MS = 24 * 60 * 60 * 1000;\nconst HALF_LIFE_DAYS = 7;\nconst USES_FOR_FULL_SCORE = 3;\nconst MAX_IDLE_DAYS = 30;\n\nconst load = (storage, key) => {\n try {\n return JSON.parse(storage.getItem(key)) ?? {};\n } catch {\n return {};\n }\n};\n\nexport const createFrecency = ({\n storage,\n key,\n now = Date.now,\n limit = 10,\n}) => {\n const uses = load(storage, key);\n\n const record = id => {\n const at = now();\n uses[id] = [...(uses[id] ?? []), at].slice(-limit);\n Object.keys(uses).forEach(other => {\n if (at - uses[other].at(-1) > MAX_IDLE_DAYS * DAY_MS) delete uses[other];\n });\n storage.setItem(key, JSON.stringify(uses));\n };\n\n const score = id => {\n const total = (uses[id] ?? []).reduce(\n (sum, usedAt) =>\n sum + 0.5 ** ((now() - usedAt) / DAY_MS / HALF_LIFE_DAYS),\n 0\n );\n return Math.min(1, total / USES_FOR_FULL_SCORE);\n };\n\n return { record, score };\n};\n","import { fuzzyScore } from './match';\n\nconst SCOPE_BOOST = 0.3;\nconst FRECENCY_BOOST = 0.2;\nconst WEIGHT_BOOST = 0.1;\n\nconst NO_FRECENCY = { score: () => 0 };\nconst TIERS = { first: 0, last: 2 };\nconst tierOf = item => TIERS[item.place] ?? 1;\n\nconst visibleItems = (items, { text, prefix, page }) => {\n if (page) return items.filter(item => item.parent === page);\n if (prefix) return items.filter(item => item.prefix === prefix);\n if (!text) return items.filter(item => !item.parent);\n return items.filter(item => item.searchFromRoot !== false);\n};\n\nconst detailText = item =>\n [item.subtitle, ...(item.keywords ?? [])].filter(Boolean).join(' ');\n\nconst matchScore = (text, item, parentTitle) =>\n Math.max(\n fuzzyScore(text, item.title),\n parentTitle ? fuzzyScore(text, `${parentTitle} ${item.title}`) : 0,\n fuzzyScore(text, detailText(item), { subsequence: false })\n );\n\nexport const rank = (\n items,\n { text = '', prefix = null, page = null, scopes = [], frecency = NO_FRECENCY }\n) => {\n const titles = new Map(items.map(item => [item.id, item.title]));\n\n return visibleItems(items, { text, prefix, page })\n .map(item => {\n const parentTitle =\n item.parent && !page ? titles.get(item.parent) : undefined;\n const match = text ? matchScore(text, item, parentTitle) : 1;\n const inScope = item.scopes?.some(scope => scopes.includes(scope))\n ? 1\n : 0;\n const score =\n match +\n SCOPE_BOOST * inScope +\n FRECENCY_BOOST * frecency.score(item.id) +\n WEIGHT_BOOST * (item.weight ?? 0);\n return {\n item: parentTitle ? { ...item, parentTitle } : item,\n match,\n score,\n };\n })\n .filter(entry => entry.match > 0)\n .sort((a, b) => {\n const tier = tierOf(a.item) - tierOf(b.item);\n if (tier) return tier;\n return a.item.place ? 0 : b.score - a.score;\n })\n .map(entry => entry.item);\n};\n\nexport const groupBySection = items => {\n const groups = new Map();\n items.forEach(item => {\n const section = item.section ?? null;\n if (!groups.has(section)) groups.set(section, []);\n groups.get(section).push(item);\n });\n return [...groups].map(([section, members]) => ({ section, items: members }));\n};\n"],"names":[],"mappings":"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"}
|