@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.
Files changed (129) hide show
  1. package/.storybook/addons/description/preview.js +15 -0
  2. package/.storybook/addons/description/register.js +60 -0
  3. package/.storybook/addons/html-preview/Panel.jsx +327 -0
  4. package/.storybook/addons/html-preview/constants.js +6 -0
  5. package/.storybook/addons/html-preview/preview.js +178 -0
  6. package/.storybook/addons/html-preview/register.js +16 -0
  7. package/.storybook/addons/pds-configurator/SearchTool.js +44 -0
  8. package/.storybook/addons/pds-configurator/Tool.js +30 -0
  9. package/.storybook/addons/pds-configurator/constants.js +9 -0
  10. package/.storybook/addons/pds-configurator/preview.js +159 -0
  11. package/.storybook/addons/pds-configurator/register.js +24 -0
  12. package/.storybook/docs.css +35 -0
  13. package/.storybook/htmlPreview.css +103 -0
  14. package/.storybook/htmlPreview.js +271 -0
  15. package/.storybook/main.js +160 -0
  16. package/.storybook/preview-body.html +48 -0
  17. package/.storybook/preview-head.html +11 -0
  18. package/.storybook/preview.js +1563 -0
  19. package/README.md +266 -0
  20. package/bin/index.js +40 -0
  21. package/dist/pds-reference.json +2101 -0
  22. package/package.json +45 -0
  23. package/pds.config.js +6 -0
  24. package/public/assets/css/app.css +1216 -0
  25. package/public/assets/data/auto-design-advanced.json +704 -0
  26. package/public/assets/data/auto-design-simple.json +123 -0
  27. package/public/assets/img/icon-512x512.png +0 -0
  28. package/public/assets/img/logo-trans.png +0 -0
  29. package/public/assets/img/logo.png +0 -0
  30. package/public/assets/js/app.js +15088 -0
  31. package/public/assets/js/app.js.map +7 -0
  32. package/public/assets/js/lit.js +1176 -0
  33. package/public/assets/js/lit.js.map +7 -0
  34. package/public/assets/js/pds.js +9801 -0
  35. package/public/assets/js/pds.js.map +7 -0
  36. package/public/assets/pds/components/pds-calendar.js +837 -0
  37. package/public/assets/pds/components/pds-drawer.js +857 -0
  38. package/public/assets/pds/components/pds-icon.js +338 -0
  39. package/public/assets/pds/components/pds-jsonform.js +1775 -0
  40. package/public/assets/pds/components/pds-richtext.js +1035 -0
  41. package/public/assets/pds/components/pds-scrollrow.js +331 -0
  42. package/public/assets/pds/components/pds-splitpanel.js +401 -0
  43. package/public/assets/pds/components/pds-tabstrip.js +251 -0
  44. package/public/assets/pds/components/pds-toaster.js +446 -0
  45. package/public/assets/pds/components/pds-upload.js +657 -0
  46. package/public/assets/pds/custom-elements.json +2003 -0
  47. package/public/assets/pds/icons/pds-icons.svg +498 -0
  48. package/public/assets/pds/pds-css-complete.json +1861 -0
  49. package/public/assets/pds/pds-runtime-config.json +11 -0
  50. package/public/assets/pds/pds.css-data.json +2152 -0
  51. package/public/assets/pds/styles/pds-components.css +1944 -0
  52. package/public/assets/pds/styles/pds-components.css.js +3895 -0
  53. package/public/assets/pds/styles/pds-primitives.css +352 -0
  54. package/public/assets/pds/styles/pds-primitives.css.js +711 -0
  55. package/public/assets/pds/styles/pds-styles.css +3761 -0
  56. package/public/assets/pds/styles/pds-styles.css.js +7529 -0
  57. package/public/assets/pds/styles/pds-tokens.css +699 -0
  58. package/public/assets/pds/styles/pds-tokens.css.js +1405 -0
  59. package/public/assets/pds/styles/pds-utilities.css +763 -0
  60. package/public/assets/pds/styles/pds-utilities.css.js +1533 -0
  61. package/public/assets/pds/vscode-custom-data.json +824 -0
  62. package/scripts/build-pds-reference.mjs +807 -0
  63. package/scripts/generate-stories.js +542 -0
  64. package/scripts/package-build.js +86 -0
  65. package/src/js/app.js +17 -0
  66. package/src/js/common/ask.js +208 -0
  67. package/src/js/common/common.js +20 -0
  68. package/src/js/common/font-loader.js +200 -0
  69. package/src/js/common/msg.js +90 -0
  70. package/src/js/lit.js +40 -0
  71. package/src/js/pds-core/pds-config.js +1162 -0
  72. package/src/js/pds-core/pds-enhancer-metadata.js +75 -0
  73. package/src/js/pds-core/pds-enhancers.js +357 -0
  74. package/src/js/pds-core/pds-enums.js +86 -0
  75. package/src/js/pds-core/pds-generator.js +5317 -0
  76. package/src/js/pds-core/pds-ontology.js +256 -0
  77. package/src/js/pds-core/pds-paths.js +109 -0
  78. package/src/js/pds-core/pds-query.js +571 -0
  79. package/src/js/pds-core/pds-registry.js +129 -0
  80. package/src/js/pds-core/pds.d.ts +129 -0
  81. package/src/js/pds.d.ts +408 -0
  82. package/src/js/pds.js +1579 -0
  83. package/src/pds-core/pds-api.js +105 -0
  84. package/stories/GettingStarted.md +96 -0
  85. package/stories/GettingStarted.stories.js +144 -0
  86. package/stories/WhatIsPDS.md +194 -0
  87. package/stories/WhatIsPDS.stories.js +144 -0
  88. package/stories/components/PdsCalendar.stories.js +263 -0
  89. package/stories/components/PdsDrawer.stories.js +623 -0
  90. package/stories/components/PdsIcon.stories.js +78 -0
  91. package/stories/components/PdsJsonform.stories.js +1444 -0
  92. package/stories/components/PdsRichtext.stories.js +367 -0
  93. package/stories/components/PdsScrollrow.stories.js +140 -0
  94. package/stories/components/PdsSplitpanel.stories.js +502 -0
  95. package/stories/components/PdsTabstrip.stories.js +442 -0
  96. package/stories/components/PdsToaster.stories.js +186 -0
  97. package/stories/components/PdsUpload.stories.js +66 -0
  98. package/stories/enhancements/Dropdowns.stories.js +185 -0
  99. package/stories/enhancements/InteractiveStates.stories.js +625 -0
  100. package/stories/enhancements/MeshGradients.stories.js +320 -0
  101. package/stories/enhancements/OpenGroups.stories.js +227 -0
  102. package/stories/enhancements/RangeSliders.stories.js +232 -0
  103. package/stories/enhancements/RequiredFields.stories.js +189 -0
  104. package/stories/enhancements/Toggles.stories.js +167 -0
  105. package/stories/foundations/Colors.stories.js +283 -0
  106. package/stories/foundations/Icons.stories.js +305 -0
  107. package/stories/foundations/SmartSurfaces.stories.js +367 -0
  108. package/stories/foundations/Spacing.stories.js +175 -0
  109. package/stories/foundations/Typography.stories.js +960 -0
  110. package/stories/foundations/ZIndex.stories.js +325 -0
  111. package/stories/patterns/BorderEffects.stories.js +72 -0
  112. package/stories/patterns/Layout.stories.js +99 -0
  113. package/stories/patterns/Utilities.stories.js +107 -0
  114. package/stories/primitives/Accordion.stories.js +359 -0
  115. package/stories/primitives/Alerts.stories.js +64 -0
  116. package/stories/primitives/Badges.stories.js +183 -0
  117. package/stories/primitives/Buttons.stories.js +229 -0
  118. package/stories/primitives/Cards.stories.js +353 -0
  119. package/stories/primitives/FormGroups.stories.js +569 -0
  120. package/stories/primitives/Forms.stories.js +131 -0
  121. package/stories/primitives/Media.stories.js +203 -0
  122. package/stories/primitives/Tables.stories.js +232 -0
  123. package/stories/reference/ReferenceCatalog.stories.js +28 -0
  124. package/stories/reference/reference-catalog.js +413 -0
  125. package/stories/reference/reference-docs.js +302 -0
  126. package/stories/reference/reference-helpers.js +310 -0
  127. package/stories/utilities/GridSystem.stories.js +208 -0
  128. package/stories/utils/PdsAsk.stories.js +420 -0
  129. 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';