cf-elements 1.0.2 → 1.0.4

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 (2) hide show
  1. package/cf-elements.js +145 -7
  2. package/package.json +1 -1
package/cf-elements.js CHANGED
@@ -99,6 +99,116 @@
99
99
  }
100
100
  })();
101
101
 
102
+ // ==========================================================================
103
+ // GOOGLE FONTS - Auto-load fonts from font attributes
104
+ // ==========================================================================
105
+
106
+ // System fonts that don't need to be loaded from Google
107
+ const SYSTEM_FONTS = new Set([
108
+ 'sans-serif', 'serif', 'monospace', 'cursive', 'fantasy', 'system-ui',
109
+ 'ui-sans-serif', 'ui-serif', 'ui-monospace', 'ui-rounded',
110
+ 'arial', 'helvetica', 'times new roman', 'times', 'courier new', 'courier',
111
+ 'verdana', 'georgia', 'palatino', 'garamond', 'bookman', 'tahoma',
112
+ 'trebuchet ms', 'arial black', 'impact', 'comic sans ms', 'inherit'
113
+ ]);
114
+
115
+ // Track which fonts have been loaded to avoid duplicates
116
+ const loadedFonts = new Set();
117
+
118
+ /**
119
+ * Extract Google Font names from the document
120
+ * Scans font attributes on cf-* elements and styleguide data
121
+ */
122
+ function extractFontsFromDocument() {
123
+ const fonts = new Set();
124
+
125
+ // 1. Extract from font attributes on elements
126
+ document.querySelectorAll('[font]').forEach(el => {
127
+ const font = el.getAttribute('font');
128
+ if (font) {
129
+ // Clean and add font name
130
+ const cleanFont = font.replace(/["']/g, '').split(',')[0].trim();
131
+ if (cleanFont && !SYSTEM_FONTS.has(cleanFont.toLowerCase())) {
132
+ fonts.add(cleanFont);
133
+ }
134
+ }
135
+ });
136
+
137
+ // 2. Extract from styleguide data if present
138
+ const styleguideEl = document.getElementById('cf-styleguide-data');
139
+ if (styleguideEl) {
140
+ try {
141
+ const data = JSON.parse(styleguideEl.textContent);
142
+ if (data.typography) {
143
+ const { headlineFont, subheadlineFont, contentFont } = data.typography;
144
+ [headlineFont, subheadlineFont, contentFont].forEach(font => {
145
+ if (font && !SYSTEM_FONTS.has(font.toLowerCase())) {
146
+ fonts.add(font);
147
+ }
148
+ });
149
+ }
150
+ } catch (e) {
151
+ // Ignore parse errors
152
+ }
153
+ }
154
+
155
+ // 3. Extract from inline font-family styles in cf-* elements
156
+ document.querySelectorAll('cf-headline, cf-subheadline, cf-paragraph, cf-button').forEach(el => {
157
+ const style = el.getAttribute('style') || '';
158
+ const match = style.match(/font-family:\s*["']?([^"';,]+)/i);
159
+ if (match) {
160
+ const font = match[1].trim();
161
+ if (font && !SYSTEM_FONTS.has(font.toLowerCase())) {
162
+ fonts.add(font);
163
+ }
164
+ }
165
+ });
166
+
167
+ return fonts;
168
+ }
169
+
170
+ /**
171
+ * Inject Google Fonts stylesheet into document head
172
+ */
173
+ function injectGoogleFonts(fonts) {
174
+ if (!fonts || fonts.size === 0) return;
175
+
176
+ // Filter out already loaded fonts
177
+ const newFonts = Array.from(fonts).filter(f => !loadedFonts.has(f));
178
+ if (newFonts.length === 0) return;
179
+
180
+ // Mark as loaded
181
+ newFonts.forEach(f => loadedFonts.add(f));
182
+
183
+ // Build Google Fonts URL
184
+ const fontParams = newFonts
185
+ .map(font => font.replace(/ /g, '+'))
186
+ .join('&family=');
187
+
188
+ const url = `https://fonts.googleapis.com/css2?family=${fontParams}:wght@300;400;500;600;700;800;900&display=swap`;
189
+
190
+ // Check if already loaded
191
+ if (document.querySelector(`link[href^="https://fonts.googleapis.com"][href*="${newFonts[0].replace(/ /g, '+')}"]`)) {
192
+ return;
193
+ }
194
+
195
+ // Inject link tag
196
+ const link = document.createElement('link');
197
+ link.id = 'cf-google-fonts';
198
+ link.rel = 'stylesheet';
199
+ link.href = url;
200
+ document.head.appendChild(link);
201
+ }
202
+
203
+ /**
204
+ * Auto-load Google Fonts from document
205
+ * Called before element rendering
206
+ */
207
+ function loadGoogleFonts() {
208
+ const fonts = extractFontsFromDocument();
209
+ injectGoogleFonts(fonts);
210
+ }
211
+
102
212
  // ==========================================================================
103
213
  // CONSTANTS & MAPPINGS
104
214
  // ==========================================================================
@@ -944,9 +1054,10 @@
944
1054
  * bg-style - Background image style: cover, cover-center (default), parallax, w100, w100h100, no-repeat, repeat, repeat-x, repeat-y
945
1055
  * gradient - CSS gradient
946
1056
  * overlay - Overlay color (rgba)
947
- * text-color - Default text color
1057
+ * text-color - Default text color (inherited by child elements)
948
1058
  * link-color - Default link color
949
- * font-family - Default font family (e.g., '"Roboto", sans-serif')
1059
+ * font - Default font family (e.g., "Roboto") - inherited by child elements
1060
+ * font-family - Alias for font
950
1061
  * font-weight - Default font weight
951
1062
  * header-code - Custom header HTML/scripts
952
1063
  * footer-code - Custom footer HTML/scripts
@@ -961,7 +1072,8 @@
961
1072
  const overlay = attr(this, "overlay");
962
1073
  const textColor = attr(this, "text-color", "#334155");
963
1074
  const linkColor = attr(this, "link-color", "#3b82f6");
964
- const fontFamily = attr(this, "font-family");
1075
+ // Support both "font" (simple) and "font-family" (explicit) attributes
1076
+ const font = attr(this, "font") || attr(this, "font-family");
965
1077
  const fontWeight = attr(this, "font-weight");
966
1078
  const headerCode = attr(this, "header-code");
967
1079
  const footerCode = attr(this, "footer-code");
@@ -971,8 +1083,15 @@
971
1083
  width: "100%",
972
1084
  "min-height": "100vh",
973
1085
  position: "relative",
1086
+ // Apply text color and font for CSS inheritance
1087
+ color: textColor,
974
1088
  };
975
1089
 
1090
+ // Apply font-family for inheritance by child elements
1091
+ if (font) {
1092
+ styles["font-family"] = `"${font}", sans-serif`;
1093
+ }
1094
+
976
1095
  if (gradient) {
977
1096
  styles["background"] = gradient;
978
1097
  } else if (bg) {
@@ -998,7 +1117,7 @@
998
1117
 
999
1118
  // Build optional data attributes for settings
1000
1119
  let optionalAttrs = "";
1001
- if (fontFamily) optionalAttrs += ` data-font-family="${fontFamily}"`;
1120
+ if (font) optionalAttrs += ` data-font="${font}"`;
1002
1121
  if (fontWeight) optionalAttrs += ` data-font-weight="${fontWeight}"`;
1003
1122
  if (headerCode)
1004
1123
  optionalAttrs += ` data-header-code="${encodeURIComponent(
@@ -2058,6 +2177,7 @@
2058
2177
  */
2059
2178
  class CFHeadline extends CFElement {
2060
2179
  render() {
2180
+ const elementId = attr(this, "element-id");
2061
2181
  const size = attr(this, "size", "48px");
2062
2182
  const weight = attr(this, "weight", "bold");
2063
2183
  const font = attr(this, "font");
@@ -2103,6 +2223,7 @@
2103
2223
  // Build data attributes for round-trip conversion
2104
2224
  // Store original size (preset or px) for reliable roundtrip
2105
2225
  let dataAttrs = 'data-type="Headline/V1"';
2226
+ if (elementId) dataAttrs += ` data-element-id="${elementId}"`;
2106
2227
  dataAttrs += ` data-size="${size}"`;
2107
2228
  dataAttrs += ` data-weight="${weight}"`;
2108
2229
  if (hasExplicitColor && color) {
@@ -2119,6 +2240,9 @@
2119
2240
  if (icon) dataAttrs += ` data-icon="${icon}"`;
2120
2241
  if (icon && iconAlign !== "left") dataAttrs += ` data-icon-align="${iconAlign}"`;
2121
2242
 
2243
+ // Build ID attribute for scroll-to and show-hide targeting
2244
+ const idAttr = elementId ? ` id="${elementId}"` : "";
2245
+
2122
2246
  // Build icon HTML if present
2123
2247
  let iconHtml = "";
2124
2248
  if (icon) {
@@ -2136,7 +2260,7 @@
2136
2260
  dataAttrs += animationAttrs;
2137
2261
 
2138
2262
  this.outerHTML = `
2139
- <div ${dataAttrs} style="${buildStyle(wrapperStyles)}">
2263
+ <div${idAttr} ${dataAttrs} style="${buildStyle(wrapperStyles)}">
2140
2264
  <${tag} style="${buildStyle(textStyles)}">${textWithIcon}</${tag}>
2141
2265
  </div>
2142
2266
  `;
@@ -2149,6 +2273,7 @@
2149
2273
  */
2150
2274
  class CFSubheadline extends CFElement {
2151
2275
  render() {
2276
+ const elementId = attr(this, "element-id");
2152
2277
  const size = attr(this, "size", "24px");
2153
2278
  const weight = attr(this, "weight", "normal");
2154
2279
  const font = attr(this, "font");
@@ -2192,6 +2317,7 @@
2192
2317
  // Build data attributes for round-trip conversion
2193
2318
  // Store original size (preset or px) for reliable roundtrip
2194
2319
  let dataAttrs = 'data-type="SubHeadline/V1"';
2320
+ if (elementId) dataAttrs += ` data-element-id="${elementId}"`;
2195
2321
  dataAttrs += ` data-size="${size}"`;
2196
2322
  dataAttrs += ` data-weight="${weight}"`;
2197
2323
  if (hasExplicitColor && color) {
@@ -2208,6 +2334,9 @@
2208
2334
  if (icon) dataAttrs += ` data-icon="${icon}"`;
2209
2335
  if (icon && iconAlign !== "left") dataAttrs += ` data-icon-align="${iconAlign}"`;
2210
2336
 
2337
+ // Build ID attribute for scroll-to and show-hide targeting
2338
+ const idAttr = elementId ? ` id="${elementId}"` : "";
2339
+
2211
2340
  // Build icon HTML if present
2212
2341
  let iconHtml = "";
2213
2342
  if (icon) {
@@ -2225,7 +2354,7 @@
2225
2354
  dataAttrs += animationAttrs;
2226
2355
 
2227
2356
  this.outerHTML = `
2228
- <div ${dataAttrs} style="${buildStyle(wrapperStyles)}">
2357
+ <div${idAttr} ${dataAttrs} style="${buildStyle(wrapperStyles)}">
2229
2358
  <${tag} style="${buildStyle(textStyles)}">${textWithIcon}</${tag}>
2230
2359
  </div>
2231
2360
  `;
@@ -2238,6 +2367,7 @@
2238
2367
  */
2239
2368
  class CFParagraph extends CFElement {
2240
2369
  render() {
2370
+ const elementId = attr(this, "element-id");
2241
2371
  const size = attr(this, "size", "16px");
2242
2372
  const weight = attr(this, "weight", "normal");
2243
2373
  const font = attr(this, "font");
@@ -2289,6 +2419,7 @@
2289
2419
  // Build data attributes for round-trip conversion
2290
2420
  // Store original size (preset or px) for reliable roundtrip
2291
2421
  let dataAttrs = 'data-type="Paragraph/V1"';
2422
+ if (elementId) dataAttrs += ` data-element-id="${elementId}"`;
2292
2423
  dataAttrs += ` data-size="${size}"`;
2293
2424
  dataAttrs += ` data-weight="${weight}"`;
2294
2425
  if (hasExplicitColor && color) {
@@ -2307,6 +2438,9 @@
2307
2438
  if (icon) dataAttrs += ` data-icon="${icon}"`;
2308
2439
  if (icon && iconAlign !== "left") dataAttrs += ` data-icon-align="${iconAlign}"`;
2309
2440
 
2441
+ // Build ID attribute for scroll-to and show-hide targeting
2442
+ const idAttr = elementId ? ` id="${elementId}"` : "";
2443
+
2310
2444
  // Build icon HTML if present
2311
2445
  let iconHtml = "";
2312
2446
  if (icon) {
@@ -2324,7 +2458,7 @@
2324
2458
  dataAttrs += animationAttrs;
2325
2459
 
2326
2460
  this.outerHTML = `
2327
- <div ${dataAttrs} style="${buildStyle(wrapperStyles)}">
2461
+ <div${idAttr} ${dataAttrs} style="${buildStyle(wrapperStyles)}">
2328
2462
  <p style="${buildStyle(textStyles)}">${textWithIcon}</p>
2329
2463
  </div>
2330
2464
  `;
@@ -4588,6 +4722,7 @@
4588
4722
  // Auto-initialize when DOM is ready
4589
4723
  if (document.readyState === "loading") {
4590
4724
  document.addEventListener("DOMContentLoaded", () => {
4725
+ loadGoogleFonts(); // Load fonts before rendering
4591
4726
  styleguideManager.init();
4592
4727
  brandAssetsManager.init();
4593
4728
  initFunnelWind();
@@ -4600,6 +4735,7 @@
4600
4735
  } else {
4601
4736
  // DOM already ready, use requestAnimationFrame to ensure all elements are parsed
4602
4737
  requestAnimationFrame(() => {
4738
+ loadGoogleFonts(); // Load fonts before rendering
4603
4739
  styleguideManager.init();
4604
4740
  brandAssetsManager.init();
4605
4741
  initFunnelWind();
@@ -4616,12 +4752,14 @@
4616
4752
  init: initFunnelWind,
4617
4753
  initAnimations: initAnimations,
4618
4754
  loadAnimateCSS: loadAnimateCSS,
4755
+ loadGoogleFonts: loadGoogleFonts,
4619
4756
  initVideoBackgrounds: initVideoBackgrounds,
4620
4757
  elements: elements,
4621
4758
  StyleguideManager: styleguideManager,
4622
4759
  BrandAssetsManager: brandAssetsManager,
4623
4760
  initStyleguide: (data) => {
4624
4761
  styleguideManager.init(data);
4762
+ loadGoogleFonts(); // Load fonts from styleguide
4625
4763
  initFunnelWind();
4626
4764
  },
4627
4765
  initBrandAssets: (data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-elements",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Zero-dependency markup library that generates ClickFunnels compatible HTML with inline styles",
5
5
  "main": "cf-elements.js",
6
6
  "browser": "cf-elements.js",