@widgetic/editor 4.0.9 → 4.0.11

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 (55) hide show
  1. package/README.md +118 -0
  2. package/dist/components/WidgetPreview.svelte +65 -27
  3. package/dist/components/WidgetPreview.svelte.d.ts +8 -0
  4. package/dist/components/editors/CompositionEditorApp.svelte +508 -0
  5. package/dist/components/editors/CompositionEditorApp.svelte.d.ts +75 -0
  6. package/dist/components/editors/PropsEditor.svelte +193 -792
  7. package/dist/components/editors/PropsEditor.svelte.d.ts +7 -0
  8. package/dist/components/ic/BrowserInputController.svelte +21 -2
  9. package/dist/components/ic/ColorPickerInputController.svelte +24 -6
  10. package/dist/components/ic/DateFieldInputController.svelte +7 -3
  11. package/dist/components/ic/DatePickerInputController.svelte +10 -4
  12. package/dist/components/ic/DropdownInputController.svelte +11 -4
  13. package/dist/components/ic/FontInputController.svelte +68 -44
  14. package/dist/components/ic/InputControllerHeader.svelte +11 -8
  15. package/dist/components/ic/NumberInputController.svelte +4 -2
  16. package/dist/components/ic/PositionInputController.svelte +14 -11
  17. package/dist/components/ic/RangeCalendarInputController.svelte +10 -4
  18. package/dist/components/ic/RoundedCornersInputController.svelte +17 -9
  19. package/dist/components/ic/TextAreaInputController.svelte +20 -7
  20. package/dist/components/ic/TextAreaPreviewInputController.svelte +19 -69
  21. package/dist/components/ic/TimeInputController.svelte +9 -3
  22. package/dist/components/ic/VectorInputController.svelte +80 -54
  23. package/dist/components/ic/shared/ItemGrid.svelte +5 -1
  24. package/dist/composition/compositionApi.d.ts +95 -0
  25. package/dist/composition/compositionApi.js +289 -0
  26. package/dist/composition/compositionEditorController.d.ts +254 -0
  27. package/dist/composition/compositionEditorController.js +734 -0
  28. package/dist/composition/compositionHelpers.d.ts +72 -0
  29. package/dist/composition/compositionHelpers.js +403 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.js +4 -0
  32. package/dist/mediaLibraryContext.d.ts +10 -0
  33. package/dist/mediaLibraryContext.js +30 -0
  34. package/dist/utils/FontManager.d.ts +21 -5
  35. package/dist/utils/FontManager.js +62 -67
  36. package/dist/utils/VectorManager.d.ts +0 -1
  37. package/dist/utils/VectorManager.js +10 -12
  38. package/dist/utils/editorStorage.d.ts +23 -0
  39. package/dist/utils/editorStorage.js +95 -0
  40. package/dist/utils/fontUtils.js +5 -3
  41. package/dist/utils/htmlSafety.d.ts +21 -0
  42. package/dist/utils/htmlSafety.js +38 -0
  43. package/dist/utils/itemReorder.d.ts +90 -0
  44. package/dist/utils/itemReorder.js +354 -0
  45. package/dist/utils/recentColors.js +4 -2
  46. package/dist/utils/richTextPreview.d.ts +38 -0
  47. package/dist/utils/richTextPreview.js +207 -0
  48. package/dist/utils/scrollActivity.d.ts +14 -0
  49. package/dist/utils/scrollActivity.js +63 -0
  50. package/dist/utils/shadowDom.d.ts +67 -0
  51. package/dist/utils/shadowDom.js +211 -0
  52. package/dist/utils/vectorUtils.js +5 -3
  53. package/dist/utils/visibilityCondition.d.ts +40 -0
  54. package/dist/utils/visibilityCondition.js +535 -0
  55. package/package.json +6 -5
@@ -79,6 +79,11 @@ class FontManager {
79
79
  static instance = null;
80
80
  // Track loaded and pending fonts to prevent duplicates
81
81
  loadedFonts = new Set();
82
+ // What this manager added to the page's <head>, to take away again when the editor leaves the page
83
+ addedHeadElements = new Set();
84
+ // True from the moment the editor leaves the page until a new one asks for the manager: a stylesheet that was still
85
+ // on its way is not added to a page the editor is no longer on
86
+ pageWasReleased = false;
82
87
  // Map of intersection observers keyed by container ID
83
88
  observers = new Map();
84
89
  // Debounce timer for scroll events
@@ -105,11 +110,9 @@ class FontManager {
105
110
  // Controls how long we keep the font list in the durable L1 store
106
111
  static CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
107
112
  static LOADED_FONTS_KEY = 'fonts.loaded';
108
- // Legacy localStorage keys read once for migration
109
- static LEGACY_CACHE_KEY = 'google_fonts_cache';
110
- static LEGACY_LOADED_FONTS_KEY = 'loaded_fonts_cache';
111
- // Controls how long browsers should cache the actual font files and stylesheets
112
- static CACHE_CONTROL = 'public, max-age=31536000'; // 1 year
113
+ // The old localStorage copies (`google_fonts_cache`, `loaded_fonts_cache`) are no longer read or removed: the editor
114
+ // runs in customers' pages, whose own localStorage keys it must not touch, and the durable store above has been the
115
+ // source for long enough that the worst case is one fresh download of the catalog.
113
116
  // Add new property to store visible fonts
114
117
  visibleFonts = new Set();
115
118
  static initialPreloadComplete = false;
@@ -257,6 +260,8 @@ class FontManager {
257
260
  FontManager.instance.apiKey = trimmedKey;
258
261
  FontManager.instance.catalogFetchPromise = null;
259
262
  }
263
+ // An editor that asks for the manager is on the page: its fonts may be added again
264
+ FontManager.instance.pageWasReleased = false;
260
265
  // Always await in-flight init — concurrent callers used to return mid-initialize.
261
266
  if (FontManager.initializationPromise) {
262
267
  await FontManager.initializationPromise;
@@ -370,24 +375,11 @@ class FontManager {
370
375
  return this.preInitializedObservers.get('default') || null;
371
376
  }
372
377
  /**
373
- * Loads previously cached fonts from the durable L1 store (legacy localStorage fallback)
378
+ * Loads previously cached fonts from the durable L1 store
374
379
  */
375
380
  async loadCachedFonts() {
376
381
  try {
377
- let cachedLoadedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
378
- if (!cachedLoadedFonts) {
379
- const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
380
- if (legacyRaw) {
381
- try {
382
- cachedLoadedFonts = JSON.parse(legacyRaw);
383
- await writeKvValue(FontManager.LOADED_FONTS_KEY, cachedLoadedFonts);
384
- }
385
- catch {
386
- /* ignore malformed legacy data */
387
- }
388
- localStorage.removeItem(FontManager.LEGACY_LOADED_FONTS_KEY);
389
- }
390
- }
382
+ const cachedLoadedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
391
383
  if (cachedLoadedFonts && Array.isArray(cachedLoadedFonts)) {
392
384
  // Only load the first fonts initially
393
385
  const initialFonts = cachedLoadedFonts.slice(0, FontManager.initialFontNumber);
@@ -420,18 +412,10 @@ class FontManager {
420
412
  try {
421
413
  const fontsToCache = Array.from(this.loadedFonts);
422
414
  void writeKvValue(FontManager.LOADED_FONTS_KEY, fontsToCache);
423
- // Add cache control headers for font files
424
- const styleSheets = document.querySelectorAll('link[href*="fonts.googleapis.com"]');
425
- styleSheets.forEach(sheet => {
426
- if (sheet instanceof HTMLLinkElement) {
427
- sheet.setAttribute('crossorigin', 'anonymous');
428
- // Add cache control header through meta tag since we can't directly set headers
429
- const meta = document.createElement('meta');
430
- meta.setAttribute('http-equiv', 'Cache-Control');
431
- meta.setAttribute('content', FontManager.CACHE_CONTROL);
432
- document.head.appendChild(meta);
433
- }
434
- });
415
+ // (This used to go through every Google Fonts stylesheet link of the page, the page's own included, to set
416
+ // `crossorigin` on it and to add a <meta http-equiv="Cache-Control"> to the page's head for each one. A stylesheet
417
+ // that has been fetched is not fetched again because of an attribute, no browser reads that meta, and both were
418
+ // changes to a page that is not the editor's, so they are gone.)
435
419
  }
436
420
  catch (error) {
437
421
  console.error('❌ [FontManager] Error saving loaded fonts:', error);
@@ -465,6 +449,42 @@ class FontManager {
465
449
  }
466
450
  return this.completeFontList.some(font => font.family.toLowerCase() === family.toLowerCase());
467
451
  }
452
+ /**
453
+ * Puts an element in the page's <head> and remembers it, so that it can be taken away again
454
+ * ({@link removeAddedHeadElements}): the editor can be on somebody else's page, which it should leave as it found it.
455
+ */
456
+ addToHead(element) {
457
+ // The editor has left the page (a font that was on its way arrives late): nothing is added to it any more
458
+ if (this.pageWasReleased)
459
+ return;
460
+ document.head.appendChild(element);
461
+ this.addedHeadElements.add(element);
462
+ }
463
+ /**
464
+ * Takes out of the page's <head> everything this manager put there (the stylesheets of the fonts, their preload
465
+ * hints and the connection hint), and forgets which fonts were loaded, because they are not any more. The next
466
+ * font to be asked for puts its stylesheet back. The page's own links are never touched: only what was added here
467
+ * is removed.
468
+ */
469
+ removeAddedHeadElements() {
470
+ try {
471
+ this.pageWasReleased = true;
472
+ for (const element of this.addedHeadElements)
473
+ element.remove();
474
+ this.addedHeadElements.clear();
475
+ this.loadedFonts.clear();
476
+ }
477
+ catch (error) {
478
+ console.error('❌ [FontManager] Error removing the font stylesheets from the page:', error);
479
+ }
480
+ }
481
+ /**
482
+ * Leaves the page as the editor found it as far as fonts go. Meant for when the editor is taken away; does nothing
483
+ * if no font was ever asked for.
484
+ */
485
+ static releasePageResources() {
486
+ FontManager.instance?.removeAddedHeadElements();
487
+ }
468
488
  /**
469
489
  * Inserts a font stylesheet into the document
470
490
  * @param family - Font family name
@@ -487,7 +507,7 @@ class FontManager {
487
507
  preconnect.rel = 'preconnect';
488
508
  preconnect.href = 'https://fonts.gstatic.com';
489
509
  preconnect.crossOrigin = 'anonymous';
490
- document.head.appendChild(preconnect);
510
+ this.addToHead(preconnect);
491
511
  }
492
512
  // Build URL based on whether we want subset or full font
493
513
  const baseUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400&display=swap`;
@@ -504,15 +524,10 @@ class FontManager {
504
524
  link.setAttribute('data-full-font', 'true');
505
525
  }
506
526
  // Add to document
507
- document.head.appendChild(link);
508
- // Add preload hint with correct attributes after stylesheet
509
- const preloadLink = document.createElement('link');
510
- preloadLink.rel = 'preload';
511
- preloadLink.as = 'style'; // Changed from 'font' to 'style'
512
- preloadLink.href = finalUrl;
513
- preloadLink.crossOrigin = 'anonymous';
514
- preloadLink.setAttribute('onload', "this.onload=null;this.rel='stylesheet'");
515
- document.head.appendChild(preloadLink);
527
+ this.addToHead(link);
528
+ // (A preload hint for the same address used to follow here, with an `onload` attribute that turned it into a
529
+ // second stylesheet. It added nothing to the stylesheet link above, and a page with a strict Content Security
530
+ // Policy blocks and reports that inline handler, so it is gone.)
516
531
  // Wait for stylesheet to load
517
532
  await new Promise((resolve, reject) => {
518
533
  link.onload = () => resolve();
@@ -662,23 +677,11 @@ class FontManager {
662
677
  }
663
678
  }
664
679
  /**
665
- * Gets font list from the durable L1 cache (legacy localStorage fallback)
680
+ * Gets font list from the durable L1 cache
666
681
  */
667
682
  async getFontListFromCache() {
668
683
  try {
669
- let cache = await readKvValue(FontManager.CACHE_KEY, FontManager.CACHE_DURATION);
670
- if (!cache) {
671
- const legacyRaw = localStorage.getItem(FontManager.LEGACY_CACHE_KEY);
672
- if (legacyRaw) {
673
- try {
674
- cache = JSON.parse(legacyRaw);
675
- }
676
- catch {
677
- /* ignore malformed legacy data */
678
- }
679
- localStorage.removeItem(FontManager.LEGACY_CACHE_KEY);
680
- }
681
- }
684
+ const cache = await readKvValue(FontManager.CACHE_KEY, FontManager.CACHE_DURATION);
682
685
  if (!cache)
683
686
  return null;
684
687
  // Check if cache is expired
@@ -1040,12 +1043,8 @@ class FontManager {
1040
1043
  fontFace: false
1041
1044
  };
1042
1045
  try {
1043
- // Check the durable loaded-fonts registry (legacy localStorage fallback)
1044
- let cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
1045
- if (!cachedFonts) {
1046
- const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
1047
- cachedFonts = legacyRaw ? JSON.parse(legacyRaw) : [];
1048
- }
1046
+ // Check the durable loaded-fonts registry
1047
+ const cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
1049
1048
  const loadedFonts = Array.isArray(cachedFonts) ? cachedFonts : [];
1050
1049
  const isCached = loadedFonts.includes(family);
1051
1050
  // Check if stylesheet exists in DOM
@@ -1072,12 +1071,8 @@ class FontManager {
1072
1071
  */
1073
1072
  async getBrowserCachedFonts() {
1074
1073
  try {
1075
- // Get fonts from the durable loaded-fonts registry (legacy fallback)
1074
+ // Get fonts from the durable loaded-fonts registry
1076
1075
  let cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
1077
- if (!cachedFonts) {
1078
- const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
1079
- cachedFonts = legacyRaw ? JSON.parse(legacyRaw) : [];
1080
- }
1081
1076
  cachedFonts = Array.isArray(cachedFonts) ? cachedFonts : [];
1082
1077
  // console.log('📦 [FontManager] Fonts in durable registry:', cachedFonts);
1083
1078
  // Verify each font has its stylesheet in the DOM
@@ -35,7 +35,6 @@ declare class VectorManager {
35
35
  private initialVectorsLoaded;
36
36
  private allVectorsLoaded;
37
37
  private cachedVectorList;
38
- private static readonly CACHE_KEY;
39
38
  private static readonly CACHE_DURATION;
40
39
  private static readonly LOADED_VECTORS_KEY;
41
40
  private static readonly CACHE_CONTROL;
@@ -1,4 +1,5 @@
1
1
  import { vectorUtils, vectorCategories } from "./vectorUtils";
2
+ import { readEditorStorageValue, writeEditorStorageValue } from "./editorStorage";
2
3
  class VectorManager {
3
4
  // Debug configuration
4
5
  static DEBUG_MODE = false;
@@ -17,9 +18,11 @@ class VectorManager {
17
18
  allVectorsLoaded = false;
18
19
  cachedVectorList = [];
19
20
  // Cache configuration
20
- static CACHE_KEY = 'svg_vectors_cache';
21
+ // (The vector list used to be kept in localStorage under `svg_vectors_cache`; that key is no longer read or removed,
22
+ // because the editor runs in customers' pages whose own keys it must not touch.)
21
23
  // Controls how long we keep the vector list in browser's localStorage
22
24
  static CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
25
+ // Stored in the editor's own corner of localStorage (see editorStorage.ts), not under a key the customer's page may use.
23
26
  static LOADED_VECTORS_KEY = 'loaded_vectors_cache';
24
27
  // Controls how long browsers should cache the actual vector files
25
28
  static CACHE_CONTROL = 'public, max-age=31536000'; // 1 year
@@ -218,7 +221,7 @@ class VectorManager {
218
221
  */
219
222
  async getBrowserCachedVectors() {
220
223
  try {
221
- const cachedVectorsList = localStorage.getItem(VectorManager.LOADED_VECTORS_KEY);
224
+ const cachedVectorsList = readEditorStorageValue(VectorManager.LOADED_VECTORS_KEY);
222
225
  const cachedVectors = cachedVectorsList ? JSON.parse(cachedVectorsList) : [];
223
226
  // console.log('📦 [VectorManager] Vectors in localStorage:', cachedVectors);
224
227
  // Verify each vector exists in the DOM/cache
@@ -247,9 +250,9 @@ class VectorManager {
247
250
  saveLoadedVectors() {
248
251
  try {
249
252
  const vectorsToCache = Array.from(this.loadedVectors);
250
- localStorage.setItem(VectorManager.LOADED_VECTORS_KEY, JSON.stringify(vectorsToCache));
253
+ writeEditorStorageValue(VectorManager.LOADED_VECTORS_KEY, JSON.stringify(vectorsToCache));
251
254
  // Verify the save operation
252
- const savedData = localStorage.getItem(VectorManager.LOADED_VECTORS_KEY);
255
+ const savedData = readEditorStorageValue(VectorManager.LOADED_VECTORS_KEY);
253
256
  // console.log('💾 [VectorManager] Vector cache status:', {
254
257
  // savedVectors: vectorsToCache,
255
258
  // verifiedCache: savedData ? JSON.parse(savedData) : null,
@@ -306,13 +309,8 @@ class VectorManager {
306
309
  * Saves vector list to cache (avoids writing 1.2MB JSON to localStorage)
307
310
  */
308
311
  saveVectorListToCache(vectors) {
309
- try {
310
- // Remove legacy large JSON from localStorage if present to free storage quota
311
- localStorage.removeItem(VectorManager.CACHE_KEY);
312
- }
313
- catch (error) {
314
- // Ignore storage errors
315
- }
312
+ // Nothing to save: the list is not written to localStorage. (This used to remove the large legacy JSON that an
313
+ // earlier version stored under `svg_vectors_cache`; the editor no longer touches keys it did not prefix itself.)
316
314
  }
317
315
  /**
318
316
  * Verifies if a vector is actually cached in the browser
@@ -326,7 +324,7 @@ class VectorManager {
326
324
  };
327
325
  try {
328
326
  // Check if vector is in localStorage
329
- const cachedVectors = localStorage.getItem(VectorManager.LOADED_VECTORS_KEY);
327
+ const cachedVectors = readEditorStorageValue(VectorManager.LOADED_VECTORS_KEY);
330
328
  const loadedVectors = cachedVectors ? JSON.parse(cachedVectors) : [];
331
329
  const isCached = loadedVectors.includes(name);
332
330
  // Find vector URL
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The editor's own corner of the browser's localStorage.
3
+ *
4
+ * The editor can run inside a customer's page, and a page shares its localStorage with everything on it. A key such as
5
+ * `recentColors` or `recent_fonts` is as likely to be the customer's own as the editor's: reading it would take the
6
+ * customer's data for the editor's, writing it would overwrite theirs. So every key the editor uses starts with
7
+ * `widgetic:editor:`, and the editor only reads, writes and removes keys that do.
8
+ *
9
+ * Storage can also be unavailable (a browser setting, a private window, a frame without storage access) or full. None
10
+ * of that may stop the editor, so a failure is reported once and the call answers as if nothing were stored.
11
+ */
12
+ export declare const EDITOR_STORAGE_PREFIX = "widgetic:editor:";
13
+ /** What was stored under `key`, or null when nothing was (or storage is not usable). */
14
+ export declare function readEditorStorageValue(key: string): string | null;
15
+ /** Stores `value` under `key`. Answers whether it was stored. */
16
+ export declare function writeEditorStorageValue(key: string, value: string): boolean;
17
+ /** Forgets what was stored under `key`. */
18
+ export declare function removeEditorStorageValue(key: string): void;
19
+ /**
20
+ * Forgets everything the editor stored under a key that begins with `keyStart`, and nothing else: the customer's keys
21
+ * stay, even those with the same text in them. Answers how many were forgotten.
22
+ */
23
+ export declare function removeEditorStorageValuesStartingWith(keyStart: string): number;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The editor's own corner of the browser's localStorage.
3
+ *
4
+ * The editor can run inside a customer's page, and a page shares its localStorage with everything on it. A key such as
5
+ * `recentColors` or `recent_fonts` is as likely to be the customer's own as the editor's: reading it would take the
6
+ * customer's data for the editor's, writing it would overwrite theirs. So every key the editor uses starts with
7
+ * `widgetic:editor:`, and the editor only reads, writes and removes keys that do.
8
+ *
9
+ * Storage can also be unavailable (a browser setting, a private window, a frame without storage access) or full. None
10
+ * of that may stop the editor, so a failure is reported once and the call answers as if nothing were stored.
11
+ */
12
+ export const EDITOR_STORAGE_PREFIX = 'widgetic:editor:';
13
+ /** The kinds of failure already reported, so that each is reported once however often it happens. */
14
+ const reportedFailures = new Set();
15
+ function reportFailureOnce(kind, error) {
16
+ if (reportedFailures.has(kind))
17
+ return;
18
+ reportedFailures.add(kind);
19
+ console.warn(`[editorStorage] The browser's storage is not usable (${kind}); the editor goes on without it:`, error);
20
+ }
21
+ /** The browser's localStorage, or nothing where there is none (a server) or where the browser refuses access to it. */
22
+ function getBrowserStorage() {
23
+ try {
24
+ return typeof localStorage === 'undefined' ? null : localStorage;
25
+ }
26
+ catch (error) {
27
+ // Even looking at `localStorage` throws when the browser blocks storage for the page.
28
+ reportFailureOnce('blocked', error);
29
+ return null;
30
+ }
31
+ }
32
+ /** What was stored under `key`, or null when nothing was (or storage is not usable). */
33
+ export function readEditorStorageValue(key) {
34
+ const storage = getBrowserStorage();
35
+ if (!storage)
36
+ return null;
37
+ try {
38
+ return storage.getItem(EDITOR_STORAGE_PREFIX + key);
39
+ }
40
+ catch (error) {
41
+ reportFailureOnce('read', error);
42
+ return null;
43
+ }
44
+ }
45
+ /** Stores `value` under `key`. Answers whether it was stored. */
46
+ export function writeEditorStorageValue(key, value) {
47
+ const storage = getBrowserStorage();
48
+ if (!storage)
49
+ return false;
50
+ try {
51
+ storage.setItem(EDITOR_STORAGE_PREFIX + key, value);
52
+ return true;
53
+ }
54
+ catch (error) {
55
+ reportFailureOnce('write', error);
56
+ return false;
57
+ }
58
+ }
59
+ /** Forgets what was stored under `key`. */
60
+ export function removeEditorStorageValue(key) {
61
+ const storage = getBrowserStorage();
62
+ if (!storage)
63
+ return;
64
+ try {
65
+ storage.removeItem(EDITOR_STORAGE_PREFIX + key);
66
+ }
67
+ catch (error) {
68
+ reportFailureOnce('remove', error);
69
+ }
70
+ }
71
+ /**
72
+ * Forgets everything the editor stored under a key that begins with `keyStart`, and nothing else: the customer's keys
73
+ * stay, even those with the same text in them. Answers how many were forgotten.
74
+ */
75
+ export function removeEditorStorageValuesStartingWith(keyStart) {
76
+ const storage = getBrowserStorage();
77
+ if (!storage)
78
+ return 0;
79
+ try {
80
+ const fullKeyStart = EDITOR_STORAGE_PREFIX + keyStart;
81
+ // Collected first: removing a key while walking the keys would skip the one that moves into its place.
82
+ const keysToRemove = [];
83
+ for (let index = 0; index < storage.length; index += 1) {
84
+ const key = storage.key(index);
85
+ if (key !== null && key.startsWith(fullKeyStart))
86
+ keysToRemove.push(key);
87
+ }
88
+ keysToRemove.forEach((key) => storage.removeItem(key));
89
+ return keysToRemove.length;
90
+ }
91
+ catch (error) {
92
+ reportFailureOnce('remove', error);
93
+ return 0;
94
+ }
95
+ }
@@ -1,4 +1,5 @@
1
1
  import { writable } from 'svelte/store';
2
+ import { readEditorStorageValue, writeEditorStorageValue } from './editorStorage';
2
3
  // Define our category types with proper typing
3
4
  export const fontCategories = {
4
5
  // Google Fonts categories to our categories mapping
@@ -69,6 +70,7 @@ export const fontCategories = {
69
70
  return inferredCategory;
70
71
  }
71
72
  };
73
+ // Stored in the editor's own corner of localStorage (see editorStorage.ts), not under a key the customer's page may use.
72
74
  const RECENT_FONTS_KEY = 'recent_fonts';
73
75
  const MAX_RECENT_FONTS = 3;
74
76
  // Add store for recent fonts
@@ -76,7 +78,7 @@ export const recentFontsStore = writable([]);
76
78
  export const fontUtils = {
77
79
  getRecentFonts() {
78
80
  try {
79
- const stored = localStorage.getItem(RECENT_FONTS_KEY);
81
+ const stored = readEditorStorageValue(RECENT_FONTS_KEY);
80
82
  return stored ? JSON.parse(stored) : [];
81
83
  }
82
84
  catch (error) {
@@ -95,7 +97,7 @@ export const fontUtils = {
95
97
  },
96
98
  ...filteredFonts
97
99
  ].slice(0, MAX_RECENT_FONTS);
98
- localStorage.setItem(RECENT_FONTS_KEY, JSON.stringify(newRecentFonts));
100
+ writeEditorStorageValue(RECENT_FONTS_KEY, JSON.stringify(newRecentFonts));
99
101
  recentFontsStore.set(newRecentFonts);
100
102
  return newRecentFonts;
101
103
  }
@@ -108,7 +110,7 @@ export const fontUtils = {
108
110
  try {
109
111
  const recentFonts = this.getRecentFonts();
110
112
  const filteredFonts = recentFonts.filter(f => f.family !== family);
111
- localStorage.setItem(RECENT_FONTS_KEY, JSON.stringify(filteredFonts));
113
+ writeEditorStorageValue(RECENT_FONTS_KEY, JSON.stringify(filteredFonts));
112
114
  return filteredFonts;
113
115
  }
114
116
  catch (error) {
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Text from outside the editor (a file's name, a font's name, a label in a widget's schema, what a person typed) must
3
+ * never reach the page as markup or as style rules. The editor can run inside a customer's own page, where a name like
4
+ * `x" onerror="steal()` that breaks out of an attribute runs with the customer page's rights.
5
+ *
6
+ * Svelte's `{text}` and `attribute={value}` already escape for HTML. These helpers are for the places that build HTML
7
+ * as a string for `{@html ...}`, where nothing is escaped unless it is done here, on every piece, as it goes in, and
8
+ * for the places that put a name into a CSS rule, where HTML escaping does not help at all.
9
+ */
10
+ /** `text` as it must be written between tags or inside a quoted attribute value. Anything but text becomes its string. */
11
+ export declare function escapeHtml(text: unknown): string;
12
+ /**
13
+ * `text` as a quoted CSS string (`'Open Sans'`), whatever it contains. A font family name goes into a rule like
14
+ * `font-family: 'Open Sans', sans-serif`; a name such as `x'; background: url(//evil/a)` would otherwise end the string
15
+ * and add rules of its own, and escaping it for HTML does not stop that (the HTML parser turns `&#39;` back into the
16
+ * quote). Backslashes and single quotes are escaped; line breaks and other control characters, which end a CSS string,
17
+ * become spaces. The name still reaches the browser as the same name, so it matches the font that was loaded under it.
18
+ *
19
+ * When the result goes into an HTML attribute, escape it for HTML as well (`escapeHtml(cssQuotedString(name))`).
20
+ */
21
+ export declare function cssQuotedString(text: unknown): string;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Text from outside the editor (a file's name, a font's name, a label in a widget's schema, what a person typed) must
3
+ * never reach the page as markup or as style rules. The editor can run inside a customer's own page, where a name like
4
+ * `x" onerror="steal()` that breaks out of an attribute runs with the customer page's rights.
5
+ *
6
+ * Svelte's `{text}` and `attribute={value}` already escape for HTML. These helpers are for the places that build HTML
7
+ * as a string for `{@html ...}`, where nothing is escaped unless it is done here, on every piece, as it goes in, and
8
+ * for the places that put a name into a CSS rule, where HTML escaping does not help at all.
9
+ */
10
+ const HTML_ESCAPES = {
11
+ '&': '&amp;',
12
+ '<': '&lt;',
13
+ '>': '&gt;',
14
+ '"': '&quot;',
15
+ "'": '&#39;'
16
+ };
17
+ /** `text` as it must be written between tags or inside a quoted attribute value. Anything but text becomes its string. */
18
+ export function escapeHtml(text) {
19
+ if (text === null || text === undefined)
20
+ return '';
21
+ return String(text).replace(/[&<>"']/g, (character) => HTML_ESCAPES[character]);
22
+ }
23
+ /**
24
+ * `text` as a quoted CSS string (`'Open Sans'`), whatever it contains. A font family name goes into a rule like
25
+ * `font-family: 'Open Sans', sans-serif`; a name such as `x'; background: url(//evil/a)` would otherwise end the string
26
+ * and add rules of its own, and escaping it for HTML does not stop that (the HTML parser turns `&#39;` back into the
27
+ * quote). Backslashes and single quotes are escaped; line breaks and other control characters, which end a CSS string,
28
+ * become spaces. The name still reaches the browser as the same name, so it matches the font that was loaded under it.
29
+ *
30
+ * When the result goes into an HTML attribute, escape it for HTML as well (`escapeHtml(cssQuotedString(name))`).
31
+ */
32
+ export function cssQuotedString(text) {
33
+ const cleaned = String(text ?? '')
34
+ // eslint-disable-next-line no-control-regex
35
+ .replace(/[\u0000-\u001f\u007f\u2028\u2029]/g, ' ')
36
+ .replace(/[\\']/g, '\\$&');
37
+ return `'${cleaned}'`;
38
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Reordering a list of items by dragging a handle.
3
+ *
4
+ * This replaces @shopify/draggable, which cannot work inside a shadow root (the editor runs in one when it is a
5
+ * module on a customer's page):
6
+ * - it listens on the `document`, where every event that comes from inside a shadow root has the shadow host as its
7
+ * target, so it never finds the list it was given and no drag ever starts;
8
+ * - the events it makes itself do not leave the shadow root, so its own listeners on the `document` never hear them;
9
+ * - it adds classes to the page's `<body>`, switches text selection off on it, appends its floating copy of the item
10
+ * to it, and leaves a screen reader region in it that stays after the editor is gone.
11
+ *
12
+ * This one listens on the list itself, keeps hold of the pointer with pointer capture (so the drag carries on over the
13
+ * preview frame and outside the editor), works out where an item would be dropped from where the items are on the
14
+ * screen, and leaves the page it runs in alone. It never moves the items itself: the list changes only when the
15
+ * caller changes the data the list is drawn from, in `onReorder`.
16
+ */
17
+ /** How far (px) the pointer must move from where it was pressed before the press counts as a drag and not a click. */
18
+ export declare const DRAG_START_DISTANCE_PX = 4;
19
+ /** The height (px) of the copy of the item that follows the pointer. */
20
+ export declare const MIRROR_HEIGHT_PX = 56;
21
+ /** How close (px) to the top or bottom edge of the scrolling area the pointer must be for the area to start scrolling. */
22
+ export declare const EDGE_SCROLL_ZONE_PX = 56;
23
+ /** The most (px) the scrolling area moves in one frame, reached when the pointer is at or beyond the edge. */
24
+ export declare const EDGE_SCROLL_MAX_STEP_PX = 18;
25
+ /** The classes put on the list and its items while one is dragged; the editor's stylesheet draws them. */
26
+ export declare const REORDER_CLASSES: {
27
+ /** On the list for as long as an item is being dragged. */
28
+ readonly list: "is-reordering";
29
+ /** On the item being dragged. */
30
+ readonly source: "is-reorder-source";
31
+ /** On the item the dragged one would be dropped in front of. */
32
+ readonly dropBefore: "is-reorder-drop-before";
33
+ /** On the last item, when the dragged one would be dropped behind it. */
34
+ readonly dropAfter: "is-reorder-drop-after";
35
+ /** On the floating copy of the dragged item. */
36
+ readonly mirror: "reorder-mirror";
37
+ };
38
+ /** The top and height of an item on the screen. */
39
+ export interface VerticalBox {
40
+ top: number;
41
+ height: number;
42
+ }
43
+ /**
44
+ * Where a dragged item ends up when it is dropped with the pointer at `pointerY`: its index in the list once it has
45
+ * been taken out and put back. `otherItems` are the boxes of the items on the screen without the dragged one. The
46
+ * dragged item goes after every item whose middle is above the pointer.
47
+ */
48
+ export declare function dropIndexForPointer(otherItems: readonly VerticalBox[], pointerY: number): number;
49
+ /**
50
+ * How far (px) to scroll the area that holds the list in this frame: upwards (negative) when the pointer is in the
51
+ * top `zone` of the area or above it, downwards (positive) in the bottom `zone` or below it, faster the nearer the
52
+ * edge, and 0 anywhere else.
53
+ */
54
+ export declare function edgeScrollStep(areaTop: number, areaBottom: number, pointerY: number, zone?: number, maxStep?: number): number;
55
+ /**
56
+ * A copy of `items` with the item at `oldIndex` moved to `newIndex` (its index in the copy). When either index is not
57
+ * a place in the list, the copy keeps the original order.
58
+ */
59
+ export declare function moveListItem<ItemType>(items: readonly ItemType[], oldIndex: number, newIndex: number): ItemType[];
60
+ /** What `reorderableList` needs to know about the list. */
61
+ export interface ItemReorderOptions {
62
+ /** CSS selector of one item. Items are direct children of the list. */
63
+ itemSelector: string;
64
+ /** CSS selector of the part of an item that starts a drag when it is pressed. */
65
+ handleSelector: string;
66
+ /** The drag began (the pointer moved far enough from where it was pressed). The list is not changed yet. */
67
+ onDragStart?: (drag: {
68
+ item: HTMLElement;
69
+ index: number;
70
+ }) => void;
71
+ /** The item was dropped at another place in the list. Change the data the list is drawn from here. */
72
+ onReorder: (move: {
73
+ item: HTMLElement;
74
+ oldIndex: number;
75
+ newIndex: number;
76
+ }) => void;
77
+ /** The drag is over: dropped (`reordered` tells if the order changed), cancelled with Escape, or the list went away. */
78
+ onDragEnd?: (outcome: {
79
+ reordered: boolean;
80
+ }) => void;
81
+ }
82
+ /**
83
+ * Svelte action that makes the direct children of a list reorderable by dragging their handle:
84
+ * `<div use:reorderableList={{ itemSelector, handleSelector, onReorder }}>`.
85
+ */
86
+ export declare function reorderableList(list: HTMLElement, initialOptions: ItemReorderOptions): {
87
+ /** Svelte calls this when the options change; the callbacks of the new options are the ones used from then on. */
88
+ update(nextOptions: ItemReorderOptions): void;
89
+ destroy(): void;
90
+ };