@pure-ds/core 0.7.30 → 0.7.33

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.
Files changed (86) hide show
  1. package/.github/copilot-instructions.md +20 -11
  2. package/LOCALIZATION.md +197 -0
  3. package/dist/types/pds.config.d.ts +1 -1
  4. package/dist/types/pds.config.d.ts.map +1 -1
  5. package/dist/types/pds.d.ts +150 -0
  6. package/dist/types/public/assets/pds/components/pds-form.d.ts +2 -2
  7. package/dist/types/public/assets/pds/components/pds-form.d.ts.map +1 -1
  8. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +2 -2
  9. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  10. package/dist/types/public/assets/pds/components/pds-rating.d.ts +1 -119
  11. package/dist/types/public/assets/pds/components/pds-rating.d.ts.map +1 -1
  12. package/dist/types/public/assets/pds/components/pds-treeview.d.ts.map +1 -1
  13. package/dist/types/public/assets/pds/components/pds-upload.d.ts.map +1 -1
  14. package/dist/types/src/js/common/ask.d.ts.map +1 -1
  15. package/dist/types/src/js/common/font-loader.d.ts.map +1 -1
  16. package/dist/types/src/js/common/localization-resource-provider.d.ts +49 -0
  17. package/dist/types/src/js/common/localization-resource-provider.d.ts.map +1 -0
  18. package/dist/types/src/js/common/localization.d.ts +25 -0
  19. package/dist/types/src/js/common/localization.d.ts.map +1 -0
  20. package/dist/types/src/js/common/msg.d.ts +1 -2
  21. package/dist/types/src/js/common/msg.d.ts.map +1 -1
  22. package/dist/types/src/js/common/pds-log.d.ts +3 -0
  23. package/dist/types/src/js/common/pds-log.d.ts.map +1 -0
  24. package/dist/types/src/js/lit.d.ts +0 -7
  25. package/dist/types/src/js/lit.d.ts.map +1 -1
  26. package/dist/types/src/js/pds-core/pds-config.d.ts +51 -0
  27. package/dist/types/src/js/pds-core/pds-config.d.ts.map +1 -1
  28. package/dist/types/src/js/pds-core/pds-enhancers.d.ts.map +1 -1
  29. package/dist/types/src/js/pds-core/pds-live.d.ts.map +1 -1
  30. package/dist/types/src/js/pds-core/pds-registry.d.ts.map +1 -1
  31. package/dist/types/src/js/pds-core/pds-runtime.d.ts.map +1 -1
  32. package/dist/types/src/js/pds-core/pds-start-helpers.d.ts.map +1 -1
  33. package/dist/types/src/js/pds-localization.d.ts +3 -0
  34. package/dist/types/src/js/pds-localization.d.ts.map +1 -0
  35. package/dist/types/src/js/pds-singleton.d.ts +13 -0
  36. package/dist/types/src/js/pds-singleton.d.ts.map +1 -0
  37. package/dist/types/src/js/pds.d.ts +9 -1
  38. package/dist/types/src/js/pds.d.ts.map +1 -1
  39. package/package.json +10 -4
  40. package/packages/pds-cli/README.md +2 -0
  41. package/packages/pds-cli/lib/pds-mcp-core.js +2 -2
  42. package/public/assets/js/app.js +9 -11
  43. package/public/assets/js/lit.js +3 -94
  44. package/public/assets/js/pds-ask.js +4 -4
  45. package/public/assets/js/pds-enhancers.js +1 -1
  46. package/public/assets/js/pds-localization.js +1 -0
  47. package/public/assets/js/pds-manager.js +118 -118
  48. package/public/assets/js/pds.js +2 -2
  49. package/public/assets/pds/components/pds-calendar.js +4 -4
  50. package/public/assets/pds/components/pds-daterange.js +11 -8
  51. package/public/assets/pds/components/pds-form.js +22 -22
  52. package/public/assets/pds/components/pds-live-edit.js +503 -42
  53. package/public/assets/pds/components/pds-live-importer.js +66 -66
  54. package/public/assets/pds/components/pds-live-template-canvas.js +3 -3
  55. package/public/assets/pds/components/pds-omnibox.js +4 -4
  56. package/public/assets/pds/components/pds-rating.js +5 -3
  57. package/public/assets/pds/components/pds-tags.js +5 -5
  58. package/public/assets/pds/components/pds-theme.js +4 -4
  59. package/public/assets/pds/components/pds-toaster.js +6 -6
  60. package/public/assets/pds/components/pds-treeview.js +8 -4
  61. package/public/assets/pds/components/pds-upload.js +7 -6
  62. package/public/assets/pds/core/pds-ask.js +4 -4
  63. package/public/assets/pds/core/pds-enhancers.js +1 -1
  64. package/public/assets/pds/core/pds-localization.js +1 -0
  65. package/public/assets/pds/core/pds-manager.js +118 -118
  66. package/public/assets/pds/core.js +2 -2
  67. package/public/assets/pds/external/lit.js +3 -94
  68. package/readme.md +34 -6
  69. package/src/js/common/ask.js +530 -0
  70. package/src/js/common/common.js +122 -0
  71. package/src/js/common/font-loader.js +202 -0
  72. package/src/js/common/localization-resource-provider.js +274 -0
  73. package/src/js/common/localization.js +839 -0
  74. package/src/js/common/msg.js +9 -0
  75. package/src/js/common/pds-core/pds.d.ts +128 -0
  76. package/src/js/common/pds-log.js +144 -0
  77. package/src/js/common/toast.js +122 -0
  78. package/src/js/pds-core/pds-config.js +51 -10
  79. package/src/js/pds-core/pds-enhancers.js +5 -3
  80. package/src/js/pds-core/pds-live.js +7 -5
  81. package/src/js/pds-core/pds-registry.js +6 -4
  82. package/src/js/pds-core/pds-runtime.js +12 -8
  83. package/src/js/pds-core/pds-start-helpers.js +9 -4
  84. package/src/js/pds-singleton.js +49 -0
  85. package/src/js/pds.d.ts +150 -0
  86. package/src/js/pds.js +420 -40
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Font Loading Utility
3
+ * Automatically loads fonts from Google Fonts when they're not available in the browser
4
+ */
5
+
6
+ import { PDS } from "../pds-singleton.js";
7
+
8
+ /**
9
+ * Checks if a font is available in the browser
10
+ * @param {string} fontName - The name of the font to check
11
+ * @returns {boolean} True if the font is available
12
+ */
13
+ function isFontAvailable(fontName) {
14
+ // Clean up font name (remove quotes and extra spacing)
15
+ const cleanName = fontName.replace(/['"]/g, '').trim();
16
+
17
+ // System fonts that are always available
18
+ const systemFonts = [
19
+ 'system-ui',
20
+ '-apple-system',
21
+ 'sans-serif',
22
+ 'serif',
23
+ 'monospace',
24
+ 'cursive',
25
+ 'fantasy',
26
+ 'ui-sans-serif',
27
+ 'ui-serif',
28
+ 'ui-monospace',
29
+ 'ui-rounded'
30
+ ];
31
+
32
+ if (systemFonts.includes(cleanName.toLowerCase())) {
33
+ return true;
34
+ }
35
+
36
+ // Use canvas-based detection
37
+ const canvas = document.createElement('canvas');
38
+ const context = canvas.getContext('2d');
39
+
40
+ if (!context) return false;
41
+
42
+ const testString = 'mmmmmmmmmmlli'; // Characters with varying widths
43
+ const testSize = '72px';
44
+ const baselineFont = 'monospace';
45
+
46
+ // Measure with baseline font
47
+ context.font = `${testSize} ${baselineFont}`;
48
+ const baselineWidth = context.measureText(testString).width;
49
+
50
+ // Measure with test font
51
+ context.font = `${testSize} "${cleanName}", ${baselineFont}`;
52
+ const testWidth = context.measureText(testString).width;
53
+
54
+ return baselineWidth !== testWidth;
55
+ }
56
+
57
+ /**
58
+ * Extracts the primary font name from a font-family string
59
+ * @param {string} fontFamily - Font family string (e.g., "Roboto, sans-serif")
60
+ * @returns {string} The primary font name
61
+ */
62
+ function extractPrimaryFont(fontFamily) {
63
+ if (!fontFamily) return null;
64
+
65
+ // Split by comma and get first font
66
+ const fonts = fontFamily.split(',').map(f => f.trim());
67
+ const primaryFont = fonts[0];
68
+
69
+ // Remove quotes
70
+ return primaryFont.replace(/['"]/g, '').trim();
71
+ }
72
+
73
+ /**
74
+ * Loads a Google Font dynamically
75
+ * @param {string} fontFamily - The font family to load (can be comma-separated list)
76
+ * @param {Object} options - Loading options
77
+ * @param {number[]} options.weights - Font weights to load (default: [400, 500, 600, 700])
78
+ * @param {boolean} options.italic - Whether to include italic variants (default: false)
79
+ * @returns {Promise<void>}
80
+ */
81
+ export async function loadGoogleFont(fontFamily, options = {}) {
82
+ if (!fontFamily) {
83
+ return Promise.resolve();
84
+ }
85
+
86
+ const {
87
+ weights = [400, 500, 600, 700],
88
+ italic = false
89
+ } = options;
90
+
91
+ const primaryFont = extractPrimaryFont(fontFamily);
92
+
93
+ if (!primaryFont) {
94
+ return Promise.resolve();
95
+ }
96
+
97
+ // Check if font is already available
98
+ if (isFontAvailable(primaryFont)) {
99
+ return Promise.resolve();
100
+ }
101
+
102
+ // Check if font link already exists
103
+ const encodedFont = encodeURIComponent(primaryFont);
104
+ const existingLink = document.querySelector(
105
+ `link[href*="fonts.googleapis.com"][href*="${encodedFont}"]`
106
+ );
107
+
108
+ if (existingLink) {
109
+ PDS.log("log", `Font "${primaryFont}" is already loading or loaded`);
110
+ return Promise.resolve();
111
+ }
112
+
113
+ PDS.log("log", `Loading font "${primaryFont}" from Google Fonts...`);
114
+
115
+ return new Promise((resolve, reject) => {
116
+ const link = document.createElement('link');
117
+ link.rel = 'stylesheet';
118
+
119
+ // Build Google Fonts URL with specified weights
120
+ const weightsParam = italic
121
+ ? `ital,wght@0,${weights.join(';0,')};1,${weights.join(';1,')}`
122
+ : `wght@${weights.join(';')}`;
123
+
124
+ link.href = `https://fonts.googleapis.com/css2?family=${encodedFont}:${weightsParam}&display=swap`;
125
+
126
+ // Add a data attribute for easy identification
127
+ link.setAttribute('data-font-loader', primaryFont);
128
+
129
+ link.onload = () => {
130
+ PDS.log("log", `Successfully loaded font "${primaryFont}"`);
131
+ resolve();
132
+ };
133
+
134
+ link.onerror = () => {
135
+ PDS.log("warn", `Failed to load font "${primaryFont}" from Google Fonts`);
136
+ reject(new Error(`Failed to load font: ${primaryFont}`));
137
+ };
138
+
139
+ document.head.appendChild(link);
140
+
141
+ // Set a timeout to prevent hanging indefinitely
142
+ setTimeout(() => {
143
+ if (!isFontAvailable(primaryFont)) {
144
+ PDS.log("warn", `Font "${primaryFont}" did not load within timeout`);
145
+ }
146
+ resolve(); // Resolve anyway to not block the application
147
+ }, 5000);
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Loads fonts for all font families in a typography config
153
+ * @param {Object} typographyConfig - Typography configuration object
154
+ * @returns {Promise<void>}
155
+ */
156
+ export async function loadTypographyFonts(typographyConfig) {
157
+ if (!typographyConfig) {
158
+ return Promise.resolve();
159
+ }
160
+
161
+ const fontFamilies = new Set();
162
+
163
+ // Collect all font families from the config
164
+ if (typographyConfig.fontFamilyHeadings) {
165
+ fontFamilies.add(typographyConfig.fontFamilyHeadings);
166
+ }
167
+ if (typographyConfig.fontFamilyBody) {
168
+ fontFamilies.add(typographyConfig.fontFamilyBody);
169
+ }
170
+ if (typographyConfig.fontFamilyMono) {
171
+ fontFamilies.add(typographyConfig.fontFamilyMono);
172
+ }
173
+
174
+ // Load all fonts in parallel
175
+ const loadPromises = Array.from(fontFamilies).map(fontFamily =>
176
+ loadGoogleFont(fontFamily).catch(err => {
177
+ PDS.log("warn", `Could not load font: ${fontFamily}`, err);
178
+ // Don't fail the whole operation if one font fails
179
+ })
180
+ );
181
+
182
+ await Promise.all(loadPromises);
183
+ }
184
+
185
+ /**
186
+ * Removes previously loaded Google Fonts
187
+ * @param {string} fontName - Optional font name to remove. If not specified, removes all.
188
+ */
189
+ export function unloadGoogleFont(fontName = null) {
190
+ const selector = fontName
191
+ ? `link[data-font-loader="${fontName}"]`
192
+ : 'link[data-font-loader]';
193
+
194
+ const links = document.querySelectorAll(selector);
195
+ links.forEach(link => link.remove());
196
+
197
+ if (fontName) {
198
+ PDS.log("log", `Unloaded font "${fontName}"`);
199
+ } else {
200
+ PDS.log("log", `Unloaded ${links.length} font(s)`);
201
+ }
202
+ }
@@ -0,0 +1,274 @@
1
+ function normalizeLocaleTag(locale) {
2
+ return String(locale || "").trim().toLowerCase();
3
+ }
4
+
5
+ function toBaseLocale(locale) {
6
+ return normalizeLocaleTag(locale).split("-")[0] || "";
7
+ }
8
+
9
+ function toLocaleList(locales, fallbackLocale) {
10
+ const output = [];
11
+ const seen = new Set();
12
+
13
+ const add = (locale) => {
14
+ const normalized = normalizeLocaleTag(locale);
15
+ if (!normalized || seen.has(normalized)) {
16
+ return;
17
+ }
18
+
19
+ seen.add(normalized);
20
+ output.push(normalized);
21
+ };
22
+
23
+ if (Array.isArray(locales)) {
24
+ locales.forEach(add);
25
+ }
26
+
27
+ add(fallbackLocale);
28
+ return output;
29
+ }
30
+
31
+ function normalizeAliasMap(aliases = {}) {
32
+ const normalized = {};
33
+
34
+ if (!aliases || typeof aliases !== "object") {
35
+ return normalized;
36
+ }
37
+
38
+ Object.entries(aliases).forEach(([key, values]) => {
39
+ const normalizedKey = normalizeLocaleTag(key);
40
+ if (!normalizedKey) {
41
+ return;
42
+ }
43
+
44
+ const list = [];
45
+ const seen = new Set();
46
+
47
+ if (Array.isArray(values)) {
48
+ values.forEach((value) => {
49
+ if (typeof value !== "string") {
50
+ return;
51
+ }
52
+
53
+ const trimmed = value.trim();
54
+ if (!trimmed) {
55
+ return;
56
+ }
57
+
58
+ const dedupeKey = trimmed.toLowerCase();
59
+ if (seen.has(dedupeKey)) {
60
+ return;
61
+ }
62
+
63
+ seen.add(dedupeKey);
64
+ list.push(trimmed);
65
+ });
66
+ }
67
+
68
+ normalized[normalizedKey] = list;
69
+ });
70
+
71
+ return normalized;
72
+ }
73
+
74
+ function normalizeBundleShape(bundle) {
75
+ if (!bundle || typeof bundle !== "object" || Array.isArray(bundle)) {
76
+ return {};
77
+ }
78
+
79
+ return bundle;
80
+ }
81
+
82
+ function normalizeBasePath(basePath) {
83
+ const raw = typeof basePath === "string" ? basePath.trim() : "";
84
+ if (!raw) {
85
+ return "/assets/locales";
86
+ }
87
+
88
+ if (raw === "/") {
89
+ return "";
90
+ }
91
+
92
+ return raw.replace(/\/+$/, "");
93
+ }
94
+
95
+ function buildCandidateLocales({ locale, effectiveLocale, defaultLocale, aliases }) {
96
+ const candidates = [];
97
+ const seen = new Set();
98
+
99
+ const add = (candidate) => {
100
+ if (typeof candidate !== "string") {
101
+ return;
102
+ }
103
+
104
+ const trimmed = candidate.trim();
105
+ if (!trimmed) {
106
+ return;
107
+ }
108
+
109
+ const dedupeKey = trimmed.toLowerCase();
110
+ if (seen.has(dedupeKey)) {
111
+ return;
112
+ }
113
+
114
+ seen.add(dedupeKey);
115
+ candidates.push(trimmed);
116
+ };
117
+
118
+ const addAliases = (aliasKey) => {
119
+ const values = Array.isArray(aliases?.[aliasKey]) ? aliases[aliasKey] : [];
120
+ if (!values.length) {
121
+ return;
122
+ }
123
+
124
+ const prioritized = [...values].sort((a, b) => {
125
+ const aSpecific = String(a || "").includes("-");
126
+ const bSpecific = String(b || "").includes("-");
127
+ if (aSpecific === bSpecific) return 0;
128
+ return aSpecific ? -1 : 1;
129
+ });
130
+
131
+ prioritized.forEach(add);
132
+ };
133
+
134
+ const normalizedRequested = normalizeLocaleTag(locale);
135
+ const requestedBase = toBaseLocale(normalizedRequested);
136
+ const effectiveBase = toBaseLocale(effectiveLocale);
137
+
138
+ addAliases(normalizedRequested);
139
+ addAliases(requestedBase);
140
+ addAliases(effectiveLocale);
141
+ addAliases(effectiveBase);
142
+
143
+ add(locale);
144
+ add(normalizedRequested);
145
+ add(requestedBase);
146
+ add(effectiveLocale);
147
+ add(effectiveBase);
148
+
149
+ if (effectiveLocale !== defaultLocale) {
150
+ addAliases(defaultLocale);
151
+ add(defaultLocale);
152
+ }
153
+
154
+ return candidates;
155
+ }
156
+
157
+ /**
158
+ * Create a localization config that lazy-loads locale JSON resources.
159
+ *
160
+ * JSON files are expected at:
161
+ * - `${basePath}/{locale}.json`
162
+ *
163
+ * Bundle format can be either:
164
+ * - `{ "Key": "Translated" }`
165
+ * - `{ "Key": { "content": "Translated" } }`
166
+ *
167
+ * @param {{
168
+ * locale?: string,
169
+ * locales?: string[],
170
+ * basePath?: string,
171
+ * aliases?: Record<string, string[]>,
172
+ * requestInit?: RequestInit,
173
+ * cache?: Map<string, Record<string, string | { content?: string }>>,
174
+ * }} [options]
175
+ * @returns {{
176
+ * locale: string,
177
+ * locales: string[],
178
+ * provider: {
179
+ * locales: string[],
180
+ * loadLocale: (context: { locale: string }) => Promise<Record<string, string | { content?: string }>>,
181
+ * },
182
+ * }}
183
+ */
184
+ export function createJSONLocalization(options = {}) {
185
+ const defaultLocale = normalizeLocaleTag(options?.locale || "en") || "en";
186
+ const locales = toLocaleList(options?.locales, defaultLocale);
187
+ const localeSet = new Set(locales);
188
+ const aliases = normalizeAliasMap(options?.aliases || {});
189
+ const cache = options?.cache instanceof Map ? options.cache : new Map();
190
+ const basePath = normalizeBasePath(options?.basePath);
191
+ const requestInit =
192
+ options?.requestInit && typeof options.requestInit === "object"
193
+ ? options.requestInit
194
+ : {};
195
+
196
+ const resolveEffectiveLocale = (locale) => {
197
+ const normalized = normalizeLocaleTag(locale);
198
+ if (!normalized) {
199
+ return defaultLocale;
200
+ }
201
+
202
+ if (localeSet.has(normalized)) {
203
+ return normalized;
204
+ }
205
+
206
+ const base = toBaseLocale(normalized);
207
+ if (localeSet.has(base)) {
208
+ return base;
209
+ }
210
+
211
+ return defaultLocale;
212
+ };
213
+
214
+ const loadLocale = async ({ locale }) => {
215
+ const effectiveLocale = resolveEffectiveLocale(locale);
216
+ if (cache.has(effectiveLocale)) {
217
+ return cache.get(effectiveLocale) || {};
218
+ }
219
+
220
+ if (effectiveLocale === defaultLocale) {
221
+ const defaultBundle = {};
222
+ cache.set(effectiveLocale, defaultBundle);
223
+ return defaultBundle;
224
+ }
225
+
226
+ if (typeof fetch !== "function") {
227
+ const emptyBundle = {};
228
+ cache.set(effectiveLocale, emptyBundle);
229
+ return emptyBundle;
230
+ }
231
+
232
+ const candidates = buildCandidateLocales({
233
+ locale,
234
+ effectiveLocale,
235
+ defaultLocale,
236
+ aliases,
237
+ });
238
+
239
+ for (const candidate of candidates) {
240
+ try {
241
+ const resourcePath = basePath
242
+ ? `${basePath}/${candidate}.json`
243
+ : `/${candidate}.json`;
244
+ const response = await fetch(resourcePath, {
245
+ headers: {
246
+ Accept: "application/json",
247
+ },
248
+ ...requestInit,
249
+ });
250
+
251
+ if (!response.ok) {
252
+ continue;
253
+ }
254
+
255
+ const bundle = normalizeBundleShape(await response.json());
256
+ cache.set(effectiveLocale, bundle);
257
+ return bundle;
258
+ } catch (error) {}
259
+ }
260
+
261
+ const emptyBundle = {};
262
+ cache.set(effectiveLocale, emptyBundle);
263
+ return emptyBundle;
264
+ };
265
+
266
+ return {
267
+ locale: defaultLocale,
268
+ locales: [...locales],
269
+ provider: {
270
+ locales: [...locales],
271
+ loadLocale,
272
+ },
273
+ };
274
+ }