@birdapi/velinstyle 0.9.0 → 1.0.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.de.md +7 -7
- package/README.md +7 -7
- package/cli/cli-manifest.json +1 -1
- package/core/a11y/component-contracts.json +1 -1
- package/core/search/worker-client.js +2 -3
- package/dist/chunks/attributes-ADOKLKA7.js +391 -0
- package/dist/chunks/chunk-IEHKRARD.js +109 -0
- package/dist/chunks/runtime-entry.js +1 -1
- package/dist/chunks/velin-search-FDHEMCSO.js +557 -0
- package/dist/chunks/worker-client-MPT7PNQ4.js +47 -0
- package/dist/llms.txt +1 -1
- package/dist/search-index.json +2 -2
- package/dist/velin-agent.json +2 -2
- package/dist/velinstyle-components.iife.js +3 -3
- package/dist/velinstyle-components.js +2 -1
- package/dist/velinstyle-components.min.js +1 -1
- package/dist/velinstyle.css +12 -19
- package/dist/velinstyle.min.css +1 -1
- package/package.json +1 -1
- package/dist/llms.test.txt +0 -40
- package/dist/search-index.test.json +0 -1773
- package/dist/velin-agent.test.json +0 -684
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeHTMLAttribute,
|
|
3
|
+
sanitizeSearchUrl
|
|
4
|
+
} from "./chunk-RDE3QE7Z.js";
|
|
5
|
+
|
|
6
|
+
// core/search/types.js
|
|
7
|
+
var SEARCH_CATEGORIES = (
|
|
8
|
+
/** @type {const} */
|
|
9
|
+
["docs", "components", "api", "examples"]
|
|
10
|
+
);
|
|
11
|
+
var CATEGORY_BOOST = {
|
|
12
|
+
components: 1.25,
|
|
13
|
+
api: 1.15,
|
|
14
|
+
docs: 1,
|
|
15
|
+
examples: 0.9
|
|
16
|
+
};
|
|
17
|
+
function normalizeEntry(entry) {
|
|
18
|
+
if (!entry || typeof entry !== "object") return null;
|
|
19
|
+
const e = (
|
|
20
|
+
/** @type {Record<string, unknown>} */
|
|
21
|
+
entry
|
|
22
|
+
);
|
|
23
|
+
const id = String(e.id || e.url || e.title || "").trim();
|
|
24
|
+
const title = String(e.title || "").trim();
|
|
25
|
+
const url = String(e.url || "").trim();
|
|
26
|
+
if (!id || !title) return null;
|
|
27
|
+
const keywords = Array.isArray(e.keywords) ? e.keywords.map((k) => String(k)) : typeof e.keywords === "string" ? e.keywords.split(/\s+/).filter(Boolean) : [];
|
|
28
|
+
let category = (
|
|
29
|
+
/** @type {SearchCategory} */
|
|
30
|
+
SEARCH_CATEGORIES.includes(
|
|
31
|
+
/** @type {SearchCategory} */
|
|
32
|
+
e.category
|
|
33
|
+
) ? e.category : "docs"
|
|
34
|
+
);
|
|
35
|
+
if (!e.category && typeof e.url === "string") {
|
|
36
|
+
if (e.url.includes("/components/")) category = "components";
|
|
37
|
+
else if (e.url.includes("/cli/") || e.url.includes("/api/")) category = "api";
|
|
38
|
+
else if (e.url.includes("samples/") || e.url.includes("examples/")) category = "examples";
|
|
39
|
+
}
|
|
40
|
+
const safeUrl = url ? sanitizeSearchUrl(url) : `#${id}`;
|
|
41
|
+
return {
|
|
42
|
+
id,
|
|
43
|
+
title,
|
|
44
|
+
excerpt: String(e.excerpt || e.section || "").slice(0, 200),
|
|
45
|
+
url: safeUrl,
|
|
46
|
+
category: (
|
|
47
|
+
/** @type {SearchCategory} */
|
|
48
|
+
category
|
|
49
|
+
),
|
|
50
|
+
keywords,
|
|
51
|
+
weight: typeof e.weight === "number" ? e.weight : 1
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// core/search/engine.js
|
|
56
|
+
var VelinSearchEngine = class {
|
|
57
|
+
constructor() {
|
|
58
|
+
this._entries = [];
|
|
59
|
+
}
|
|
60
|
+
/** @param {import('./types.js').SearchEntry[]} entries */
|
|
61
|
+
setEntries(entries) {
|
|
62
|
+
this._entries = entries.map((e) => normalizeEntry(e)).filter(Boolean);
|
|
63
|
+
}
|
|
64
|
+
/** @param {import('./types.js').SearchEntry[]} entries */
|
|
65
|
+
addEntries(entries) {
|
|
66
|
+
const next = entries.map((e) => normalizeEntry(e)).filter(Boolean);
|
|
67
|
+
const ids = new Set(this._entries.map((e) => e.id));
|
|
68
|
+
for (const e of next) {
|
|
69
|
+
if (!ids.has(e.id)) {
|
|
70
|
+
this._entries.push(e);
|
|
71
|
+
ids.add(e.id);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} query
|
|
77
|
+
* @param {object} [opts]
|
|
78
|
+
* @param {number} [opts.limit]
|
|
79
|
+
* @param {number} [opts.minChars]
|
|
80
|
+
* @param {number} [opts.fuzzy] 0–1 threshold for typo tolerance
|
|
81
|
+
* @param {import('./types.js').SearchCategory[]} [opts.categories]
|
|
82
|
+
*/
|
|
83
|
+
query(query, opts = {}) {
|
|
84
|
+
const q = String(query || "").trim().toLowerCase();
|
|
85
|
+
const minChars = opts.minChars ?? 2;
|
|
86
|
+
const limit = opts.limit ?? 12;
|
|
87
|
+
const fuzzy = opts.fuzzy ?? 0.2;
|
|
88
|
+
const categories = opts.categories;
|
|
89
|
+
if (q.length < minChars) {
|
|
90
|
+
return { results: [], groups: {} };
|
|
91
|
+
}
|
|
92
|
+
const scored = [];
|
|
93
|
+
for (const entry of this._entries) {
|
|
94
|
+
if (categories && categories.length && !categories.includes(entry.category)) continue;
|
|
95
|
+
const s = scoreEntry(entry, q, fuzzy);
|
|
96
|
+
if (s > 0) scored.push({ entry, score: s });
|
|
97
|
+
}
|
|
98
|
+
scored.sort((a, b) => b.score - a.score);
|
|
99
|
+
const results = scored.slice(0, limit).map((x) => ({ ...x.entry, _score: x.score }));
|
|
100
|
+
const groups = {};
|
|
101
|
+
for (const r of results) {
|
|
102
|
+
if (!groups[r.category]) groups[r.category] = [];
|
|
103
|
+
groups[r.category].push(r);
|
|
104
|
+
}
|
|
105
|
+
return { results, groups };
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
function scoreEntry(entry, q, fuzzy) {
|
|
109
|
+
const title = entry.title.toLowerCase();
|
|
110
|
+
const excerpt = (entry.excerpt || "").toLowerCase();
|
|
111
|
+
const keywords = (entry.keywords || []).join(" ").toLowerCase();
|
|
112
|
+
const boost = (CATEGORY_BOOST[entry.category] || 1) * (entry.weight || 1);
|
|
113
|
+
let score = 0;
|
|
114
|
+
if (title === q) score = 100;
|
|
115
|
+
else if (title.startsWith(q)) score = 70;
|
|
116
|
+
else if (title.includes(q)) score = 50;
|
|
117
|
+
else if (keywords.includes(q)) score = 35;
|
|
118
|
+
else if (excerpt.includes(q)) score = 25;
|
|
119
|
+
else if (fuzzy > 0 && fuzzyMatch(title, q, fuzzy)) score = 40;
|
|
120
|
+
else if (fuzzy > 0 && fuzzyMatch(keywords, q, fuzzy)) score = 28;
|
|
121
|
+
else if (fuzzy > 0 && fuzzyMatch(excerpt, q, fuzzy)) score = 18;
|
|
122
|
+
else if (subsequenceMatch(title, q)) score = 22;
|
|
123
|
+
else if (fuzzy > 0) {
|
|
124
|
+
for (const word of title.split(/[^a-z0-9]+/)) {
|
|
125
|
+
if (word.length >= 3 && fuzzyMatch(word, q, fuzzy)) {
|
|
126
|
+
score = 32;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return score > 0 ? score * boost : 0;
|
|
132
|
+
}
|
|
133
|
+
function subsequenceMatch(haystack, needle) {
|
|
134
|
+
let j = 0;
|
|
135
|
+
for (let i = 0; i < haystack.length && j < needle.length; i++) {
|
|
136
|
+
if (haystack[i] === needle[j]) j++;
|
|
137
|
+
}
|
|
138
|
+
return j === needle.length;
|
|
139
|
+
}
|
|
140
|
+
function fuzzyMatch(text, query, threshold) {
|
|
141
|
+
if (!text || !query) return false;
|
|
142
|
+
if (text.includes(query)) return true;
|
|
143
|
+
if (subsequenceMatch(text, query)) return true;
|
|
144
|
+
const maxDist = Math.max(1, Math.floor(query.length * threshold * 2));
|
|
145
|
+
return levenshtein(text.slice(0, Math.min(text.length, query.length + 8)), query) <= maxDist;
|
|
146
|
+
}
|
|
147
|
+
function levenshtein(a, b) {
|
|
148
|
+
const m = a.length;
|
|
149
|
+
const n = b.length;
|
|
150
|
+
if (m === 0) return n;
|
|
151
|
+
if (n === 0) return m;
|
|
152
|
+
const dp = new Uint16Array((n + 1) * (m + 1));
|
|
153
|
+
for (let j = 0; j <= n; j++) dp[j] = j;
|
|
154
|
+
for (let i = 1; i <= m; i++) {
|
|
155
|
+
dp[i * (n + 1)] = i;
|
|
156
|
+
for (let j = 1; j <= n; j++) {
|
|
157
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
158
|
+
dp[i * (n + 1) + j] = Math.min(
|
|
159
|
+
dp[(i - 1) * (n + 1) + j] + 1,
|
|
160
|
+
dp[i * (n + 1) + (j - 1)] + 1,
|
|
161
|
+
dp[(i - 1) * (n + 1) + (j - 1)] + cost
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return dp[m * (n + 1) + n];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// core/search/highlight.js
|
|
169
|
+
function highlightHtml(text, query) {
|
|
170
|
+
const raw = String(text || "");
|
|
171
|
+
const q = String(query || "").trim();
|
|
172
|
+
if (!q || q.length < 2) return escapeHtml(raw);
|
|
173
|
+
const lower = raw.toLowerCase();
|
|
174
|
+
const ql = q.toLowerCase();
|
|
175
|
+
let start = lower.indexOf(ql);
|
|
176
|
+
let len = q.length;
|
|
177
|
+
if (start === -1) {
|
|
178
|
+
start = fuzzySubsequenceIndex(lower, ql);
|
|
179
|
+
if (start === -1) return escapeHtml(raw);
|
|
180
|
+
len = Math.min(q.length, raw.length - start);
|
|
181
|
+
}
|
|
182
|
+
return escapeHtml(raw.slice(0, start)) + '<mark class="velin-search-hit">' + escapeHtml(raw.slice(start, start + len)) + "</mark>" + escapeHtml(raw.slice(start + len));
|
|
183
|
+
}
|
|
184
|
+
function escapeHtml(s) {
|
|
185
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
186
|
+
}
|
|
187
|
+
function fuzzySubsequenceIndex(haystack, needle) {
|
|
188
|
+
let j = 0;
|
|
189
|
+
let start = -1;
|
|
190
|
+
for (let i = 0; i < haystack.length && j < needle.length; i++) {
|
|
191
|
+
if (haystack[i] === needle[j]) {
|
|
192
|
+
if (j === 0) start = i;
|
|
193
|
+
j++;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return j === needle.length ? start : -1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// core/search/docs-url.js
|
|
200
|
+
var DOCS_MOUNT = "/docs/";
|
|
201
|
+
var DOC_ROOT_SEGMENTS = /* @__PURE__ */ new Set([
|
|
202
|
+
"getting-started",
|
|
203
|
+
"extend",
|
|
204
|
+
"guides",
|
|
205
|
+
"utilities",
|
|
206
|
+
"components",
|
|
207
|
+
"forms",
|
|
208
|
+
"layout",
|
|
209
|
+
"content",
|
|
210
|
+
"customize",
|
|
211
|
+
"animations",
|
|
212
|
+
"about",
|
|
213
|
+
"helpers",
|
|
214
|
+
"generated",
|
|
215
|
+
"migration"
|
|
216
|
+
]);
|
|
217
|
+
function getDocsBaseUrl() {
|
|
218
|
+
if (typeof document === "undefined") {
|
|
219
|
+
return "https://example.invalid/docs/";
|
|
220
|
+
}
|
|
221
|
+
const script = document.querySelector('script[src*="doc-search"]');
|
|
222
|
+
if (script?.src) {
|
|
223
|
+
const u = new URL(script.src);
|
|
224
|
+
const i = u.pathname.indexOf(DOCS_MOUNT);
|
|
225
|
+
if (i !== -1) {
|
|
226
|
+
return u.origin + u.pathname.slice(0, i + DOCS_MOUNT.length);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
const path = window.location.pathname.replace(/\\/g, "/");
|
|
230
|
+
const idx = path.indexOf(DOCS_MOUNT);
|
|
231
|
+
if (idx >= 0) {
|
|
232
|
+
return new URL(path.slice(0, idx + DOCS_MOUNT.length), window.location.origin).href;
|
|
233
|
+
}
|
|
234
|
+
return new URL("./", window.location.href).href;
|
|
235
|
+
}
|
|
236
|
+
function splitUrlHash(url) {
|
|
237
|
+
const i = url.indexOf("#");
|
|
238
|
+
if (i === -1) return { path: url, hash: "" };
|
|
239
|
+
return { path: url.slice(0, i), hash: url.slice(i) };
|
|
240
|
+
}
|
|
241
|
+
function relativizeDocsPathname(pathname) {
|
|
242
|
+
const docsIdx = pathname.indexOf(DOCS_MOUNT);
|
|
243
|
+
if (docsIdx < 0) return null;
|
|
244
|
+
const rest = pathname.slice(docsIdx + DOCS_MOUNT.length);
|
|
245
|
+
const parts = rest.split("/").filter(Boolean);
|
|
246
|
+
if (parts.length >= 2 && DOC_ROOT_SEGMENTS.has(parts[0]) && DOC_ROOT_SEGMENTS.has(parts[1]) && parts[0] !== parts[1]) {
|
|
247
|
+
return parts.slice(1).join("/");
|
|
248
|
+
}
|
|
249
|
+
return rest;
|
|
250
|
+
}
|
|
251
|
+
function resolveDocsSearchUrl(url) {
|
|
252
|
+
if (!url || url === "#") return "";
|
|
253
|
+
if (typeof window === "undefined") return url;
|
|
254
|
+
const { path, hash } = splitUrlHash(url);
|
|
255
|
+
if (!path) return hash || "";
|
|
256
|
+
let rel = path;
|
|
257
|
+
if (/^https?:\/\//i.test(path)) {
|
|
258
|
+
try {
|
|
259
|
+
const fixed = relativizeDocsPathname(new URL(path).pathname);
|
|
260
|
+
rel = fixed ?? path;
|
|
261
|
+
} catch {
|
|
262
|
+
return "";
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
const target = new URL(rel || "", getDocsBaseUrl());
|
|
267
|
+
return target.href + (hash && !target.href.includes("#") ? hash : "");
|
|
268
|
+
} catch {
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// core/search/index.js
|
|
274
|
+
var defaultEngine = new VelinSearchEngine();
|
|
275
|
+
function parseIndexPayload(data) {
|
|
276
|
+
if (Array.isArray(data)) return data;
|
|
277
|
+
if (data && Array.isArray(data.entries)) return data.entries;
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
function createSearch(config = {}) {
|
|
281
|
+
const engine = new VelinSearchEngine();
|
|
282
|
+
const wantWorker = config.worker === true;
|
|
283
|
+
let workerClient = null;
|
|
284
|
+
async function getWorker() {
|
|
285
|
+
if (!wantWorker || workerClient) return workerClient;
|
|
286
|
+
if (typeof Worker === "undefined") return null;
|
|
287
|
+
const { createSearchWorker } = await import("./worker-client-MPT7PNQ4.js");
|
|
288
|
+
workerClient = createSearchWorker(config.workerUrl);
|
|
289
|
+
return workerClient;
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
engine,
|
|
293
|
+
async loadIndex(source) {
|
|
294
|
+
let entries = [];
|
|
295
|
+
if (typeof source === "string") {
|
|
296
|
+
const res = await fetch(source);
|
|
297
|
+
entries = parseIndexPayload(await res.json());
|
|
298
|
+
} else if (Array.isArray(source)) {
|
|
299
|
+
entries = source;
|
|
300
|
+
}
|
|
301
|
+
engine.setEntries(entries);
|
|
302
|
+
const w = await getWorker();
|
|
303
|
+
if (w) await w.setEntries(engine._entries);
|
|
304
|
+
return entries.length;
|
|
305
|
+
},
|
|
306
|
+
async query(q, opts) {
|
|
307
|
+
const w = await getWorker();
|
|
308
|
+
if (w) return w.query(q, opts);
|
|
309
|
+
return engine.query(q, opts);
|
|
310
|
+
},
|
|
311
|
+
addEntries(entries) {
|
|
312
|
+
engine.addEntries(entries);
|
|
313
|
+
void getWorker().then((w) => w?.setEntries(engine._entries));
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// components/velin-search.js
|
|
319
|
+
var CATEGORY_LABELS = {
|
|
320
|
+
docs: "Documentation",
|
|
321
|
+
components: "Components",
|
|
322
|
+
api: "API",
|
|
323
|
+
examples: "Examples"
|
|
324
|
+
};
|
|
325
|
+
var VelinSearch = class extends HTMLElement {
|
|
326
|
+
static get observedAttributes() {
|
|
327
|
+
return ["index", "categories", "min-chars", "fuzzy", "placeholder", "debounce"];
|
|
328
|
+
}
|
|
329
|
+
constructor() {
|
|
330
|
+
super();
|
|
331
|
+
this._search = createSearch();
|
|
332
|
+
this._debounceTimer = null;
|
|
333
|
+
this._activeIndex = -1;
|
|
334
|
+
this._flatResults = [];
|
|
335
|
+
this._indexLoaded = false;
|
|
336
|
+
this._listId = `velin-search-listbox-${Math.random().toString(36).slice(2, 9)}`;
|
|
337
|
+
this._mounted = false;
|
|
338
|
+
}
|
|
339
|
+
connectedCallback() {
|
|
340
|
+
if (this._mounted) return;
|
|
341
|
+
this._mounted = true;
|
|
342
|
+
this._ensureMarkup();
|
|
343
|
+
this._loadIndex();
|
|
344
|
+
this._bindEvents();
|
|
345
|
+
}
|
|
346
|
+
disconnectedCallback() {
|
|
347
|
+
this._teardown?.();
|
|
348
|
+
this._mounted = false;
|
|
349
|
+
}
|
|
350
|
+
get indexUrl() {
|
|
351
|
+
return this.getAttribute("index") || "/search-index.json";
|
|
352
|
+
}
|
|
353
|
+
get minChars() {
|
|
354
|
+
return parseInt(this.getAttribute("min-chars") || "2", 10);
|
|
355
|
+
}
|
|
356
|
+
get fuzzy() {
|
|
357
|
+
const v = this.getAttribute("fuzzy");
|
|
358
|
+
return v === null ? 0.2 : parseFloat(v) || 0;
|
|
359
|
+
}
|
|
360
|
+
get categories() {
|
|
361
|
+
const raw = this.getAttribute("categories");
|
|
362
|
+
if (!raw) return null;
|
|
363
|
+
return raw.split(",").map((c) => c.trim()).filter((c) => SEARCH_CATEGORIES.includes(c));
|
|
364
|
+
}
|
|
365
|
+
get debounceMs() {
|
|
366
|
+
return parseInt(this.getAttribute("debounce") || "120", 10);
|
|
367
|
+
}
|
|
368
|
+
_ensureMarkup() {
|
|
369
|
+
if (this.querySelector("[data-velin-search-input]")) return;
|
|
370
|
+
const ph = escapeHTMLAttribute(this.getAttribute("placeholder") || "Search\u2026");
|
|
371
|
+
const label = escapeHTMLAttribute(this.getAttribute("aria-label") || "Search");
|
|
372
|
+
this.innerHTML = `
|
|
373
|
+
<div class="velin-search">
|
|
374
|
+
<div class="velin-search__field">
|
|
375
|
+
<input type="search" class="velin-search__input" data-velin-search-input
|
|
376
|
+
placeholder="${ph}" autocomplete="off" role="combobox" aria-expanded="false"
|
|
377
|
+
aria-label="${label}" aria-controls="${this._listId}" aria-autocomplete="list" />
|
|
378
|
+
</div>
|
|
379
|
+
<div class="velin-search__status velin-visually-hidden" data-velin-search-status aria-live="polite" aria-atomic="true"></div>
|
|
380
|
+
<div class="velin-search__results" data-velin-search-results role="listbox"
|
|
381
|
+
id="${this._listId}" hidden></div>
|
|
382
|
+
</div>
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
_inputEl() {
|
|
386
|
+
return this.querySelector("[data-velin-search-input]");
|
|
387
|
+
}
|
|
388
|
+
_resultsEl() {
|
|
389
|
+
return this.querySelector("[data-velin-search-results]");
|
|
390
|
+
}
|
|
391
|
+
async _loadIndex() {
|
|
392
|
+
try {
|
|
393
|
+
await this._search.loadIndex(this.indexUrl);
|
|
394
|
+
this._indexLoaded = true;
|
|
395
|
+
} catch {
|
|
396
|
+
this._indexLoaded = false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
_bindEvents() {
|
|
400
|
+
const input = this._inputEl();
|
|
401
|
+
const panel = this._resultsEl();
|
|
402
|
+
if (!input || !panel) return;
|
|
403
|
+
input.addEventListener("input", () => {
|
|
404
|
+
clearTimeout(this._debounceTimer);
|
|
405
|
+
this._debounceTimer = setTimeout(() => this._runQuery(input.value), this.debounceMs);
|
|
406
|
+
});
|
|
407
|
+
input.addEventListener("focus", () => {
|
|
408
|
+
if (input.value.trim().length >= this.minChars) this._runQuery(input.value);
|
|
409
|
+
});
|
|
410
|
+
input.addEventListener("keydown", (e) => this._onKeydown(e, input, panel));
|
|
411
|
+
const onDocClick = (e) => {
|
|
412
|
+
if (!this.contains(e.target)) this._hide(panel, input);
|
|
413
|
+
};
|
|
414
|
+
document.addEventListener("click", onDocClick);
|
|
415
|
+
this._teardown = () => {
|
|
416
|
+
document.removeEventListener("click", onDocClick);
|
|
417
|
+
clearTimeout(this._debounceTimer);
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
async _runQuery(raw) {
|
|
421
|
+
const input = this._inputEl();
|
|
422
|
+
const panel = this._resultsEl();
|
|
423
|
+
if (!input || !panel) return;
|
|
424
|
+
const q = raw.trim();
|
|
425
|
+
if (q.length < this.minChars) {
|
|
426
|
+
this._hide(panel, input);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (!this._indexLoaded) await this._loadIndex();
|
|
430
|
+
const { groups } = this._search.query(q, {
|
|
431
|
+
minChars: this.minChars,
|
|
432
|
+
fuzzy: this.fuzzy,
|
|
433
|
+
categories: this.categories || void 0,
|
|
434
|
+
limit: 12
|
|
435
|
+
});
|
|
436
|
+
this._activeIndex = -1;
|
|
437
|
+
this._renderResults(panel, input, q, groups);
|
|
438
|
+
if (this._flatResults.length) {
|
|
439
|
+
panel.hidden = false;
|
|
440
|
+
panel.classList.add("velin-search__results--open");
|
|
441
|
+
input.setAttribute("aria-expanded", "true");
|
|
442
|
+
} else {
|
|
443
|
+
this._hide(panel, input);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
_renderResults(panel, input, q, groups) {
|
|
447
|
+
panel.innerHTML = "";
|
|
448
|
+
const order = this.categories || [...SEARCH_CATEGORIES];
|
|
449
|
+
this._flatResults = [];
|
|
450
|
+
let globalIdx = 0;
|
|
451
|
+
for (const cat of order) {
|
|
452
|
+
const items = groups[cat];
|
|
453
|
+
if (!items?.length) continue;
|
|
454
|
+
const groupId = `${this._listId}-group-${cat}`;
|
|
455
|
+
const group = document.createElement("div");
|
|
456
|
+
group.setAttribute("role", "group");
|
|
457
|
+
group.setAttribute("aria-labelledby", groupId);
|
|
458
|
+
const heading = document.createElement("div");
|
|
459
|
+
heading.className = "velin-search__group-label";
|
|
460
|
+
heading.id = groupId;
|
|
461
|
+
heading.textContent = CATEGORY_LABELS[cat] || cat;
|
|
462
|
+
group.appendChild(heading);
|
|
463
|
+
for (const item of items) {
|
|
464
|
+
const a = document.createElement("a");
|
|
465
|
+
const navHref = resolveDocsSearchUrl(item.url);
|
|
466
|
+
if (navHref) a.href = navHref;
|
|
467
|
+
a.className = "velin-search__item";
|
|
468
|
+
a.setAttribute("role", "option");
|
|
469
|
+
a.id = `velin-search-opt-${globalIdx}`;
|
|
470
|
+
a.innerHTML = `<span class="velin-search__title">${highlightHtml(item.title, q)}</span><span class="velin-search__excerpt">${highlightHtml(item.excerpt || "", q)}</span>`;
|
|
471
|
+
a.addEventListener(
|
|
472
|
+
"click",
|
|
473
|
+
(e) => {
|
|
474
|
+
e.preventDefault();
|
|
475
|
+
if (navHref) window.location.assign(navHref);
|
|
476
|
+
},
|
|
477
|
+
true
|
|
478
|
+
);
|
|
479
|
+
a.addEventListener("mouseenter", () => this._setActive(globalIdx, panel, input));
|
|
480
|
+
group.appendChild(a);
|
|
481
|
+
this._flatResults.push(item);
|
|
482
|
+
globalIdx++;
|
|
483
|
+
}
|
|
484
|
+
panel.appendChild(group);
|
|
485
|
+
}
|
|
486
|
+
const status = this._statusEl();
|
|
487
|
+
if (status) {
|
|
488
|
+
status.textContent = this._flatResults.length ? `${this._flatResults.length} results` : "No results";
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
_statusEl() {
|
|
492
|
+
return this.querySelector("[data-velin-search-status]");
|
|
493
|
+
}
|
|
494
|
+
_setActive(idx, panel, input) {
|
|
495
|
+
this._activeIndex = idx;
|
|
496
|
+
panel.querySelectorAll(".velin-search__item").forEach((el, i) => {
|
|
497
|
+
el.classList.toggle("velin-search__item--active", i === idx);
|
|
498
|
+
if (i === idx) {
|
|
499
|
+
input.setAttribute("aria-activedescendant", el.id);
|
|
500
|
+
el.scrollIntoView({ block: "nearest" });
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
_onKeydown(e, input, panel) {
|
|
505
|
+
if (e.key === "Escape") {
|
|
506
|
+
this._hide(panel, input);
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (!this._flatResults.length) return;
|
|
510
|
+
if (e.key === "ArrowDown") {
|
|
511
|
+
e.preventDefault();
|
|
512
|
+
const next = this._activeIndex < this._flatResults.length - 1 ? this._activeIndex + 1 : 0;
|
|
513
|
+
this._setActive(next, panel, input);
|
|
514
|
+
} else if (e.key === "ArrowUp") {
|
|
515
|
+
e.preventDefault();
|
|
516
|
+
const prev = this._activeIndex > 0 ? this._activeIndex - 1 : this._flatResults.length - 1;
|
|
517
|
+
this._setActive(prev, panel, input);
|
|
518
|
+
} else if (e.key === "Enter") {
|
|
519
|
+
const target = this._activeIndex >= 0 ? this._flatResults[this._activeIndex] : this._flatResults[0];
|
|
520
|
+
const navHref = target?.url ? resolveDocsSearchUrl(target.url) : "";
|
|
521
|
+
if (navHref) {
|
|
522
|
+
e.preventDefault();
|
|
523
|
+
window.location.assign(navHref);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
_hide(panel, input) {
|
|
528
|
+
panel.hidden = true;
|
|
529
|
+
panel.classList.remove("velin-search__results--open");
|
|
530
|
+
input.setAttribute("aria-expanded", "false");
|
|
531
|
+
input.removeAttribute("aria-activedescendant");
|
|
532
|
+
this._activeIndex = -1;
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var velin_search_default = VelinSearch;
|
|
536
|
+
if (typeof HTMLElement !== "undefined" && typeof customElements !== "undefined" && !customElements.get("velin-search")) {
|
|
537
|
+
customElements.define("velin-search", VelinSearch);
|
|
538
|
+
}
|
|
539
|
+
function bindDeclarativeSearch(root = document) {
|
|
540
|
+
root.querySelectorAll("[velin-search-input]").forEach((input) => {
|
|
541
|
+
if (input.dataset.velinSearchBound) return;
|
|
542
|
+
input.dataset.velinSearchBound = "1";
|
|
543
|
+
let host = input.closest("velin-search");
|
|
544
|
+
if (!host) {
|
|
545
|
+
host = document.createElement("velin-search");
|
|
546
|
+
host.setAttribute("index", input.getAttribute("data-search-index") || "/search-index.json");
|
|
547
|
+
const results = input.parentElement?.querySelector("[velin-search-results]");
|
|
548
|
+
input.before(host);
|
|
549
|
+
host.appendChild(input);
|
|
550
|
+
if (results) host.appendChild(results);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
export {
|
|
555
|
+
bindDeclarativeSearch,
|
|
556
|
+
velin_search_default as default
|
|
557
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// core/search/worker-client.js
|
|
2
|
+
var _nextId = 0;
|
|
3
|
+
function createSearchWorker(workerUrl) {
|
|
4
|
+
if (typeof Worker === "undefined") return null;
|
|
5
|
+
if (!workerUrl) return null;
|
|
6
|
+
const url = workerUrl;
|
|
7
|
+
let worker;
|
|
8
|
+
try {
|
|
9
|
+
worker = new Worker(url, { type: "module" });
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const pending = /* @__PURE__ */ new Map();
|
|
14
|
+
worker.onmessage = (e) => {
|
|
15
|
+
const { id, ok, result, error } = e.data || {};
|
|
16
|
+
const p = pending.get(id);
|
|
17
|
+
if (!p) return;
|
|
18
|
+
pending.delete(id);
|
|
19
|
+
if (ok) p.resolve(result);
|
|
20
|
+
else p.reject(new Error(error || "Worker error"));
|
|
21
|
+
};
|
|
22
|
+
worker.onerror = () => {
|
|
23
|
+
for (const p of pending.values()) p.reject(new Error("Worker failed"));
|
|
24
|
+
pending.clear();
|
|
25
|
+
};
|
|
26
|
+
function send(type, payload) {
|
|
27
|
+
const id = ++_nextId;
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
pending.set(id, { resolve, reject });
|
|
30
|
+
worker.postMessage({ id, type, payload });
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
setEntries(entries) {
|
|
35
|
+
return send("setEntries", { entries });
|
|
36
|
+
},
|
|
37
|
+
query(query, options) {
|
|
38
|
+
return send("query", { query, options });
|
|
39
|
+
},
|
|
40
|
+
terminate() {
|
|
41
|
+
worker.terminate();
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
createSearchWorker
|
|
47
|
+
};
|
package/dist/llms.txt
CHANGED
package/dist/search-index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"generatedAt": "2026-05-
|
|
3
|
+
"generatedAt": "2026-05-29T12:13:11.270Z",
|
|
4
4
|
"entries": [
|
|
5
5
|
{
|
|
6
6
|
"id": "examples:samples/a11y-patterns.html",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
{
|
|
58
58
|
"id": "api:commands",
|
|
59
59
|
"title": "CLI commands",
|
|
60
|
-
"excerpt": "VelinStyle CLI
|
|
60
|
+
"excerpt": "VelinStyle CLI v1.0.0. Run velinstyle --help for full usage.",
|
|
61
61
|
"url": "docs/generated/cli/commands.html",
|
|
62
62
|
"category": "api",
|
|
63
63
|
"keywords": [
|
package/dist/velin-agent.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"mime": "application/vnd.velinstyle.meta+json",
|
|
4
|
-
"generatedAt": "2026-05-
|
|
4
|
+
"generatedAt": "2026-05-29T12:13:11.252Z",
|
|
5
5
|
"framework": {
|
|
6
6
|
"name": "@birdapi/velinstyle",
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "1.0.0",
|
|
8
8
|
"homepage": "https://velinstyle.info",
|
|
9
9
|
"repository": "https://github.com/SkyliteDesign/velinstyle.git",
|
|
10
10
|
"tagline": "VelinStyle is the WCAG 2.2 AAA CSS framework with native JavaScript runtime and Web Components."
|
|
@@ -6701,7 +6701,8 @@
|
|
|
6701
6701
|
});
|
|
6702
6702
|
function createSearchWorker(workerUrl) {
|
|
6703
6703
|
if (typeof Worker === "undefined") return null;
|
|
6704
|
-
|
|
6704
|
+
if (!workerUrl) return null;
|
|
6705
|
+
const url = workerUrl;
|
|
6705
6706
|
let worker;
|
|
6706
6707
|
try {
|
|
6707
6708
|
worker = new Worker(url, { type: "module" });
|
|
@@ -6740,10 +6741,9 @@
|
|
|
6740
6741
|
}
|
|
6741
6742
|
};
|
|
6742
6743
|
}
|
|
6743
|
-
var
|
|
6744
|
+
var _nextId;
|
|
6744
6745
|
var init_worker_client = __esm({
|
|
6745
6746
|
"core/search/worker-client.js"() {
|
|
6746
|
-
import_meta = {};
|
|
6747
6747
|
_nextId = 0;
|
|
6748
6748
|
}
|
|
6749
6749
|
});
|
|
@@ -6700,7 +6700,8 @@ __export(worker_client_exports, {
|
|
|
6700
6700
|
});
|
|
6701
6701
|
function createSearchWorker(workerUrl) {
|
|
6702
6702
|
if (typeof Worker === "undefined") return null;
|
|
6703
|
-
|
|
6703
|
+
if (!workerUrl) return null;
|
|
6704
|
+
const url = workerUrl;
|
|
6704
6705
|
let worker;
|
|
6705
6706
|
try {
|
|
6706
6707
|
worker = new Worker(url, { type: "module" });
|