@widgetic/editor 4.0.1 → 4.0.2

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.
@@ -1,11 +1,9 @@
1
1
  <script lang="ts">
2
- // Import Shadcn UI components from Widgetic design-system package
3
- import { Button, Portal } from '@widgetic/design-system/components';
4
- import { IconArrowsDiagonal, IconArrowsMaximize, IconX, IconBrush, IconBold, IconItalic, IconUnderline, IconLink } from '@widgetic/design-system/icons';
2
+ import { Portal } from '@widgetic/design-system/components';
3
+ import { IconArrowsDiagonal } from '@widgetic/design-system/icons';
5
4
  import { onMount, onDestroy } from 'svelte';
6
5
 
7
- // Local components
8
- import { InputControllerHeader, TextAreaInputController } from './index';
6
+ import { TextAreaInputController } from './index';
9
7
  import type { InputControllerConfig } from './index';
10
8
 
11
9
  /**
@@ -50,8 +48,15 @@
50
48
  function createPreviewText(text: string) {
51
49
  if (!text) return '';
52
50
 
51
+ let html = text;
52
+ if (!html.includes('<') && html.includes('&lt;')) {
53
+ const decoded = document.createElement('textarea');
54
+ decoded.innerHTML = html;
55
+ html = decoded.value;
56
+ }
57
+
53
58
  const parser = new DOMParser();
54
- const doc = parser.parseFromString(text, 'text/html');
59
+ const doc = parser.parseFromString(html, 'text/html');
55
60
 
56
61
  function processNode(node: Node): string {
57
62
  if (node.nodeType === Node.TEXT_NODE) {
@@ -60,59 +65,59 @@
60
65
 
61
66
  if (node.nodeType === Node.ELEMENT_NODE) {
62
67
  const element = node as Element;
63
- const tag = element.tagName.toLowerCase();
68
+ let tag = element.tagName.toLowerCase();
64
69
  const attributes: Record<string, string> = {};
70
+ const children = Array.from(node.childNodes)
71
+ .map(child => processNode(child))
72
+ .join('');
73
+
74
+ // Flatten block wrappers so marks render inside the compact clamp box
75
+ if (tag === 'p' || tag === 'div' || tag === 'br') {
76
+ return tag === 'br' ? ' ' : `${children} `;
77
+ }
65
78
 
66
- // Preserve all style attributes
67
79
  const style = element.getAttribute('style');
68
80
  if (style) {
69
- // Extract color value from style
70
81
  const colorMatch = style.match(/color:\s*([^;]+)/);
71
82
  if (colorMatch) {
72
- const color = colorMatch[1];
73
- // Set the color as a CSS variable
74
- attributes.style = `--color: ${color}; ${style}`;
83
+ attributes.style = `--color: ${colorMatch[1]}; ${style}`;
75
84
  } else {
76
85
  attributes.style = style;
77
86
  }
78
87
  }
79
88
 
80
- // Preserve data-text-style attribute
81
89
  const textStyle = element.getAttribute('data-text-style');
82
90
  if (textStyle) {
83
91
  attributes['data-text-style'] = textStyle;
84
- // Add preserve-color class
85
92
  attributes.class = (attributes.class || '') + ' preserve-color';
86
93
  }
87
94
 
88
- // Preserve heading classes
89
95
  if (tag.match(/^h[1-4]$/)) {
96
+ tag = 'span';
90
97
  attributes.class = (attributes.class || '') + ' tiptap-heading';
91
98
  }
92
99
 
93
- // Build start tag with attributes
94
100
  let startTag = `<${tag}`;
95
- for (const [key, value] of Object.entries(attributes)) {
96
- startTag += ` ${key}="${value.trim()}"`;
101
+ for (const [key, attrValue] of Object.entries(attributes)) {
102
+ startTag += ` ${key}="${attrValue.trim()}"`;
97
103
  }
98
104
  startTag += '>';
99
105
 
100
- // Process child nodes
101
- const children = Array.from(node.childNodes)
102
- .map(child => processNode(child))
103
- .join('');
104
-
105
106
  return `${startTag}${children}</${tag}>`;
106
107
  }
107
108
 
108
109
  return '';
109
110
  }
110
111
 
111
- const processedNodes = Array.from(doc.body.childNodes)
112
+ return Array.from(doc.body.childNodes)
112
113
  .map(node => processNode(node))
113
- .join('');
114
+ .join('')
115
+ .trim();
116
+ }
114
117
 
115
- return processedNodes;
118
+ function handleFullEditorChange(html: string) {
119
+ value = html;
120
+ if (typeof onChange === 'function') onChange(html);
116
121
  }
117
122
 
118
123
  function handleClick() {
@@ -139,6 +144,10 @@
139
144
  }
140
145
  }
141
146
 
147
+ $: if (value) {
148
+ console.log('[TextAreaPreview] compact preview html', value.slice(0, 180));
149
+ }
150
+
142
151
  function handlePortalMouseEnter() {
143
152
  // console.log('Portal mouse entered');
144
153
  isPortalHovered = true;
@@ -211,42 +220,31 @@
211
220
  }
212
221
  </script>
213
222
 
214
- <div class="text-area-preview-input-controller flex items-center justify-between w-full widgetic-colors">
215
- <div class="header-container flex-1 min-w-0">
216
- <InputControllerHeader {label} />
217
- </div>
218
-
223
+ <div class="text-area-preview-input-controller w-full min-w-0 overflow-hidden widgetic-colors">
219
224
  <div
220
- class="text-area-preview-main-container w-[185px] flex-shrink-0"
225
+ class="text-area-preview-main-container w-full min-w-0 overflow-hidden"
221
226
  >
222
227
  <div
223
- class="h-[92px] w-full border border-grey2 flex items-start bg-grey1 transition-colors duration-200 hover:border-forest-green cursor-pointer group overflow-hidden"
228
+ class="textarea-ic-preview-box relative h-[92px] w-full min-w-0 border border-grey2 bg-grey1 transition-colors duration-200 hover:border-forest-green cursor-pointer group overflow-hidden"
224
229
  style="border-radius: var(--radius-large);"
225
230
  on:click={handleClick}
226
231
  on:keydown={(e) => e.key === 'Enter' && handleClick()}
227
232
  role="button"
228
233
  tabindex="0"
229
234
  >
230
- <div class="relative w-full h-full">
235
+ <div class="textarea-ic-preview-copy min-w-0 overflow-hidden pr-10 pt-[13px] pb-4 pl-4">
231
236
  <div
232
- class="absolute top-0 left-0 w-full h-full flex flex-col"
237
+ class="textarea-ic-preview-html break-words text-grey7 text-sm leading-5"
238
+ class:text-grey3={isShowingPlaceholder}
233
239
  >
234
- <div class="flex-1" style="padding: 13px 48px 16px 16px;">
235
- <div
236
- class="subtext line-clamp-3 break-words text-ellipsis prose-sm w-[129px] text-grey7"
237
- class:text-grey3={isShowingPlaceholder}
238
- style="display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;"
239
- >
240
- {@html previewText}
241
- </div>
242
- </div>
243
- <div class="absolute top-[14px] right-[12px]">
244
- <IconArrowsDiagonal
245
- class="w-5 h-5 text-grey5 group-hover:text-grey7 transition-colors duration-200"
246
- />
247
- </div>
240
+ {@html previewText}
248
241
  </div>
249
242
  </div>
243
+ <div class="textarea-ic-expand-icon absolute top-[14px] right-[12px]">
244
+ <IconArrowsDiagonal
245
+ class="w-5 h-5 text-grey5 group-hover:text-grey7 transition-colors duration-200"
246
+ />
247
+ </div>
250
248
  </div>
251
249
  </div>
252
250
 
@@ -280,24 +278,14 @@
280
278
  style="border-radius: var(--radius-large);"
281
279
  ></div>
282
280
 
283
- <Button
284
- variant="ghost"
285
- class="absolute z-[999] p-0 hover:bg-transparent h-[32px] w-[32px] top-[10px] right-[10px]"
286
- onclick={handleClose}
287
- >
288
- <IconX
289
- class="w-[24px] h-[24px] text-grey5 hover:text-grey7 transition-colors duration-200"
290
- style="min-width: 24px; min-height: 24px;"
291
- />
292
- </Button>
293
-
294
281
  <TextAreaInputController
295
282
  class="text-area w-full h-full transition-colors duration-200 {isPortalHovered ? 'border-solid !border-forest-green' : ''}"
296
283
  label={label}
297
- value={value}
298
- onChange={onChange}
284
+ bind:value
285
+ onChange={handleFullEditorChange}
299
286
  config={config}
300
287
  description={description}
288
+ onClose={handleClose}
301
289
  />
302
290
  </div>
303
291
  </Portal>
@@ -318,7 +306,7 @@
318
306
 
319
307
 
320
308
  /* Heading styles matching tiptap editor */
321
- :global(.subtext h1.tiptap-heading) {
309
+ :global(.textarea-ic-preview-html h1.tiptap-heading) {
322
310
  font-size: 2em !important;
323
311
  font-weight: 600 !important;
324
312
  line-height: 1.2 !important;
@@ -327,7 +315,7 @@
327
315
  color: inherit !important;
328
316
  }
329
317
 
330
- :global(.subtext h2.tiptap-heading) {
318
+ :global(.textarea-ic-preview-html h2.tiptap-heading) {
331
319
  font-size: 1.5em !important;
332
320
  font-weight: 600 !important;
333
321
  line-height: 1.3 !important;
@@ -336,7 +324,7 @@
336
324
  color: inherit !important;
337
325
  }
338
326
 
339
- :global(.subtext h3.tiptap-heading) {
327
+ :global(.textarea-ic-preview-html h3.tiptap-heading) {
340
328
  font-size: 1.17em !important;
341
329
  font-weight: 600 !important;
342
330
  line-height: 1.3 !important;
@@ -345,7 +333,7 @@
345
333
  color: inherit !important;
346
334
  }
347
335
 
348
- :global(.subtext h4.tiptap-heading) {
336
+ :global(.textarea-ic-preview-html h4.tiptap-heading) {
349
337
  font-size: 1em !important;
350
338
  font-weight: 600 !important;
351
339
  line-height: 1.4 !important;
@@ -354,36 +342,50 @@
354
342
  color: inherit !important;
355
343
  }
356
344
 
345
+ :global(.textarea-ic-preview-html) {
346
+ display: -webkit-box;
347
+ -webkit-box-orient: vertical;
348
+ -webkit-line-clamp: 3;
349
+ line-clamp: 3;
350
+ overflow: hidden;
351
+ }
352
+
357
353
  /* Color preservation */
358
- :global(.subtext [style*="color"]) {
354
+ :global(.textarea-ic-preview-html [style*="color"]) {
359
355
  color: var(--color) !important;
360
356
  }
361
357
 
362
- /* Text formatting */
363
- :global(.subtext strong),
364
- :global(.subtext em),
365
- :global(.subtext u) {
366
- color: inherit !important;
358
+ /* Text formatting — {@html} children need :global; include b/i as well as strong/em/u */
359
+ :global(.textarea-ic-preview-html :is(strong, b, [style*="font-weight"])) {
360
+ font-weight: 700 !important;
361
+ }
362
+
363
+ :global(.textarea-ic-preview-html :is(em, i)) {
364
+ font-style: italic !important;
365
+ }
366
+
367
+ :global(.textarea-ic-preview-html :is(u, [style*="text-decoration: underline"])) {
368
+ text-decoration: underline !important;
367
369
  }
368
370
 
369
- :global(.subtext strong[style*="color"]),
370
- :global(.subtext em[style*="color"]),
371
- :global(.subtext u[style*="color"]) {
371
+ :global(.textarea-ic-preview-html strong[style*="color"]),
372
+ :global(.textarea-ic-preview-html em[style*="color"]),
373
+ :global(.textarea-ic-preview-html u[style*="color"]) {
372
374
  color: var(--color) !important;
373
375
  }
374
376
 
375
377
  /* Add specific color inheritance for data-text-style */
376
- :global(.subtext [data-text-style*="color"]) {
378
+ :global(.textarea-ic-preview-html [data-text-style*="color"]) {
377
379
  color: var(--color) !important;
378
380
  }
379
381
 
380
382
  /* Ensure all text elements can inherit colors */
381
- :global(.subtext *) {
383
+ :global(.textarea-ic-preview-html *) {
382
384
  color: inherit;
383
385
  }
384
386
 
385
387
  /* Ensure paragraphs have no margins */
386
- :global(.subtext p) {
388
+ :global(.textarea-ic-preview-html p) {
387
389
  margin-top: 0 !important;
388
390
  margin-bottom: 0 !important;
389
391
  }
package/dist/config.d.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  interface Config {
2
2
  GOOGLE_FONTS_API_KEY: string;
3
3
  }
4
+ export declare function setGoogleFontsApiKey(key: string): void;
5
+ /**
6
+ * Google Fonts Web Fonts API key.
7
+ * Order: runtime setter → PUBLIC_GOOGLE_FONTS_API_KEY → VITE_GOOGLE_FONTS_API_KEY.
8
+ */
9
+ export declare function getGoogleFontsKey(): string;
4
10
  export declare const config: Config;
5
11
  export {};
package/dist/config.js CHANGED
@@ -1,19 +1,31 @@
1
+ /**
2
+ * Set by the consuming app (widget-creator / site) so the published
3
+ * `@widgetic/editor` package never bakes a key into dist at pack time.
4
+ * Vite still inlines PUBLIC_/VITE_ keys when the consumer bundles this source.
5
+ */
6
+ let runtimeGoogleFontsApiKey = '';
7
+ export function setGoogleFontsApiKey(key) {
8
+ runtimeGoogleFontsApiKey = String(key || '').trim();
9
+ }
1
10
  /**
2
11
  * Google Fonts Web Fonts API key.
3
- * Set in consuming app .env as PUBLIC_GOOGLE_FONTS_API_KEY or VITE_GOOGLE_FONTS_API_KEY.
4
- * Avoids $env/static/public import so the library works when embedded in site/creator.
12
+ * Order: runtime setter PUBLIC_GOOGLE_FONTS_API_KEY VITE_GOOGLE_FONTS_API_KEY.
5
13
  */
6
- function getGoogleFontsKey() {
14
+ export function getGoogleFontsKey() {
15
+ if (runtimeGoogleFontsApiKey)
16
+ return runtimeGoogleFontsApiKey;
7
17
  if (typeof import.meta !== 'undefined' && import.meta.env) {
8
- const fromVite = import.meta.env.VITE_GOOGLE_FONTS_API_KEY;
9
- if (fromVite && String(fromVite).trim())
10
- return String(fromVite).trim();
11
18
  const fromPublicMeta = import.meta.env.PUBLIC_GOOGLE_FONTS_API_KEY;
12
19
  if (fromPublicMeta && String(fromPublicMeta).trim())
13
20
  return String(fromPublicMeta).trim();
21
+ const fromVite = import.meta.env.VITE_GOOGLE_FONTS_API_KEY;
22
+ if (fromVite && String(fromVite).trim())
23
+ return String(fromVite).trim();
14
24
  }
15
25
  return '';
16
26
  }
17
27
  export const config = {
18
- GOOGLE_FONTS_API_KEY: getGoogleFontsKey(),
28
+ get GOOGLE_FONTS_API_KEY() {
29
+ return getGoogleFontsKey();
30
+ },
19
31
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { default as PropsEditor } from './components/editors/PropsEditor.svelte';
2
2
  export { default as WidgetPreview } from './components/WidgetPreview.svelte';
3
3
  export { setFileBrowserUploadConfig } from '@widgetic/file-browser';
4
+ export { setGoogleFontsApiKey, getGoogleFontsKey } from './config';
4
5
  export { default as EmbedEditor } from './components/editors/EmbedEditor.svelte';
package/dist/index.js CHANGED
@@ -4,5 +4,6 @@
4
4
  export { default as PropsEditor } from './components/editors/PropsEditor.svelte';
5
5
  export { default as WidgetPreview } from './components/WidgetPreview.svelte';
6
6
  export { setFileBrowserUploadConfig } from '@widgetic/file-browser';
7
+ export { setGoogleFontsApiKey, getGoogleFontsKey } from './config';
7
8
  // Embed Editor
8
9
  export { default as EmbedEditor } from './components/editors/EmbedEditor.svelte';
@@ -70,6 +70,8 @@ declare class FontManager {
70
70
  private static forbiddenApiKeyWarned;
71
71
  private static badRequestApiKeyWarned;
72
72
  private catalogFetchPromise;
73
+ /** Popular Google families shown when the Web Fonts API key is missing or blocked. */
74
+ private static getFallbackCatalog;
73
75
  private hasGoogleFontsApiKey;
74
76
  private warnMissingApiKey;
75
77
  private warnForbiddenApiKey;
@@ -112,6 +112,25 @@ class FontManager {
112
112
  static forbiddenApiKeyWarned = false;
113
113
  static badRequestApiKeyWarned = false;
114
114
  catalogFetchPromise = null;
115
+ /** Popular Google families shown when the Web Fonts API key is missing or blocked. */
116
+ static getFallbackCatalog() {
117
+ const sans = ['Inter', 'Roboto', 'Open Sans', 'Poppins', 'Lato', 'Montserrat', 'Nunito', 'Source Sans 3', 'Work Sans', 'DM Sans'];
118
+ const serif = ['Playfair Display', 'Merriweather', 'Libre Baskerville', 'Lora', 'Cormorant Garamond'];
119
+ const script = ['Dancing Script', 'Pacifico', 'Great Vibes'];
120
+ const other = ['Oswald', 'Raleway', 'Space Grotesk'];
121
+ const pack = (families, category) => families.map((family) => ({
122
+ family,
123
+ category,
124
+ isCustom: false,
125
+ variants: ['regular', '500', '600', '700'],
126
+ }));
127
+ return [
128
+ ...pack(sans, 'Sans Serif'),
129
+ ...pack(serif, 'Serif'),
130
+ ...pack(script, 'Script'),
131
+ ...pack(other, 'Other'),
132
+ ];
133
+ }
115
134
  hasGoogleFontsApiKey() {
116
135
  return Boolean(this.apiKey?.trim());
117
136
  }
@@ -621,7 +640,7 @@ class FontManager {
621
640
  // Add custom fonts from fonts.json
622
641
  const customFonts = await this.loadCustomFonts();
623
642
  const catalog = await this.fetchGoogleFontsCatalog();
624
- const googleFonts = catalog || [];
643
+ const googleFonts = (catalog && catalog.length > 0) ? catalog : FontManager.getFallbackCatalog();
625
644
  // Combine all fonts. A full Google catalog means the list is already
626
645
  // complete; otherwise keep the 'initial' stage so a later fetch can
627
646
  // still upgrade it.
@@ -638,7 +657,7 @@ class FontManager {
638
657
  }
639
658
  catch (error) {
640
659
  console.error('Error getting initial fonts:', error);
641
- return [];
660
+ return FontManager.getFallbackCatalog();
642
661
  }
643
662
  }
644
663
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@widgetic/editor",
3
- "version": "4.0.1",
3
+ "version": "4.0.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",