@getdom/studio 0.2.2 → 0.2.3
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 +52 -0
- package/package.json +1 -1
- package/src/pages/_layout/componentCatalog.generated.js +114 -12
- package/src/pages/forms/_shared/DomOptionStatus.vue +18 -0
- package/src/pages/forms/_shared/useOptionMenu.js +18 -2
- package/src/pages/forms/_shared/useOptionPaging.js +42 -0
- package/src/pages/forms/_shared/useSelectedOptions.js +25 -0
- package/src/pages/forms/autocomplete/DomAutocomplete.vue +36 -8
- package/src/pages/forms/combobox/DomCombobox.vue +44 -7
- package/src/pages/forms/font-picker/DomFontPicker.vue +416 -0
- package/src/pages/forms/font-picker/FontPreview.vue +39 -0
- package/src/pages/forms/font-picker/fontPicker.js +102 -0
- package/src/pages/forms/font-picker/googleFonts.js +1951 -0
- package/src/pages/forms/select/DomSelect.vue +118 -29
- package/src/pages/forms/tag-combobox/DomTagCombobox.vue +67 -15
- package/src/pages/lib/headless/autocomplete.js +5 -0
- package/src/pages/lib/headless/combobox.js +129 -25
- package/src/pages/lib/headless/dropdown.js +3 -2
- package/src/pages/lib/headless/listbox.d.ts +1 -0
- package/src/pages/lib/headless/listbox.js +41 -5
- package/src/pages/lib/vue/index.d.ts +133 -3
- package/src/pages/lib/vue/index.js +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
const DEFAULT_STYLESHEET_URL = 'https://fonts.googleapis.com/css2';
|
|
2
|
+
const DEFAULT_FALLBACK = 'sans-serif';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Normalize a user-provided font family record for search and rendering.
|
|
6
|
+
*
|
|
7
|
+
* @param {string|Record<string, unknown>} font Font family name or metadata record.
|
|
8
|
+
* @param {number} index Position used for a stable fallback key.
|
|
9
|
+
* @returns {Record<string, string>|null} Normalized font record, or null for invalid input.
|
|
10
|
+
*/
|
|
11
|
+
export function normalizeFont(font, index = 0) {
|
|
12
|
+
const source = typeof font === 'string' ? { family: font } : font;
|
|
13
|
+
if (!source || typeof source !== 'object') return null;
|
|
14
|
+
const family = String(source.family || source.label || source.value || '').trim();
|
|
15
|
+
if (!family) return null;
|
|
16
|
+
const category = String(source.category || DEFAULT_FALLBACK).trim() || DEFAULT_FALLBACK;
|
|
17
|
+
const value = String(source.value || family);
|
|
18
|
+
const keywords = Array.isArray(source.keywords) ? source.keywords.join(' ') : String(source.keywords || '');
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
...source,
|
|
22
|
+
key: String(source.key || `${family}-${index}`),
|
|
23
|
+
family,
|
|
24
|
+
label: String(source.label || family),
|
|
25
|
+
value,
|
|
26
|
+
category,
|
|
27
|
+
searchText: `${family} ${source.label || ''} ${category} ${keywords}`.toLowerCase(),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Rank and limit font records for a picker query.
|
|
33
|
+
*
|
|
34
|
+
* @param {Array<string|Record<string, unknown>>} fonts Available font records.
|
|
35
|
+
* @param {string} query Current user query.
|
|
36
|
+
* @param {number} limit Maximum number of preview rows.
|
|
37
|
+
* @param {string} category Optional category filter.
|
|
38
|
+
* @returns {Array<Record<string, string>>} Matching normalized font records.
|
|
39
|
+
*/
|
|
40
|
+
export function filterFonts(fonts, query = '', limit = 12, category = '') {
|
|
41
|
+
const needle = query.trim().toLowerCase();
|
|
42
|
+
const normalized = fonts
|
|
43
|
+
.map(normalizeFont)
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.filter((font) => !category || font.category === category);
|
|
46
|
+
|
|
47
|
+
if (!needle) return normalized.slice(0, limit);
|
|
48
|
+
|
|
49
|
+
return normalized
|
|
50
|
+
.filter((font) => font.searchText.includes(needle))
|
|
51
|
+
.sort((left, right) => fontMatchRank(left, needle) - fontMatchRank(right, needle)
|
|
52
|
+
|| left.family.length - right.family.length
|
|
53
|
+
|| left.label.localeCompare(right.label))
|
|
54
|
+
.slice(0, limit);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Build a Google Fonts CSS v2 URL for only the requested preview families and glyphs.
|
|
59
|
+
*
|
|
60
|
+
* @param {Array<string>} families Font family names to request.
|
|
61
|
+
* @param {string} text Exact preview text to subset into the response.
|
|
62
|
+
* @param {string} baseUrl Google Fonts CSS endpoint or compatible proxy endpoint.
|
|
63
|
+
* @returns {string} Stylesheet URL, or an empty string when no families are supplied.
|
|
64
|
+
*/
|
|
65
|
+
export function buildGoogleFontsStylesheetUrl(families, text, baseUrl = DEFAULT_STYLESHEET_URL) {
|
|
66
|
+
const uniqueFamilies = [...new Set(families.map((family) => String(family || '').trim()).filter(Boolean))]
|
|
67
|
+
.sort((left, right) => left.localeCompare(right));
|
|
68
|
+
if (!uniqueFamilies.length) return '';
|
|
69
|
+
const familyQuery = uniqueFamilies
|
|
70
|
+
.map((family) => `family=${encodeURIComponent(family).replace(/%20/g, '+')}`)
|
|
71
|
+
.join('&');
|
|
72
|
+
const textQuery = text ? `&text=${encodeURIComponent(text)}` : '';
|
|
73
|
+
return `${baseUrl}?${familyQuery}${textQuery}&display=swap`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a safe inline font stack for a preview row.
|
|
78
|
+
*
|
|
79
|
+
* @param {Record<string, string>|null|undefined} font Normalized font record.
|
|
80
|
+
* @returns {string} CSS font-family value.
|
|
81
|
+
*/
|
|
82
|
+
export function fontStack(font) {
|
|
83
|
+
if (!font?.family) return DEFAULT_FALLBACK;
|
|
84
|
+
const family = font.family.replace(/[\\']/g, '\\$&');
|
|
85
|
+
const fallback = { serif: 'serif', monospace: 'monospace', handwriting: 'cursive', display: 'sans-serif', 'sans-serif': 'sans-serif' }[font.category] || DEFAULT_FALLBACK;
|
|
86
|
+
return `'${family}', ${fallback}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Score a font match so exact and prefix matches appear before loose matches.
|
|
91
|
+
*
|
|
92
|
+
* @param {Record<string, string>} font Normalized font record.
|
|
93
|
+
* @param {string} needle Lowercase search query.
|
|
94
|
+
* @returns {number} Lower scores sort first.
|
|
95
|
+
*/
|
|
96
|
+
function fontMatchRank(font, needle) {
|
|
97
|
+
const family = font.family.toLowerCase();
|
|
98
|
+
if (family === needle) return 0;
|
|
99
|
+
if (family.startsWith(needle)) return 1;
|
|
100
|
+
if (family.split(/\s+/).some((word) => word.startsWith(needle))) return 2;
|
|
101
|
+
return 3;
|
|
102
|
+
}
|