@pure-ds/storybook 0.1.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/.storybook/addons/description/preview.js +15 -0
- package/.storybook/addons/description/register.js +60 -0
- package/.storybook/addons/html-preview/Panel.jsx +327 -0
- package/.storybook/addons/html-preview/constants.js +6 -0
- package/.storybook/addons/html-preview/preview.js +178 -0
- package/.storybook/addons/html-preview/register.js +16 -0
- package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
- package/.storybook/addons/pds-configurator/Tool.js +30 -0
- package/.storybook/addons/pds-configurator/constants.js +9 -0
- package/.storybook/addons/pds-configurator/preview.js +159 -0
- package/.storybook/addons/pds-configurator/register.js +24 -0
- package/.storybook/docs.css +35 -0
- package/.storybook/htmlPreview.css +103 -0
- package/.storybook/htmlPreview.js +271 -0
- package/.storybook/main.js +160 -0
- package/.storybook/preview-body.html +48 -0
- package/.storybook/preview-head.html +11 -0
- package/.storybook/preview.js +1563 -0
- package/README.md +266 -0
- package/bin/index.js +40 -0
- package/dist/pds-reference.json +2101 -0
- package/package.json +45 -0
- package/pds.config.js +6 -0
- package/public/assets/css/app.css +1216 -0
- package/public/assets/data/auto-design-advanced.json +704 -0
- package/public/assets/data/auto-design-simple.json +123 -0
- package/public/assets/img/icon-512x512.png +0 -0
- package/public/assets/img/logo-trans.png +0 -0
- package/public/assets/img/logo.png +0 -0
- package/public/assets/js/app.js +15088 -0
- package/public/assets/js/app.js.map +7 -0
- package/public/assets/js/lit.js +1176 -0
- package/public/assets/js/lit.js.map +7 -0
- package/public/assets/js/pds.js +9801 -0
- package/public/assets/js/pds.js.map +7 -0
- package/public/assets/pds/components/pds-calendar.js +837 -0
- package/public/assets/pds/components/pds-drawer.js +857 -0
- package/public/assets/pds/components/pds-icon.js +338 -0
- package/public/assets/pds/components/pds-jsonform.js +1775 -0
- package/public/assets/pds/components/pds-richtext.js +1035 -0
- package/public/assets/pds/components/pds-scrollrow.js +331 -0
- package/public/assets/pds/components/pds-splitpanel.js +401 -0
- package/public/assets/pds/components/pds-tabstrip.js +251 -0
- package/public/assets/pds/components/pds-toaster.js +446 -0
- package/public/assets/pds/components/pds-upload.js +657 -0
- package/public/assets/pds/custom-elements.json +2003 -0
- package/public/assets/pds/icons/pds-icons.svg +498 -0
- package/public/assets/pds/pds-css-complete.json +1861 -0
- package/public/assets/pds/pds-runtime-config.json +11 -0
- package/public/assets/pds/pds.css-data.json +2152 -0
- package/public/assets/pds/styles/pds-components.css +1944 -0
- package/public/assets/pds/styles/pds-components.css.js +3895 -0
- package/public/assets/pds/styles/pds-primitives.css +352 -0
- package/public/assets/pds/styles/pds-primitives.css.js +711 -0
- package/public/assets/pds/styles/pds-styles.css +3761 -0
- package/public/assets/pds/styles/pds-styles.css.js +7529 -0
- package/public/assets/pds/styles/pds-tokens.css +699 -0
- package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
- package/public/assets/pds/styles/pds-utilities.css +763 -0
- package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
- package/public/assets/pds/vscode-custom-data.json +824 -0
- package/scripts/build-pds-reference.mjs +807 -0
- package/scripts/generate-stories.js +542 -0
- package/scripts/package-build.js +86 -0
- package/src/js/app.js +17 -0
- package/src/js/common/ask.js +208 -0
- package/src/js/common/common.js +20 -0
- package/src/js/common/font-loader.js +200 -0
- package/src/js/common/msg.js +90 -0
- package/src/js/lit.js +40 -0
- package/src/js/pds-core/pds-config.js +1162 -0
- package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
- package/src/js/pds-core/pds-enhancers.js +357 -0
- package/src/js/pds-core/pds-enums.js +86 -0
- package/src/js/pds-core/pds-generator.js +5317 -0
- package/src/js/pds-core/pds-ontology.js +256 -0
- package/src/js/pds-core/pds-paths.js +109 -0
- package/src/js/pds-core/pds-query.js +571 -0
- package/src/js/pds-core/pds-registry.js +129 -0
- package/src/js/pds-core/pds.d.ts +129 -0
- package/src/js/pds.d.ts +408 -0
- package/src/js/pds.js +1579 -0
- package/src/pds-core/pds-api.js +105 -0
- package/stories/GettingStarted.md +96 -0
- package/stories/GettingStarted.stories.js +144 -0
- package/stories/WhatIsPDS.md +194 -0
- package/stories/WhatIsPDS.stories.js +144 -0
- package/stories/components/PdsCalendar.stories.js +263 -0
- package/stories/components/PdsDrawer.stories.js +623 -0
- package/stories/components/PdsIcon.stories.js +78 -0
- package/stories/components/PdsJsonform.stories.js +1444 -0
- package/stories/components/PdsRichtext.stories.js +367 -0
- package/stories/components/PdsScrollrow.stories.js +140 -0
- package/stories/components/PdsSplitpanel.stories.js +502 -0
- package/stories/components/PdsTabstrip.stories.js +442 -0
- package/stories/components/PdsToaster.stories.js +186 -0
- package/stories/components/PdsUpload.stories.js +66 -0
- package/stories/enhancements/Dropdowns.stories.js +185 -0
- package/stories/enhancements/InteractiveStates.stories.js +625 -0
- package/stories/enhancements/MeshGradients.stories.js +320 -0
- package/stories/enhancements/OpenGroups.stories.js +227 -0
- package/stories/enhancements/RangeSliders.stories.js +232 -0
- package/stories/enhancements/RequiredFields.stories.js +189 -0
- package/stories/enhancements/Toggles.stories.js +167 -0
- package/stories/foundations/Colors.stories.js +283 -0
- package/stories/foundations/Icons.stories.js +305 -0
- package/stories/foundations/SmartSurfaces.stories.js +367 -0
- package/stories/foundations/Spacing.stories.js +175 -0
- package/stories/foundations/Typography.stories.js +960 -0
- package/stories/foundations/ZIndex.stories.js +325 -0
- package/stories/patterns/BorderEffects.stories.js +72 -0
- package/stories/patterns/Layout.stories.js +99 -0
- package/stories/patterns/Utilities.stories.js +107 -0
- package/stories/primitives/Accordion.stories.js +359 -0
- package/stories/primitives/Alerts.stories.js +64 -0
- package/stories/primitives/Badges.stories.js +183 -0
- package/stories/primitives/Buttons.stories.js +229 -0
- package/stories/primitives/Cards.stories.js +353 -0
- package/stories/primitives/FormGroups.stories.js +569 -0
- package/stories/primitives/Forms.stories.js +131 -0
- package/stories/primitives/Media.stories.js +203 -0
- package/stories/primitives/Tables.stories.js +232 -0
- package/stories/reference/ReferenceCatalog.stories.js +28 -0
- package/stories/reference/reference-catalog.js +413 -0
- package/stories/reference/reference-docs.js +302 -0
- package/stories/reference/reference-helpers.js +310 -0
- package/stories/utilities/GridSystem.stories.js +208 -0
- package/stories/utils/PdsAsk.stories.js +420 -0
- package/stories/utils/toast-utils.js +148 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { render, html } from "lit";
|
|
2
|
+
import { config } from "../../../pds.config.js"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Get the current page title for dialogs
|
|
6
|
+
*/
|
|
7
|
+
function getPageTitle() {
|
|
8
|
+
return document.title ||
|
|
9
|
+
document.querySelector('h1')?.textContent ||
|
|
10
|
+
'Application';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a PDS-compliant dialog with proper semantic structure
|
|
15
|
+
* @param {string|TemplateResult} message - Message content (string or Lit template)
|
|
16
|
+
* @param {Object} options - Dialog options
|
|
17
|
+
* @returns {Promise} Resolves with result when dialog closes
|
|
18
|
+
*/
|
|
19
|
+
export async function ask(message, options = {}) {
|
|
20
|
+
|
|
21
|
+
const defaults = {
|
|
22
|
+
title: "Confirm",
|
|
23
|
+
type: "confirm", // 'alert', 'confirm', 'custom'
|
|
24
|
+
buttons: {
|
|
25
|
+
ok: { name: "OK", primary: true },
|
|
26
|
+
cancel: { name: "Cancel", cancel: true },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
options = { ...defaults, ...options };
|
|
31
|
+
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
// Create native dialog element
|
|
34
|
+
const dialog = document.createElement("dialog");
|
|
35
|
+
|
|
36
|
+
if(config.options?.liquidGlassEffects)
|
|
37
|
+
dialog.classList.add("liquid-glass");
|
|
38
|
+
|
|
39
|
+
// Add optional CSS classes
|
|
40
|
+
if (options.size) {
|
|
41
|
+
dialog.classList.add(`dialog-${options.size}`); // dialog-sm, dialog-lg, dialog-xl
|
|
42
|
+
}
|
|
43
|
+
if (options.type) {
|
|
44
|
+
dialog.classList.add(`dialog-${options.type}`);
|
|
45
|
+
}
|
|
46
|
+
if (options.class) {
|
|
47
|
+
if (Array.isArray(options.class)) {
|
|
48
|
+
dialog.classList.add(...options.class);
|
|
49
|
+
} else {
|
|
50
|
+
dialog.classList.add(options.class);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build button elements
|
|
55
|
+
const buttons = Object.entries(options.buttons).map(([code, obj]) => {
|
|
56
|
+
const btnClass = obj.primary ? "btn-primary btn-sm" : "btn-outline btn-sm";
|
|
57
|
+
const btnType = obj.cancel ? "button" : "submit";
|
|
58
|
+
return `<button type="${btnType}" class="${btnClass}" value="${code}">${obj.name}</button>`;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Create PDS-compliant dialog structure
|
|
62
|
+
// When useForm is true, don't wrap in a form - let the content provide the form
|
|
63
|
+
if (options.useForm) {
|
|
64
|
+
dialog.innerHTML = /*html*/ `
|
|
65
|
+
<header>
|
|
66
|
+
<h2>${options.title}</h2>
|
|
67
|
+
</header>
|
|
68
|
+
|
|
69
|
+
<article id="msg-container"></article>
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
// Render message content first
|
|
73
|
+
const article = dialog.querySelector("#msg-container");
|
|
74
|
+
if (typeof message === "object" && message._$litType$) {
|
|
75
|
+
render(message, article);
|
|
76
|
+
} else if (typeof message === "string") {
|
|
77
|
+
article.textContent = message;
|
|
78
|
+
} else {
|
|
79
|
+
render(message, article);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Wait for content to render, then find the form and add buttons to it
|
|
83
|
+
requestAnimationFrame(() => {
|
|
84
|
+
const form = dialog.querySelector("form");
|
|
85
|
+
if (form) {
|
|
86
|
+
const footer = document.createElement("footer");
|
|
87
|
+
footer.innerHTML = buttons.join("");
|
|
88
|
+
form.appendChild(footer);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
} else {
|
|
92
|
+
dialog.innerHTML = /*html*/ `
|
|
93
|
+
<form method="dialog">
|
|
94
|
+
<header>
|
|
95
|
+
<h2>${options.title}</h2>
|
|
96
|
+
</header>
|
|
97
|
+
|
|
98
|
+
<article id="msg-container"></article>
|
|
99
|
+
|
|
100
|
+
<footer>
|
|
101
|
+
${buttons.join("")}
|
|
102
|
+
</footer>
|
|
103
|
+
</form>
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
// Render message content
|
|
107
|
+
const article = dialog.querySelector("#msg-container");
|
|
108
|
+
if (typeof message === "object" && message._$litType$) {
|
|
109
|
+
render(message, article);
|
|
110
|
+
} else if (typeof message === "string") {
|
|
111
|
+
article.textContent = message;
|
|
112
|
+
} else {
|
|
113
|
+
render(message, article);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Handle cancel button clicks
|
|
118
|
+
dialog.addEventListener("click", (e) => {
|
|
119
|
+
const btn = e.target.closest('button[value="cancel"]');
|
|
120
|
+
if (btn) {
|
|
121
|
+
dialog.close();
|
|
122
|
+
resolve(false);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Wait for form to exist before adding submit listener
|
|
127
|
+
const setupFormListener = () => {
|
|
128
|
+
const form = dialog.querySelector("form");
|
|
129
|
+
if (form) {
|
|
130
|
+
form.addEventListener("submit", (event) => {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
|
|
133
|
+
let result;
|
|
134
|
+
if (options.useForm && event.submitter.value === "ok") {
|
|
135
|
+
console.log("Found form:", form);
|
|
136
|
+
console.log("Form elements:", form ? Array.from(form.elements) : "no form");
|
|
137
|
+
result = new FormData(form);
|
|
138
|
+
console.log("FormData entries:", Array.from(result.entries()));
|
|
139
|
+
} else {
|
|
140
|
+
result = (event.submitter.value === "ok");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
dialog.close();
|
|
144
|
+
resolve(result);
|
|
145
|
+
});
|
|
146
|
+
} else {
|
|
147
|
+
// Form doesn't exist yet, wait and try again
|
|
148
|
+
requestAnimationFrame(setupFormListener);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
setupFormListener();
|
|
153
|
+
|
|
154
|
+
// Handle dialog close event
|
|
155
|
+
dialog.addEventListener("close", () => {
|
|
156
|
+
// Small delay to allow exit animation
|
|
157
|
+
setTimeout(() => dialog.remove(), 200);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Append to body and show
|
|
161
|
+
document.body.appendChild(dialog);
|
|
162
|
+
|
|
163
|
+
// Call optional rendered callback
|
|
164
|
+
if (typeof options.rendered === "function") {
|
|
165
|
+
options.rendered(dialog);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Show the dialog as modal
|
|
169
|
+
dialog.showModal();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Show an alert dialog
|
|
175
|
+
* @param {string|TemplateResult} message - Alert message
|
|
176
|
+
* @param {Object} options - Optional dialog options
|
|
177
|
+
* @returns {Promise}
|
|
178
|
+
*/
|
|
179
|
+
export async function alert(message, options = {}) {
|
|
180
|
+
const defaults = {
|
|
181
|
+
title: getPageTitle(),
|
|
182
|
+
type: "alert",
|
|
183
|
+
buttons: {
|
|
184
|
+
ok: { name: "OK", primary: true },
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return ask(message, { ...defaults, ...options });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Show a confirmation dialog
|
|
193
|
+
* @param {string|TemplateResult} message - Confirmation message
|
|
194
|
+
* @param {Object} options - Optional dialog options
|
|
195
|
+
* @returns {Promise<boolean>}
|
|
196
|
+
*/
|
|
197
|
+
export async function confirm(message, options = {}) {
|
|
198
|
+
const defaults = {
|
|
199
|
+
title: "Confirm Action",
|
|
200
|
+
type: "confirm",
|
|
201
|
+
buttons: {
|
|
202
|
+
ok: { name: "Confirm", primary: true },
|
|
203
|
+
cancel: { name: "Cancel", cancel: true },
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return ask(message, { ...defaults, ...options });
|
|
208
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function isObject(item) {
|
|
2
|
+
return item && typeof item === 'object' && !Array.isArray(item);
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function deepMerge(target, source) {
|
|
6
|
+
const output = { ...target };
|
|
7
|
+
if (isObject(target) && isObject(source)) {
|
|
8
|
+
Object.keys(source).forEach(key => {
|
|
9
|
+
if (isObject(source[key])) {
|
|
10
|
+
if (!(key in target))
|
|
11
|
+
Object.assign(output, { [key]: source[key] });
|
|
12
|
+
else
|
|
13
|
+
output[key] = deepMerge(target[key], source[key]);
|
|
14
|
+
} else {
|
|
15
|
+
Object.assign(output, { [key]: source[key] });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return output;
|
|
20
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Font Loading Utility
|
|
3
|
+
* Automatically loads fonts from Google Fonts when they're not available in the browser
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a font is available in the browser
|
|
8
|
+
* @param {string} fontName - The name of the font to check
|
|
9
|
+
* @returns {boolean} True if the font is available
|
|
10
|
+
*/
|
|
11
|
+
function isFontAvailable(fontName) {
|
|
12
|
+
// Clean up font name (remove quotes and extra spacing)
|
|
13
|
+
const cleanName = fontName.replace(/['"]/g, '').trim();
|
|
14
|
+
|
|
15
|
+
// System fonts that are always available
|
|
16
|
+
const systemFonts = [
|
|
17
|
+
'system-ui',
|
|
18
|
+
'-apple-system',
|
|
19
|
+
'sans-serif',
|
|
20
|
+
'serif',
|
|
21
|
+
'monospace',
|
|
22
|
+
'cursive',
|
|
23
|
+
'fantasy',
|
|
24
|
+
'ui-sans-serif',
|
|
25
|
+
'ui-serif',
|
|
26
|
+
'ui-monospace',
|
|
27
|
+
'ui-rounded'
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
if (systemFonts.includes(cleanName.toLowerCase())) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Use canvas-based detection
|
|
35
|
+
const canvas = document.createElement('canvas');
|
|
36
|
+
const context = canvas.getContext('2d');
|
|
37
|
+
|
|
38
|
+
if (!context) return false;
|
|
39
|
+
|
|
40
|
+
const testString = 'mmmmmmmmmmlli'; // Characters with varying widths
|
|
41
|
+
const testSize = '72px';
|
|
42
|
+
const baselineFont = 'monospace';
|
|
43
|
+
|
|
44
|
+
// Measure with baseline font
|
|
45
|
+
context.font = `${testSize} ${baselineFont}`;
|
|
46
|
+
const baselineWidth = context.measureText(testString).width;
|
|
47
|
+
|
|
48
|
+
// Measure with test font
|
|
49
|
+
context.font = `${testSize} "${cleanName}", ${baselineFont}`;
|
|
50
|
+
const testWidth = context.measureText(testString).width;
|
|
51
|
+
|
|
52
|
+
return baselineWidth !== testWidth;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extracts the primary font name from a font-family string
|
|
57
|
+
* @param {string} fontFamily - Font family string (e.g., "Roboto, sans-serif")
|
|
58
|
+
* @returns {string} The primary font name
|
|
59
|
+
*/
|
|
60
|
+
function extractPrimaryFont(fontFamily) {
|
|
61
|
+
if (!fontFamily) return null;
|
|
62
|
+
|
|
63
|
+
// Split by comma and get first font
|
|
64
|
+
const fonts = fontFamily.split(',').map(f => f.trim());
|
|
65
|
+
const primaryFont = fonts[0];
|
|
66
|
+
|
|
67
|
+
// Remove quotes
|
|
68
|
+
return primaryFont.replace(/['"]/g, '').trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Loads a Google Font dynamically
|
|
73
|
+
* @param {string} fontFamily - The font family to load (can be comma-separated list)
|
|
74
|
+
* @param {Object} options - Loading options
|
|
75
|
+
* @param {number[]} options.weights - Font weights to load (default: [400, 500, 600, 700])
|
|
76
|
+
* @param {boolean} options.italic - Whether to include italic variants (default: false)
|
|
77
|
+
* @returns {Promise<void>}
|
|
78
|
+
*/
|
|
79
|
+
export async function loadGoogleFont(fontFamily, options = {}) {
|
|
80
|
+
if (!fontFamily) {
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const {
|
|
85
|
+
weights = [400, 500, 600, 700],
|
|
86
|
+
italic = false
|
|
87
|
+
} = options;
|
|
88
|
+
|
|
89
|
+
const primaryFont = extractPrimaryFont(fontFamily);
|
|
90
|
+
|
|
91
|
+
if (!primaryFont) {
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check if font is already available
|
|
96
|
+
if (isFontAvailable(primaryFont)) {
|
|
97
|
+
return Promise.resolve();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Check if font link already exists
|
|
101
|
+
const encodedFont = encodeURIComponent(primaryFont);
|
|
102
|
+
const existingLink = document.querySelector(
|
|
103
|
+
`link[href*="fonts.googleapis.com"][href*="${encodedFont}"]`
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
if (existingLink) {
|
|
107
|
+
console.log(`Font "${primaryFont}" is already loading or loaded`);
|
|
108
|
+
return Promise.resolve();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(`Loading font "${primaryFont}" from Google Fonts...`);
|
|
112
|
+
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const link = document.createElement('link');
|
|
115
|
+
link.rel = 'stylesheet';
|
|
116
|
+
|
|
117
|
+
// Build Google Fonts URL with specified weights
|
|
118
|
+
const weightsParam = italic
|
|
119
|
+
? `ital,wght@0,${weights.join(';0,')};1,${weights.join(';1,')}`
|
|
120
|
+
: `wght@${weights.join(';')}`;
|
|
121
|
+
|
|
122
|
+
link.href = `https://fonts.googleapis.com/css2?family=${encodedFont}:${weightsParam}&display=swap`;
|
|
123
|
+
|
|
124
|
+
// Add a data attribute for easy identification
|
|
125
|
+
link.setAttribute('data-font-loader', primaryFont);
|
|
126
|
+
|
|
127
|
+
link.onload = () => {
|
|
128
|
+
console.log(`Successfully loaded font "${primaryFont}"`);
|
|
129
|
+
resolve();
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
link.onerror = () => {
|
|
133
|
+
console.warn(`Failed to load font "${primaryFont}" from Google Fonts`);
|
|
134
|
+
reject(new Error(`Failed to load font: ${primaryFont}`));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
document.head.appendChild(link);
|
|
138
|
+
|
|
139
|
+
// Set a timeout to prevent hanging indefinitely
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
if (!isFontAvailable(primaryFont)) {
|
|
142
|
+
console.warn(`Font "${primaryFont}" did not load within timeout`);
|
|
143
|
+
}
|
|
144
|
+
resolve(); // Resolve anyway to not block the application
|
|
145
|
+
}, 5000);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Loads fonts for all font families in a typography config
|
|
151
|
+
* @param {Object} typographyConfig - Typography configuration object
|
|
152
|
+
* @returns {Promise<void>}
|
|
153
|
+
*/
|
|
154
|
+
export async function loadTypographyFonts(typographyConfig) {
|
|
155
|
+
if (!typographyConfig) {
|
|
156
|
+
return Promise.resolve();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const fontFamilies = new Set();
|
|
160
|
+
|
|
161
|
+
// Collect all font families from the config
|
|
162
|
+
if (typographyConfig.fontFamilyHeadings) {
|
|
163
|
+
fontFamilies.add(typographyConfig.fontFamilyHeadings);
|
|
164
|
+
}
|
|
165
|
+
if (typographyConfig.fontFamilyBody) {
|
|
166
|
+
fontFamilies.add(typographyConfig.fontFamilyBody);
|
|
167
|
+
}
|
|
168
|
+
if (typographyConfig.fontFamilyMono) {
|
|
169
|
+
fontFamilies.add(typographyConfig.fontFamilyMono);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Load all fonts in parallel
|
|
173
|
+
const loadPromises = Array.from(fontFamilies).map(fontFamily =>
|
|
174
|
+
loadGoogleFont(fontFamily).catch(err => {
|
|
175
|
+
console.warn(`Could not load font: ${fontFamily}`, err);
|
|
176
|
+
// Don't fail the whole operation if one font fails
|
|
177
|
+
})
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
await Promise.all(loadPromises);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Removes previously loaded Google Fonts
|
|
185
|
+
* @param {string} fontName - Optional font name to remove. If not specified, removes all.
|
|
186
|
+
*/
|
|
187
|
+
export function unloadGoogleFont(fontName = null) {
|
|
188
|
+
const selector = fontName
|
|
189
|
+
? `link[data-font-loader="${fontName}"]`
|
|
190
|
+
: 'link[data-font-loader]';
|
|
191
|
+
|
|
192
|
+
const links = document.querySelectorAll(selector);
|
|
193
|
+
links.forEach(link => link.remove());
|
|
194
|
+
|
|
195
|
+
if (fontName) {
|
|
196
|
+
console.log(`Unloaded font "${fontName}"`);
|
|
197
|
+
} else {
|
|
198
|
+
console.log(`Unloaded ${links.length} font(s)`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const isLocal = ["127.0.0.1", "localhost"].includes(window.location.hostname);
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks whether a given value is not a string and contains the property 'strTag'.
|
|
5
|
+
*
|
|
6
|
+
* This function evaluates the input `val` to determine if it is of a type
|
|
7
|
+
* other than string and also verifies the existence of a property named 'strTag'
|
|
8
|
+
* within the value.
|
|
9
|
+
*
|
|
10
|
+
* @param {*} val - The value to be checked.
|
|
11
|
+
* @returns {boolean} True if the value is not a string and has the 'strTag' property, false otherwise.
|
|
12
|
+
*/
|
|
13
|
+
const isStrTagged = (val) => typeof val !== "string" && "strTag" in val;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Concatenates an array of strings into a single string, inserting placeholders
|
|
17
|
+
* between elements of the array based on their index position.
|
|
18
|
+
*
|
|
19
|
+
* @param {string[]} strings - An array of strings to be collated.
|
|
20
|
+
* @return {string} A single string consisting of the input strings concatenated with index-based placeholders.
|
|
21
|
+
*/
|
|
22
|
+
function collateStrings(strings) {
|
|
23
|
+
let s = "";
|
|
24
|
+
for (let i = 0; i <= strings.length - 1; i++) {
|
|
25
|
+
s += strings[i];
|
|
26
|
+
if (i < strings.length - 1) s += `{${i}}`;
|
|
27
|
+
}
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Replaces placeholders in the input string with values provided by the callback function.
|
|
33
|
+
* Placeholders are in the format `{n}` where `n` is a numeric index.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} str - The input string containing placeholders to replace.
|
|
36
|
+
* @param {function} callback - A function that takes an index as a parameter
|
|
37
|
+
* and returns the replacement value for the placeholder.
|
|
38
|
+
* @return {string} The string with placeholders replaced by the corresponding values.
|
|
39
|
+
*/
|
|
40
|
+
function replacePlaceholders(str, callback) {
|
|
41
|
+
return str.replace(/\{(\d+)\}/g, (match, index) => callback(index));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render the result of a `str` tagged template to a string. Note we don't need
|
|
46
|
+
* to do this for Lit templates, since Lit itself handles rendering.
|
|
47
|
+
*/
|
|
48
|
+
export const joinStringsAndValues = (strings, values, options) => {
|
|
49
|
+
const matchString = collateStrings(strings);
|
|
50
|
+
const tra = getTrans(matchString, options);
|
|
51
|
+
return replacePlaceholders(tra, (index) => {
|
|
52
|
+
return values[index];
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Retrieves a translated string or provides fallback behavior.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} t - The key of the string to be translated.
|
|
60
|
+
* @param {Object} [options] - Additional options for translation.
|
|
61
|
+
* @param {string} [options.desc] - A description of the string being translated, used for debugging purposes.
|
|
62
|
+
* @return {string} The translated string if found, or the key itself as a fallback. If debugging is enabled, returns a debug string.
|
|
63
|
+
*/
|
|
64
|
+
function getTrans(t, options = {}) {
|
|
65
|
+
window.__strings = window.__strings ?? {};
|
|
66
|
+
const tra = window.__strings[t]?.content;
|
|
67
|
+
|
|
68
|
+
if (!tra && isLocal) {
|
|
69
|
+
// eslint-disable-next-line no-console
|
|
70
|
+
console.log("🌐", t, options.desc ? `(${options.desc})` : ``);
|
|
71
|
+
}
|
|
72
|
+
if (window.env?.DEBUG_TRANSLATIONS) return `__${tra ?? t}`;
|
|
73
|
+
return tra ?? t;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Processes and transforms a given template into a string based on provided options.
|
|
78
|
+
* Accepts a tagged template or a string identifier and performs appropriate operations
|
|
79
|
+
* to return the resultant string.
|
|
80
|
+
*
|
|
81
|
+
* @param {Template | string | StrResult} template - The input template, which can be a tagged template or a string
|
|
82
|
+
* @param {Object} options
|
|
83
|
+
**/
|
|
84
|
+
export const msg = (template, options = {}) => {
|
|
85
|
+
if (!template) return "";
|
|
86
|
+
|
|
87
|
+
return isStrTagged(template)
|
|
88
|
+
? joinStringsAndValues(template.strings, template.values, options)
|
|
89
|
+
: getTrans(template, options);
|
|
90
|
+
};
|
package/src/js/lit.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Explicitly import from Lit
|
|
2
|
+
import { nothing, LitElement, html as litHtml, css as litCss, svg, render } from "lit";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
// Re-export while preserving type hints
|
|
6
|
+
/** @type {typeof import("./lit").html} */
|
|
7
|
+
export const html = litHtml;
|
|
8
|
+
|
|
9
|
+
/** @type {typeof import("./lit").css} */
|
|
10
|
+
export const css = litCss;
|
|
11
|
+
|
|
12
|
+
export { LitElement, nothing, svg, render };
|
|
13
|
+
|
|
14
|
+
export * from "lit/directives/repeat.js";
|
|
15
|
+
export * from "lit/directives/keyed.js";
|
|
16
|
+
export * from 'lit/directives/class-map.js';
|
|
17
|
+
|
|
18
|
+
export { ref, createRef } from "lit/directives/ref.js";
|
|
19
|
+
export { ifDefined } from "lit/directives/if-defined.js";
|
|
20
|
+
//export { str } from "@lit/localize";
|
|
21
|
+
export { until } from "lit/directives/until.js";
|
|
22
|
+
export { unsafeHTML } from "lit/directives/unsafe-html.js";
|
|
23
|
+
|
|
24
|
+
export { unsafeSVG } from 'lit/directives/unsafe-svg.js';
|
|
25
|
+
|
|
26
|
+
export { msg } from "./common/msg.js";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Loads the strings from a given (5-letter-code) locale
|
|
30
|
+
* @param {String} locale
|
|
31
|
+
*/
|
|
32
|
+
export async function loadLocale(locale) {
|
|
33
|
+
try {
|
|
34
|
+
window.__strings = await fetch(`/assets/locales/${locale}.json`).then((r) => r.json());
|
|
35
|
+
} catch {
|
|
36
|
+
window.__strings = {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export { html as staticHtml, unsafeStatic } from 'lit/static-html.js';
|