@widgetic/editor 3.11.4 → 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.
Files changed (497) hide show
  1. package/README.md +245 -0
  2. package/dist/assets/fonts.json +331 -0
  3. package/dist/assets/icons/ic-browser/audio.svg +7 -0
  4. package/dist/assets/icons/ic-browser/document.svg +11 -0
  5. package/dist/assets/icons/ic-browser/error.svg +5 -0
  6. package/dist/assets/icons/ic-browser/folder.svg +28 -0
  7. package/dist/assets/icons/ic-browser/player-pause.svg +1 -0
  8. package/dist/assets/icons/ic-browser/player-play.svg +1 -0
  9. package/dist/assets/icons/ic-browser/trash.svg +1 -0
  10. package/dist/assets/icons/ic-fixed-position/background-deselected.svg +3 -0
  11. package/dist/assets/icons/ic-fixed-position/background-selected.svg +3 -0
  12. package/dist/assets/icons/ic-fixed-position/bottom-left-background-deselected.svg +3 -0
  13. package/dist/assets/icons/ic-fixed-position/bottom-left-background-selected.svg +3 -0
  14. package/dist/assets/icons/ic-fixed-position/bottom-left-icon.svg +4 -0
  15. package/dist/assets/icons/ic-fixed-position/bottom-right-background-deselected.svg +3 -0
  16. package/dist/assets/icons/ic-fixed-position/bottom-right-background-selected.svg +3 -0
  17. package/dist/assets/icons/ic-fixed-position/bottom-right-icon.svg +4 -0
  18. package/dist/assets/icons/ic-fixed-position/center-bottom-icon.svg +5 -0
  19. package/dist/assets/icons/ic-fixed-position/center-center-icon.svg +3 -0
  20. package/dist/assets/icons/ic-fixed-position/center-left-icon.svg +5 -0
  21. package/dist/assets/icons/ic-fixed-position/center-right-icon.svg +5 -0
  22. package/dist/assets/icons/ic-fixed-position/center-top-icon.svg +5 -0
  23. package/dist/assets/icons/ic-fixed-position/top-left-background-deselected.svg +3 -0
  24. package/dist/assets/icons/ic-fixed-position/top-left-background-selected.svg +3 -0
  25. package/dist/assets/icons/ic-fixed-position/top-left-icon.svg +4 -0
  26. package/dist/assets/icons/ic-fixed-position/top-right-background-deselected.svg +3 -0
  27. package/dist/assets/icons/ic-fixed-position/top-right-background-selected.svg +3 -0
  28. package/dist/assets/icons/ic-fixed-position/top-right-icon.svg +4 -0
  29. package/dist/assets/icons/trash.svg +1 -0
  30. package/dist/components/MockWidget.svelte +108 -0
  31. package/dist/components/MockWidget.svelte.d.ts +22 -0
  32. package/dist/components/WidgetPreview.svelte +852 -0
  33. package/dist/components/WidgetPreview.svelte.d.ts +100 -0
  34. package/dist/components/color-select/ColorSelect.svelte +258 -0
  35. package/dist/components/color-select/ColorSelect.svelte.d.ts +42 -0
  36. package/dist/components/color-select/color.d.ts +25 -0
  37. package/dist/components/color-select/color.js +21 -0
  38. package/dist/components/color-select/constants.d.ts +5 -0
  39. package/dist/components/color-select/constants.js +5 -0
  40. package/dist/components/color-select/render.d.ts +5 -0
  41. package/dist/components/color-select/render.js +77 -0
  42. package/dist/components/color-select/shaders.d.ts +3 -0
  43. package/dist/components/color-select/shaders.js +8 -0
  44. package/dist/components/editors/EmbedEditor.svelte +700 -0
  45. package/dist/components/editors/EmbedEditor.svelte.d.ts +50 -0
  46. package/dist/components/editors/PropsEditor.svelte +4009 -0
  47. package/dist/components/editors/PropsEditor.svelte.d.ts +62 -0
  48. package/dist/components/editors/props-editor.css +73 -0
  49. package/dist/components/ic/AngleInputController.svelte +276 -0
  50. package/dist/components/ic/AngleInputController.svelte.d.ts +26 -0
  51. package/dist/components/ic/BrowserInputController.svelte +731 -0
  52. package/dist/components/ic/BrowserInputController.svelte.d.ts +39 -0
  53. package/dist/components/ic/CheckboxInputController.svelte +203 -0
  54. package/dist/components/ic/CheckboxInputController.svelte.d.ts +41 -0
  55. package/dist/components/ic/ColorPickerInputController.svelte +1584 -0
  56. package/dist/components/ic/ColorPickerInputController.svelte.d.ts +50 -0
  57. package/dist/components/ic/DateFieldInputController.svelte +453 -0
  58. package/dist/components/ic/DateFieldInputController.svelte.d.ts +35 -0
  59. package/dist/components/ic/DatePickerInputController.svelte +364 -0
  60. package/dist/components/ic/DatePickerInputController.svelte.d.ts +28 -0
  61. package/dist/components/ic/DropdownInputController.svelte +226 -0
  62. package/dist/components/ic/DropdownInputController.svelte.d.ts +43 -0
  63. package/dist/components/ic/FixedPositionInputController.svelte +353 -0
  64. package/dist/components/ic/FixedPositionInputController.svelte.d.ts +23 -0
  65. package/dist/components/ic/FontInputController.svelte +2098 -0
  66. package/dist/components/ic/FontInputController.svelte.d.ts +47 -0
  67. package/dist/components/ic/InputControllerHeader.svelte +147 -0
  68. package/dist/components/ic/InputControllerHeader.svelte.d.ts +23 -0
  69. package/dist/components/ic/NumberControls.svelte +299 -0
  70. package/dist/components/ic/NumberControls.svelte.d.ts +30 -0
  71. package/dist/components/ic/NumberInputController.svelte +397 -0
  72. package/dist/components/ic/NumberInputController.svelte.d.ts +30 -0
  73. package/dist/components/ic/PositionInputController.svelte +252 -0
  74. package/dist/components/ic/PositionInputController.svelte.d.ts +37 -0
  75. package/dist/components/ic/RadioGroupInputController.svelte +162 -0
  76. package/dist/components/ic/RadioGroupInputController.svelte.d.ts +38 -0
  77. package/dist/components/ic/RangeCalendarInputController.svelte +262 -0
  78. package/dist/components/ic/RangeCalendarInputController.svelte.d.ts +32 -0
  79. package/dist/components/ic/RoundedCornersInputController.svelte +469 -0
  80. package/dist/components/ic/RoundedCornersInputController.svelte.d.ts +37 -0
  81. package/dist/components/ic/SliderInputController.svelte +411 -0
  82. package/dist/components/ic/SliderInputController.svelte.d.ts +46 -0
  83. package/dist/components/ic/SliderRangeInputController.svelte +155 -0
  84. package/dist/components/ic/SliderRangeInputController.svelte.d.ts +33 -0
  85. package/dist/components/ic/SwitchInputController.svelte +107 -0
  86. package/dist/components/ic/SwitchInputController.svelte.d.ts +24 -0
  87. package/dist/components/ic/TagsInputController.svelte +125 -0
  88. package/dist/components/ic/TagsInputController.svelte.d.ts +27 -0
  89. package/dist/components/ic/TextAreaInputController.svelte +1932 -0
  90. package/dist/components/ic/TextAreaInputController.svelte.d.ts +14 -0
  91. package/dist/components/ic/TextAreaPreviewInputController.svelte +478 -0
  92. package/dist/components/ic/TextAreaPreviewInputController.svelte.d.ts +28 -0
  93. package/dist/components/ic/TextInputController.svelte +70 -0
  94. package/dist/components/ic/TextInputController.svelte.d.ts +30 -0
  95. package/dist/components/ic/TimeInputController.svelte +434 -0
  96. package/dist/components/ic/TimeInputController.svelte.d.ts +25 -0
  97. package/dist/components/ic/VectorInputController.svelte +1826 -0
  98. package/dist/components/ic/VectorInputController.svelte.d.ts +47 -0
  99. package/dist/components/ic/index.d.ts +39 -0
  100. package/dist/components/ic/index.js +24 -0
  101. package/dist/components/ic/shared/CategoryTabs.svelte +40 -0
  102. package/dist/components/ic/shared/CategoryTabs.svelte.d.ts +25 -0
  103. package/dist/components/ic/shared/ItemGrid.svelte +181 -0
  104. package/dist/components/ic/shared/ItemGrid.svelte.d.ts +35 -0
  105. package/dist/components/ic/shared/RecentItemsSection.svelte +260 -0
  106. package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +35 -0
  107. package/dist/components/ic/shared/index.d.ts +8 -0
  108. package/dist/components/ic/shared/index.js +9 -0
  109. package/dist/components/icons/addnew.svelte +21 -0
  110. package/dist/components/icons/addnew.svelte.d.ts +20 -0
  111. package/dist/components/icons/drag.svelte +29 -0
  112. package/dist/components/icons/drag.svelte.d.ts +20 -0
  113. package/dist/components/icons/drag.svg +1 -0
  114. package/dist/components/icons/locked.svelte +4 -0
  115. package/dist/components/icons/locked.svelte.d.ts +26 -0
  116. package/dist/components/icons/locked.svg +4 -0
  117. package/dist/components/icons/ok.svelte +20 -0
  118. package/dist/components/icons/ok.svelte.d.ts +20 -0
  119. package/dist/components/icons/ok.svg +11 -0
  120. package/dist/components/icons/unlocked.svelte +4 -0
  121. package/dist/components/icons/unlocked.svelte.d.ts +26 -0
  122. package/dist/components/icons/unlocked.svg +4 -0
  123. package/dist/components/icons/x.svelte +14 -0
  124. package/dist/components/icons/x.svelte.d.ts +20 -0
  125. package/dist/components/icons/x.svg +5 -0
  126. package/dist/components/ui/arrows/OpacitySliderArrows.svelte +43 -0
  127. package/dist/components/ui/arrows/OpacitySliderArrows.svelte.d.ts +23 -0
  128. package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte +15 -0
  129. package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte.d.ts +20 -0
  130. package/dist/config.d.ts +11 -0
  131. package/dist/config.js +31 -0
  132. package/dist/index.d.ts +5 -0
  133. package/dist/index.js +9 -0
  134. package/dist/stores/userSession.d.ts +7 -0
  135. package/dist/stores/userSession.js +28 -0
  136. package/dist/utils/FontManager.d.ts +278 -0
  137. package/dist/utils/FontManager.js +1302 -0
  138. package/dist/utils/MediaManager.d.ts +35 -0
  139. package/dist/utils/MediaManager.js +59 -0
  140. package/dist/utils/VectorManager.d.ts +190 -0
  141. package/dist/utils/VectorManager.js +754 -0
  142. package/dist/utils/colorAutocomplete.d.ts +10 -0
  143. package/dist/utils/colorAutocomplete.js +28 -0
  144. package/dist/utils/colorNames.d.ts +3 -0
  145. package/dist/utils/colorNames.js +152 -0
  146. package/dist/utils/colorTransparency.d.ts +52 -0
  147. package/dist/utils/colorTransparency.js +114 -0
  148. package/dist/utils/fontUtils.d.ts +34 -0
  149. package/dist/utils/fontUtils.js +132 -0
  150. package/dist/utils/passiveEvents.js +20 -0
  151. package/dist/utils/recentColors.d.ts +10 -0
  152. package/dist/utils/recentColors.js +63 -0
  153. package/dist/utils/skipFonts.d.ts +3 -0
  154. package/dist/utils/skipFonts.js +49 -0
  155. package/dist/utils/vectorUtils.d.ts +34 -0
  156. package/dist/utils/vectorUtils.js +71 -0
  157. package/dist/utils.d.ts +11 -0
  158. package/dist/utils.js +38 -0
  159. package/package.json +134 -74
  160. package/.babelrc +0 -10
  161. package/.bowerrc +0 -4
  162. package/.nvmrc +0 -1
  163. package/Gruntfile.coffee +0 -69
  164. package/bower.json +0 -28
  165. package/css/browser/entries/activateable.styl +0 -29
  166. package/css/browser/entries/audio.styl +0 -39
  167. package/css/browser/entries/folder.styl +0 -79
  168. package/css/browser/entries/image.styl +0 -26
  169. package/css/browser/entries/index.styl +0 -66
  170. package/css/browser/entries/rss.styl +0 -11
  171. package/css/browser/entries/video.styl +0 -11
  172. package/css/browser/index.styl +0 -86
  173. package/css/browser/intent/index.styl +0 -61
  174. package/css/browser/nav/index.styl +0 -73
  175. package/css/browser/social-browser.styl +0 -23
  176. package/css/controls/clock.styl +0 -110
  177. package/css/controls/config.styl +0 -6
  178. package/css/controls/control-audio-details.styl +0 -63
  179. package/css/controls/control-audio.styl +0 -28
  180. package/css/controls/control-browser.styl +0 -48
  181. package/css/controls/control-color.styl +0 -423
  182. package/css/controls/control-date_time.styl +0 -191
  183. package/css/controls/control-dropdown.styl +0 -79
  184. package/css/controls/control-font.styl +0 -147
  185. package/css/controls/control-image-details.styl +0 -35
  186. package/css/controls/control-image.styl +0 -43
  187. package/css/controls/control-order-picker.styl +0 -29
  188. package/css/controls/control-position.styl +0 -390
  189. package/css/controls/control-range.styl +0 -68
  190. package/css/controls/control-scale.styl +0 -30
  191. package/css/controls/control-slider.styl +0 -38
  192. package/css/controls/control-spinner.styl +0 -39
  193. package/css/controls/control-textarea.styl +0 -52
  194. package/css/controls/control-toggle.styl +0 -34
  195. package/css/controls/control-url.styl +0 -32
  196. package/css/controls/control-video-details.styl +0 -85
  197. package/css/controls/control-video.styl +0 -43
  198. package/css/controls/general.styl +0 -98
  199. package/css/controls/index.styl +0 -25
  200. package/css/editor/colors.styl +0 -7
  201. package/css/editor/content/directinput.styl +0 -47
  202. package/css/editor/content/index.styl +0 -111
  203. package/css/editor/content/message.styl +0 -53
  204. package/css/editor/content/tabs.styl +0 -75
  205. package/css/editor/copied-from-old-style.styl +0 -197
  206. package/css/editor/details/bulk-editor.styl +0 -104
  207. package/css/editor/details/index.styl +0 -21
  208. package/css/editor/details/item.styl +0 -153
  209. package/css/editor/embed.styl +0 -106
  210. package/css/editor/footer.styl +0 -180
  211. package/css/editor/full-editor.styl +0 -16
  212. package/css/editor/index.styl +0 -124
  213. package/css/editor/login-popup.styl +0 -13
  214. package/css/editor/preview.styl +0 -257
  215. package/css/editor/skin/editor/index.styl +0 -54
  216. package/css/editor/skin/editor/tabs.styl +0 -111
  217. package/css/editor/skin/index.styl +0 -138
  218. package/css/editor/skin/popup.styl +0 -152
  219. package/css/editor/skin/presets/dropdown.styl +0 -61
  220. package/css/editor/skin/presets/index.styl +0 -86
  221. package/css/editor/skin/presets/list.styl +0 -244
  222. package/css/editor/tooltips.styl +0 -85
  223. package/css/fonts.styl +0 -47
  224. package/css/icons/BlogvioEditor.dev.svg +0 -112
  225. package/css/icons.styl +0 -16
  226. package/css/img/arrow-right-rounded-v-small.png +0 -0
  227. package/css/img/browser-folder-separator.png +0 -0
  228. package/css/img/calendar-input-icons.png +0 -0
  229. package/css/img/check.png +0 -0
  230. package/css/img/close.png +0 -0
  231. package/css/img/composition/edit-icon.png +0 -0
  232. package/css/img/content/audio-icon.png +0 -0
  233. package/css/img/content/folder-icon.png +0 -0
  234. package/css/img/content/messages/error-background.png +0 -0
  235. package/css/img/content/messages/error-icon.png +0 -0
  236. package/css/img/content/messages/notification-background.png +0 -0
  237. package/css/img/content/messages/notification-icon.png +0 -0
  238. package/css/img/content/no-files-audio.png +0 -0
  239. package/css/img/content/no-files-image.png +0 -0
  240. package/css/img/content/no-files-video.png +0 -0
  241. package/css/img/content/no-files.png +0 -0
  242. package/css/img/content/rss-icon.png +0 -0
  243. package/css/img/content/selected.png +0 -0
  244. package/css/img/content/video-icon.png +0 -0
  245. package/css/img/content/with-control-text.png +0 -0
  246. package/css/img/control-dropdown-arrow.png +0 -0
  247. package/css/img/control-image-valid.png +0 -0
  248. package/css/img/control-position-icon.png +0 -0
  249. package/css/img/control-scale-lock-icon.png +0 -0
  250. package/css/img/control-slider-arrows-hover.png +0 -0
  251. package/css/img/control-slider-arrows.png +0 -0
  252. package/css/img/control-spinner-buttons-hover.png +0 -0
  253. package/css/img/control-spinner-buttons.png +0 -0
  254. package/css/img/control-textarea-bold-active.png +0 -0
  255. package/css/img/control-textarea-bold.png +0 -0
  256. package/css/img/control-textarea-italic-active.png +0 -0
  257. package/css/img/control-textarea-italic.png +0 -0
  258. package/css/img/control-textarea-underline-active.png +0 -0
  259. package/css/img/control-textarea-underline.png +0 -0
  260. package/css/img/custom-checkbox-sprite.png +0 -0
  261. package/css/img/details/item-collapse-icon.png +0 -0
  262. package/css/img/details/item-expand-icon.png +0 -0
  263. package/css/img/details/item-sort-icon.png +0 -0
  264. package/css/img/done-arrow.png +0 -0
  265. package/css/img/fb-logo-icon-w.png +0 -0
  266. package/css/img/grid-icons.png +0 -0
  267. package/css/img/icon-reference.png +0 -0
  268. package/css/img/indicator.png +0 -0
  269. package/css/img/loader.gif +0 -0
  270. package/css/img/loaders/audio-white.png +0 -0
  271. package/css/img/loaders/audio.png +0 -0
  272. package/css/img/loaders/photo-white.png +0 -0
  273. package/css/img/loaders/photo.png +0 -0
  274. package/css/img/loaders/util-white.png +0 -0
  275. package/css/img/loaders/util.png +0 -0
  276. package/css/img/loaders/video-white.png +0 -0
  277. package/css/img/loaders/video.png +0 -0
  278. package/css/img/maximize.png +0 -0
  279. package/css/img/minimize.png +0 -0
  280. package/css/img/next-btn.png +0 -0
  281. package/css/img/search-icon-sprite.png +0 -0
  282. package/css/img/services/blogvio.png +0 -0
  283. package/css/img/services/drive.png +0 -0
  284. package/css/img/services/dropbox-login.png +0 -0
  285. package/css/img/services/dropbox.png +0 -0
  286. package/css/img/services/facebook-login.png +0 -0
  287. package/css/img/services/facebook.png +0 -0
  288. package/css/img/services/flickr-login.png +0 -0
  289. package/css/img/services/flickr.png +0 -0
  290. package/css/img/services/instagram-login.png +0 -0
  291. package/css/img/services/instagram.png +0 -0
  292. package/css/img/services/text.png +0 -0
  293. package/css/img/services/uploads.png +0 -0
  294. package/css/img/services/uploads.svg +0 -689
  295. package/css/img/services/webaddress-bw.png +0 -0
  296. package/css/img/services/webaddress.png +0 -0
  297. package/css/img/services/webadress.png +0 -0
  298. package/css/img/services/websearch.png +0 -0
  299. package/css/img/services/wix.png +0 -0
  300. package/css/img/skin/item-active-indicator.png +0 -0
  301. package/css/img/skin/item-hover-icon.png +0 -0
  302. package/css/img/skin/overwrite.png +0 -0
  303. package/css/img/skin/popup-name-icon.png +0 -0
  304. package/css/img/skin/popup-save.png +0 -0
  305. package/css/img/skin/preset.png +0 -0
  306. package/css/img/skin/presets-arrow-purple.png +0 -0
  307. package/css/img/tw-icon-w.png +0 -0
  308. package/css/img/warning.png +0 -0
  309. package/css/img/white-rounded-check.png +0 -0
  310. package/css/index.styl +0 -9
  311. package/css/mixins.styl +0 -62
  312. package/css/scrollable.styl +0 -101
  313. package/karma.conf.js +0 -73
  314. package/lib/control.js +0 -1
  315. package/lib/controls.js +0 -1
  316. package/lib/core.editor.js +0 -4
  317. package/lib/core.js +0 -1
  318. package/lib/core.vendor.js +0 -3
  319. package/lib/dev/control.js +0 -845
  320. package/lib/dev/controls.js +0 -7774
  321. package/lib/dev/core.editor.js +0 -6439
  322. package/lib/dev/core.js +0 -1486
  323. package/lib/dev/core.vendor.js +0 -10708
  324. package/lib/dev/editor.css +0 -166
  325. package/lib/dev/editor.js +0 -29710
  326. package/lib/dev/editor.vendor.js +0 -15119
  327. package/lib/dev/jquery.nanoscroller.js.map +0 -10
  328. package/lib/editor.css +0 -167
  329. package/lib/editor.js +0 -49
  330. package/lib/editor.vendor.js +0 -11
  331. package/src/api/index.coffee +0 -23
  332. package/src/api/mixin.coffee +0 -89
  333. package/src/browser/controller/entry/audio.coffee +0 -38
  334. package/src/browser/controller/entry/button.coffee +0 -16
  335. package/src/browser/controller/entry/file.coffee +0 -35
  336. package/src/browser/controller/entry/folder.coffee +0 -14
  337. package/src/browser/controller/entry/image.coffee +0 -24
  338. package/src/browser/controller/entry/index.coffee +0 -18
  339. package/src/browser/controller/entry/rss.coffee +0 -5
  340. package/src/browser/controller/entry/video.coffee +0 -4
  341. package/src/browser/controller/index.coffee +0 -289
  342. package/src/browser/controller/instagram.coffee +0 -129
  343. package/src/browser/controller/navbar.coffee +0 -48
  344. package/src/browser/controller/social.coffee +0 -213
  345. package/src/browser/model/entry.coffee +0 -18
  346. package/src/browser/model/index.coffee +0 -34
  347. package/src/core/controller/font-loader.coffee +0 -65
  348. package/src/core/controller/lazyload.coffee +0 -14
  349. package/src/core/controller/overlay.coffee +0 -146
  350. package/src/core/controller/popup.coffee +0 -36
  351. package/src/core/controller/scrollable.coffee +0 -68
  352. package/src/core/controls/audio.coffee +0 -56
  353. package/src/core/controls/browser.coffee +0 -271
  354. package/src/core/controls/color.coffee +0 -121
  355. package/src/core/controls/date-time/calendar/index.coffee +0 -99
  356. package/src/core/controls/date-time/calendar/months.coffee +0 -52
  357. package/src/core/controls/date-time/calendar/years.coffee +0 -58
  358. package/src/core/controls/date-time/clock.coffee +0 -62
  359. package/src/core/controls/date-time/helper.coffee +0 -31
  360. package/src/core/controls/date-time/index.coffee +0 -62
  361. package/src/core/controls/date-time/popup.coffee +0 -56
  362. package/src/core/controls/details/audio.coffee +0 -33
  363. package/src/core/controls/details/image.coffee +0 -19
  364. package/src/core/controls/details/video.coffee +0 -14
  365. package/src/core/controls/dropdown.coffee +0 -100
  366. package/src/core/controls/font/font-item.coffee +0 -22
  367. package/src/core/controls/font/font-list.coffee +0 -108
  368. package/src/core/controls/font/fonts-popup.coffee +0 -91
  369. package/src/core/controls/font/index.coffee +0 -66
  370. package/src/core/controls/image.coffee +0 -37
  371. package/src/core/controls/index.coffee +0 -88
  372. package/src/core/controls/orderPicker.coffee +0 -93
  373. package/src/core/controls/position/grid-panel.coffee +0 -75
  374. package/src/core/controls/position/index.coffee +0 -98
  375. package/src/core/controls/position/panel.coffee +0 -68
  376. package/src/core/controls/position/pixel-panel.coffee +0 -54
  377. package/src/core/controls/position/popup.coffee +0 -63
  378. package/src/core/controls/position/reference-panel.coffee +0 -26
  379. package/src/core/controls/range.coffee +0 -191
  380. package/src/core/controls/rss.coffee +0 -16
  381. package/src/core/controls/scale.coffee +0 -78
  382. package/src/core/controls/slider.coffee +0 -122
  383. package/src/core/controls/stepper.coffee +0 -97
  384. package/src/core/controls/text.coffee +0 -54
  385. package/src/core/controls/textarea.coffee +0 -78
  386. package/src/core/controls/toggle.coffee +0 -29
  387. package/src/core/controls/url.coffee +0 -88
  388. package/src/core/controls/video.coffee +0 -9
  389. package/src/core/embed/api.js +0 -178
  390. package/src/core/embed/controller.js +0 -331
  391. package/src/core/models/composition.coffee +0 -178
  392. package/src/core/models/content.coffee +0 -79
  393. package/src/core/models/skin.coffee +0 -91
  394. package/src/core/models/user.coffee +0 -6
  395. package/src/core/models/widget.coffee +0 -88
  396. package/src/core/router/index.coffee +0 -44
  397. package/src/core/services/container/index.js +0 -18
  398. package/src/core/services/container/instance.js +0 -3
  399. package/src/core/services/content.js +0 -80
  400. package/src/core/services/instagram.coffee +0 -47
  401. package/src/core/services/mixin/events.js +0 -17
  402. package/src/core/services/service.js +0 -1
  403. package/src/core/services/sounds.js +0 -81
  404. package/src/core/services/uploads.js +0 -299
  405. package/src/core/utils/popup.coffee +0 -67
  406. package/src/core/utils/should-show.coffee +0 -12
  407. package/src/core/views/audio-details.pug +0 -9
  408. package/src/core/views/audio.pug +0 -7
  409. package/src/core/views/browser.pug +0 -4
  410. package/src/core/views/color.pug +0 -8
  411. package/src/core/views/date-time/calendar.pug +0 -3
  412. package/src/core/views/date-time/clock.pug +0 -12
  413. package/src/core/views/date-time/grid.pug +0 -6
  414. package/src/core/views/date-time/input.pug +0 -4
  415. package/src/core/views/date-time/table.pug +0 -6
  416. package/src/core/views/dropdown.pug +0 -9
  417. package/src/core/views/font/font-item.pug +0 -3
  418. package/src/core/views/font/fonts-list.pug +0 -7
  419. package/src/core/views/font/fonts-popup.pug +0 -5
  420. package/src/core/views/font/index.pug +0 -7
  421. package/src/core/views/image-details.pug +0 -6
  422. package/src/core/views/image.pug +0 -5
  423. package/src/core/views/input-layout.pug +0 -15
  424. package/src/core/views/order-items.pug +0 -7
  425. package/src/core/views/order-picker.pug +0 -7
  426. package/src/core/views/pixelpanel.pug +0 -69
  427. package/src/core/views/position.pug +0 -10
  428. package/src/core/views/range.pug +0 -9
  429. package/src/core/views/scale.pug +0 -7
  430. package/src/core/views/slider.pug +0 -6
  431. package/src/core/views/stepper.pug +0 -6
  432. package/src/core/views/text.pug +0 -4
  433. package/src/core/views/textarea.pug +0 -8
  434. package/src/core/views/toggle.pug +0 -5
  435. package/src/core/views/video-details.pug +0 -7
  436. package/src/core/views/video.pug +0 -4
  437. package/src/core/widget/index.coffee +0 -66
  438. package/src/editor/content/browser/dropzone.html +0 -268
  439. package/src/editor/content/browser/file.html +0 -124
  440. package/src/editor/content/browser/scroll-pane.html +0 -32
  441. package/src/editor/content/browser/service.html +0 -53
  442. package/src/editor/content/browser/upload.coffee +0 -44
  443. package/src/editor/content/browser/upload.html +0 -439
  444. package/src/editor/content/index.coffee +0 -294
  445. package/src/editor/content/input/field.coffee +0 -45
  446. package/src/editor/content/input/index.coffee +0 -115
  447. package/src/editor/content/service/base/photoitem.coffee +0 -49
  448. package/src/editor/content/service/cache.coffee +0 -20
  449. package/src/editor/content/service/default.coffee +0 -59
  450. package/src/editor/content/service/dropbox.coffee +0 -71
  451. package/src/editor/content/service/facebook.coffee +0 -143
  452. package/src/editor/content/service/flickr.coffee +0 -85
  453. package/src/editor/content/service/google-drive-sdk.coffee +0 -18
  454. package/src/editor/content/service/google-drive.coffee +0 -144
  455. package/src/editor/content/service/instagram-social.coffee +0 -84
  456. package/src/editor/content/service/instagram.coffee +0 -168
  457. package/src/editor/content/service/share-popup.coffee +0 -15
  458. package/src/editor/content/service/soundcloud.coffee +0 -163
  459. package/src/editor/content/tab.coffee +0 -27
  460. package/src/editor/details/bulk-editor/attributes-panel.coffee +0 -11
  461. package/src/editor/details/bulk-editor/index.coffee +0 -86
  462. package/src/editor/details/index.coffee +0 -156
  463. package/src/editor/details/item/attributes.coffee +0 -45
  464. package/src/editor/details/item/index.coffee +0 -82
  465. package/src/editor/editor.coffee +0 -274
  466. package/src/editor/footer/breadcrumb.coffee +0 -17
  467. package/src/editor/footer/index.coffee +0 -36
  468. package/src/editor/index.coffee +0 -79
  469. package/src/editor/listeners/embed-listener.coffee +0 -143
  470. package/src/editor/login-popup.coffee +0 -29
  471. package/src/editor/notification-controller.coffee +0 -35
  472. package/src/editor/popup.coffee +0 -68
  473. package/src/editor/remote-preview.coffee +0 -68
  474. package/src/editor/save/index.coffee +0 -62
  475. package/src/editor/skin/delete.coffee +0 -33
  476. package/src/editor/skin/editor/index.coffee +0 -188
  477. package/src/editor/skin/editor/panel.coffee +0 -37
  478. package/src/editor/skin/index.coffee +0 -126
  479. package/src/editor/skin/save.coffee +0 -58
  480. package/src/editor/skin/selector/index.coffee +0 -59
  481. package/src/editor/skin/selector/item.coffee +0 -123
  482. package/src/editor/skin/selector/list.coffee +0 -97
  483. package/src/editor/views/embed.pug +0 -10
  484. package/src/editor/views/loginbutton.pug +0 -8
  485. package/src/editor/views/popup.pug +0 -17
  486. package/src/editor/views/presets.pug +0 -8
  487. package/src/editor/views/presetslist-header.pug +0 -8
  488. package/src/editor/views/skinitem.pug +0 -13
  489. package/src/modules/control.js +0 -3
  490. package/src/modules/controls.js +0 -29
  491. package/src/modules/core.js +0 -19
  492. package/src/modules/editor.js +0 -80
  493. package/test/config.js +0 -3
  494. package/test/core/models/skin-spec.js +0 -209
  495. package/webpack.config.js +0 -46
  496. package/webpack.config.prod.js +0 -9
  497. /package/{src/core/views/date-time/popup.pug → dist/utils/passiveEvents.d.ts} +0 -0
@@ -0,0 +1,1302 @@
1
+ import { fontUtils, fontCategories } from "./fontUtils";
2
+ import FontFaceObserver from 'fontfaceobserver';
3
+ import { shouldSkipFont } from './skipFonts';
4
+ import fontsJson from '../assets/fonts.json';
5
+ import { deleteKvValue, readKvValue, writeKvValue } from '@widgetic/cache-layer';
6
+ const fontWeightOrder = {
7
+ Thin: 1,
8
+ ExtraLight: 2,
9
+ Light: 3,
10
+ Regular: 4,
11
+ Medium: 5,
12
+ SemiBold: 6,
13
+ Bold: 7,
14
+ ExtraBold: 8,
15
+ Black: 9,
16
+ };
17
+ /** Maps Google Fonts API variant ids to display weight labels. */
18
+ export function mapGoogleVariantIdsToWeightNames(variants) {
19
+ return variants
20
+ .filter((v) => !v.includes('italic'))
21
+ .map((v) => {
22
+ switch (v) {
23
+ case '100':
24
+ return 'Thin';
25
+ case '200':
26
+ return 'ExtraLight';
27
+ case '300':
28
+ return 'Light';
29
+ case '400':
30
+ case 'regular':
31
+ return 'Regular';
32
+ case '500':
33
+ return 'Medium';
34
+ case '600':
35
+ return 'SemiBold';
36
+ case '700':
37
+ case 'bold':
38
+ return 'Bold';
39
+ case '800':
40
+ return 'ExtraBold';
41
+ case '900':
42
+ return 'Black';
43
+ default:
44
+ return v;
45
+ }
46
+ })
47
+ .sort((a, b) => (fontWeightOrder[a] || 99) - (fontWeightOrder[b] || 99));
48
+ }
49
+ class FontManager {
50
+ // Debug configuration
51
+ static DEBUG_MODE = false;
52
+ static WAIT_TIME = FontManager.DEBUG_MODE ? 2500 : 250;
53
+ // Singleton instance
54
+ static instance = null;
55
+ // Track loaded and pending fonts to prevent duplicates
56
+ loadedFonts = new Set();
57
+ // Map of intersection observers keyed by container ID
58
+ observers = new Map();
59
+ // Debounce timer for scroll events
60
+ scrollTimeout = null;
61
+ // Google Fonts API key
62
+ apiKey;
63
+ // Cache promise to prevent multiple simultaneous API calls
64
+ fontListPromise = null;
65
+ /**
66
+ * Catalog lifecycle as an explicit union. Replaces the former
67
+ * `initialFontsLoaded`/`allFontsLoaded` boolean pair whose ordering
68
+ * ('initial' must precede 'complete') was only convention. Only the
69
+ * 'complete' stage is ever persisted to the durable cache, preventing
70
+ * partial catalogs from being written.
71
+ */
72
+ catalog = { stage: 'empty' };
73
+ /** Current catalog contents regardless of stage (empty until loaded). */
74
+ get catalogFonts() {
75
+ return this.catalog.stage === 'empty' ? [] : this.catalog.fonts;
76
+ }
77
+ // Cache configuration — the font catalog is global (not user-owned), so it
78
+ // lives in the L1 kv table without an owner prefix.
79
+ static CACHE_KEY = 'fonts.catalog';
80
+ // Controls how long we keep the font list in the durable L1 store
81
+ static CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
82
+ static LOADED_FONTS_KEY = 'fonts.loaded';
83
+ // Legacy localStorage keys read once for migration
84
+ static LEGACY_CACHE_KEY = 'google_fonts_cache';
85
+ static LEGACY_LOADED_FONTS_KEY = 'loaded_fonts_cache';
86
+ // Controls how long browsers should cache the actual font files and stylesheets
87
+ static CACHE_CONTROL = 'public, max-age=31536000'; // 1 year
88
+ // Add new property to store visible fonts
89
+ visibleFonts = new Set();
90
+ static initialPreloadComplete = false;
91
+ static preloadPromise = null;
92
+ completeFontList = [];
93
+ // Add new property to track initialization
94
+ static isInitialized = false;
95
+ static initialFontNumber = 8; // 8 - how many fonts to load initially
96
+ // Add a static property to track initialization state
97
+ static initializationPromise = null;
98
+ // Add this property
99
+ remainingTime = 0;
100
+ // Add a Map to store pre-initialized observers
101
+ preInitializedObservers = new Map();
102
+ // Add batch size constant
103
+ static BATCH_SIZE = 100;
104
+ currentBatchIndex = FontManager.initialFontNumber; // Start after initial fonts
105
+ // Add a map to track batch indices per controller
106
+ batchIndices = new Map();
107
+ // Add a map to store preloaded fonts per category
108
+ categoryFonts = new Map();
109
+ // Add to existing properties
110
+ customFonts = new Map();
111
+ static missingApiKeyWarned = false;
112
+ static forbiddenApiKeyWarned = false;
113
+ static badRequestApiKeyWarned = false;
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
+ }
134
+ hasGoogleFontsApiKey() {
135
+ return Boolean(this.apiKey?.trim());
136
+ }
137
+ warnMissingApiKey() {
138
+ if (FontManager.missingApiKeyWarned)
139
+ return;
140
+ FontManager.missingApiKeyWarned = true;
141
+ console.warn('[FontManager] Google Fonts API key is missing. Set PUBLIC_GOOGLE_FONTS_API_KEY in Frontend/props-editor/.env ' +
142
+ '(or VITE_GOOGLE_FONTS_API_KEY in widget-creator/.env when using the embedded editor), then restart the dev server.');
143
+ }
144
+ warnForbiddenApiKey() {
145
+ if (FontManager.forbiddenApiKeyWarned)
146
+ return;
147
+ FontManager.forbiddenApiKeyWarned = true;
148
+ console.warn('[FontManager] Google Fonts API returned 403 Forbidden. Fix in Google Cloud Console: ' +
149
+ '(1) Enable "Web Fonts Developer API" on the project, ' +
150
+ '(2) API key → Application restrictions → HTTP referrers: http://localhost:5180/*, http://localhost:5174/*, http://127.0.0.1:5180/*, https://*.widgetic.com/*, ' +
151
+ '(3) use a key created for Web Fonts (not Gemini/other APIs). Font picker falls back to cached/custom fonts.');
152
+ }
153
+ warnBadRequestApiKey() {
154
+ if (FontManager.badRequestApiKeyWarned)
155
+ return;
156
+ FontManager.badRequestApiKeyWarned = true;
157
+ console.warn('[FontManager] Google Fonts API returned 400 Bad Request. Usually: invalid API key, Web Fonts Developer API not enabled, or wrong key type. ' +
158
+ 'Use the "Widgetic-Fonts" key from Google Cloud (Web Fonts API only), set VITE_GOOGLE_FONTS_API_KEY in widget-creator/.env, restart dev server. ' +
159
+ 'Font picker falls back to cached/custom fonts.');
160
+ }
161
+ /**
162
+ * Fetches the full Google Fonts catalog (Web Fonts Developer API).
163
+ * Returns null when key is missing or the request fails — callers use cache/custom fonts.
164
+ */
165
+ async fetchGoogleFontsCatalog() {
166
+ if (!this.catalogFetchPromise) {
167
+ this.catalogFetchPromise = this.fetchGoogleFontsCatalogOnce();
168
+ }
169
+ return this.catalogFetchPromise;
170
+ }
171
+ async fetchGoogleFontsCatalogOnce() {
172
+ if (!this.hasGoogleFontsApiKey()) {
173
+ this.warnMissingApiKey();
174
+ return null;
175
+ }
176
+ try {
177
+ const response = await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${this.apiKey}&sort=popularity&fields=items(family,category,variants)&prettyPrint=false`);
178
+ if (!response.ok) {
179
+ if (response.status === 403) {
180
+ this.warnForbiddenApiKey();
181
+ }
182
+ else if (response.status === 400) {
183
+ this.warnBadRequestApiKey();
184
+ }
185
+ else {
186
+ console.warn('[FontManager] Google Fonts API request failed:', response.status, response.statusText);
187
+ }
188
+ return null;
189
+ }
190
+ const data = await response.json();
191
+ const filteredItems = (data.items || []).filter((font) => !shouldSkipFont(font.family));
192
+ return filteredItems.map((font) => ({
193
+ family: font.family,
194
+ category: fontCategories.normalize(font.category) || this.categorizeFont(font.family),
195
+ isCustom: false,
196
+ variants: font.variants,
197
+ }));
198
+ }
199
+ catch (error) {
200
+ console.warn('[FontManager] Google Fonts API fetch error:', error);
201
+ return null;
202
+ }
203
+ }
204
+ /** Variant ids from cached catalog (avoids per-font API calls). */
205
+ getGoogleFontVariantIds(family) {
206
+ const normalized = family.trim().toLowerCase();
207
+ const match = this.catalogFonts.find((f) => f.family.toLowerCase() === normalized) ||
208
+ this.completeFontList.find((f) => f.family.toLowerCase() === normalized);
209
+ return match?.variants?.length ? match.variants : null;
210
+ }
211
+ /**
212
+ * Private constructor to enforce singleton pattern
213
+ * @param apiKey - Google Fonts API key
214
+ */
215
+ constructor(apiKey) {
216
+ this.apiKey = apiKey; // Initialize apiKey in constructor
217
+ // console.log('🏗️ [FontManager] Creating FontManager instance...');
218
+ }
219
+ /**
220
+ * Gets or creates the singleton instance and initializes it
221
+ */
222
+ static async getInstance(apiKey) {
223
+ if (!FontManager.instance) {
224
+ FontManager.instance = new FontManager(apiKey);
225
+ // Initialize only once
226
+ if (!FontManager.initializationPromise) {
227
+ FontManager.initializationPromise = FontManager.instance.initialize();
228
+ }
229
+ // Wait for initialization to complete
230
+ await FontManager.initializationPromise;
231
+ }
232
+ return FontManager.instance;
233
+ }
234
+ /**
235
+ * Initializes the FontManager with initial fonts and setup
236
+ */
237
+ async initialize() {
238
+ if (FontManager.isInitialized) {
239
+ console.log('✅ [FontManager] Already initialized');
240
+ return;
241
+ }
242
+ try {
243
+ // console.log('🚀 [FontManager] Starting initialization...');
244
+ // Load custom fonts first, before any other font operations
245
+ const customFonts = await this.loadCustomFonts();
246
+ // console.log('✅ [FontManager] Loaded custom fonts:', customFonts.length);
247
+ // First check browser's cached fonts
248
+ const cachedFonts = await this.getBrowserCachedFonts();
249
+ // console.log('📦 [FontManager] Browser cached fonts:', {
250
+ // total: cachedFonts.length,
251
+ // fonts: cachedFonts
252
+ // });
253
+ // Add cached fonts to loadedFonts set
254
+ cachedFonts.forEach(font => this.loadedFonts.add(font));
255
+ // Save to localStorage for persistence
256
+ this.saveLoadedFonts();
257
+ // Get recent fonts first
258
+ const recentFonts = fontUtils.getRecentFonts()
259
+ .filter(font => !shouldSkipFont(font.family))
260
+ .map(font => ({
261
+ family: font.family,
262
+ category: fontCategories.normalize(font.category) || this.categorizeFont(font.family)
263
+ }));
264
+ // Get initial font list and cache it
265
+ const initialFontList = await this.getInitialFontList();
266
+ // console.log('📋 [FontManager] Initial font list loaded:', initialFontList.length);
267
+ // Cache the complete font list (skip duplicate fetch if getInitialFontList already loaded catalog)
268
+ try {
269
+ const catalog = this.catalogFonts.length > 0 && this.catalog.stage !== 'empty'
270
+ ? this.catalogFonts
271
+ : await this.fetchGoogleFontsCatalog();
272
+ if (catalog?.length) {
273
+ this.catalog = { stage: 'complete', fonts: catalog };
274
+ // Preload initial fonts for each category
275
+ const categories = ['Serif', 'Sans Serif', 'Script', 'Other'];
276
+ // Preload fonts for each category
277
+ await Promise.all(categories.map(async (category) => {
278
+ const categoryFonts = this.catalogFonts
279
+ .filter(font => fontCategories.normalize(font.category) === category)
280
+ .slice(0, FontManager.initialFontNumber);
281
+ this.categoryFonts.set(category, categoryFonts);
282
+ // Preload the fonts by inserting their stylesheets
283
+ await Promise.all(categoryFonts.map(async (font) => {
284
+ try {
285
+ await this.insertFontStylesheet(font.family);
286
+ await this.loadFont(font.family, ['400', '500', '600', '700']);
287
+ }
288
+ catch (error) {
289
+ console.warn(`⚠️ [FontManager] Failed to preload font: ${font.family}`, error);
290
+ }
291
+ }));
292
+ // console.log(`📚 [FontManager] Preloaded fonts for ${category}:`, {
293
+ // total: categoryFonts.length,
294
+ // sample: categoryFonts.slice(0, 3).map(f => f.family)
295
+ // });
296
+ }));
297
+ // Save filtered list to localStorage cache
298
+ this.saveFontListToCache(this.catalogFonts);
299
+ // console.log('📚 [FontManager] Complete font list cached:', {
300
+ // total: this.cachedFontList.length,
301
+ // categoriesPreloaded: Array.from(this.categoryFonts.keys())
302
+ // });
303
+ }
304
+ }
305
+ catch (error) {
306
+ // console.error('❌ [FontManager] Error caching complete font list:', error);
307
+ }
308
+ // Define initial fonts that must be loaded (excluding skipped fonts)
309
+ const initialFonts = [
310
+ 'Roboto', 'Open Sans', 'Montserrat', 'Poppins',
311
+ 'Lato', 'Inter'
312
+ ].filter(font => !shouldSkipFont(font));
313
+ // Add popular fonts from the initial list
314
+ const popularFonts = initialFontList
315
+ .slice(0, FontManager.initialFontNumber)
316
+ .map(font => font.family)
317
+ .filter(family => !initialFonts.includes(family));
318
+ // Combine all fonts to preload: initial fonts + recent fonts + popular fonts
319
+ const fontsToLoad = [...new Set([
320
+ ...initialFonts,
321
+ ...recentFonts.map(font => font.family),
322
+ ...popularFonts
323
+ ])].filter(font => !this.loadedFonts.has(font));
324
+ if (fontsToLoad.length > 0) {
325
+ // console.log('🔄 [FontManager] Preloading fonts:', fontsToLoad);
326
+ // Load all fonts in parallel with all weights
327
+ const loadPromises = fontsToLoad.map(async (family) => {
328
+ try {
329
+ await this.insertFontStylesheet(family);
330
+ await this.loadFont(family, ['400', '500', '600', '700']);
331
+ }
332
+ catch (error) {
333
+ console.warn(`⚠️ [FontManager] Failed to load font: ${family}`, error);
334
+ }
335
+ });
336
+ await Promise.all(loadPromises);
337
+ }
338
+ else {
339
+ console.log('✨ [FontManager] All initial fonts already cached');
340
+ }
341
+ // Only set initialized after everything is complete
342
+ FontManager.isInitialized = true;
343
+ // console.log('✅ [FontManager] Initialization complete. Font status:', {
344
+ // totalLoaded: this.loadedFonts.size,
345
+ // loadedFonts: Array.from(this.loadedFonts),
346
+ // cachedFonts: this.cachedFontList.length
347
+ // });
348
+ // Pre-initialize observers for font controllers
349
+ this.preInitializeObservers();
350
+ }
351
+ catch (error) {
352
+ console.error('❌ [FontManager] Initialization failed:', error);
353
+ throw error;
354
+ }
355
+ }
356
+ /**
357
+ * Pre-initializes observers during FontManager initialization
358
+ */
359
+ preInitializeObservers() {
360
+ // Create a default observer configuration
361
+ const observerConfig = {
362
+ root: null,
363
+ rootMargin: '50px',
364
+ threshold: 0
365
+ };
366
+ // Create and store the observer
367
+ const observer = new IntersectionObserver((entries) => {
368
+ entries.forEach(entry => {
369
+ const fontFamily = entry.target.getAttribute('data-font');
370
+ if (fontFamily) {
371
+ if (entry.isIntersecting) {
372
+ this.addVisibleFont(fontFamily);
373
+ if (!this.isScrolling()) {
374
+ this.loadInitialVisibleFonts();
375
+ }
376
+ }
377
+ else {
378
+ this.removeVisibleFont(fontFamily);
379
+ }
380
+ }
381
+ });
382
+ }, observerConfig);
383
+ // Store it with a default key
384
+ this.preInitializedObservers.set('default', observer);
385
+ }
386
+ /**
387
+ * Gets a pre-initialized observer
388
+ */
389
+ getPreInitializedObserver() {
390
+ return this.preInitializedObservers.get('default') || null;
391
+ }
392
+ /**
393
+ * Loads previously cached fonts from the durable L1 store (legacy localStorage fallback)
394
+ */
395
+ async loadCachedFonts() {
396
+ try {
397
+ let cachedLoadedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
398
+ if (!cachedLoadedFonts) {
399
+ const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
400
+ if (legacyRaw) {
401
+ try {
402
+ cachedLoadedFonts = JSON.parse(legacyRaw);
403
+ await writeKvValue(FontManager.LOADED_FONTS_KEY, cachedLoadedFonts);
404
+ }
405
+ catch {
406
+ /* ignore malformed legacy data */
407
+ }
408
+ localStorage.removeItem(FontManager.LEGACY_LOADED_FONTS_KEY);
409
+ }
410
+ }
411
+ if (cachedLoadedFonts && Array.isArray(cachedLoadedFonts)) {
412
+ // Only load the first fonts initially
413
+ const initialFonts = cachedLoadedFonts.slice(0, FontManager.initialFontNumber);
414
+ const loadPromises = initialFonts.map(async (font) => {
415
+ const family = typeof font === 'string' ? font : font.family;
416
+ if (family) {
417
+ try {
418
+ await this.loadFont(family, ['400', '500', '600', '700']);
419
+ }
420
+ catch (error) {
421
+ console.warn(`⚠️ [FontManager] Failed to load cached font: ${family}`, error);
422
+ }
423
+ }
424
+ });
425
+ await Promise.all(loadPromises);
426
+ }
427
+ // console.log('📊 [FontManager] Initial cache initialization complete:', {
428
+ // loadedFontsSize: this.loadedFonts.size,
429
+ // loadedFontsList: Array.from(this.loadedFonts)
430
+ // });
431
+ }
432
+ catch (error) {
433
+ console.error('❌ [FontManager] Error loading cached fonts:', error);
434
+ }
435
+ }
436
+ /**
437
+ * Saves the current state of loaded fonts to the durable L1 store
438
+ */
439
+ saveLoadedFonts() {
440
+ try {
441
+ const fontsToCache = Array.from(this.loadedFonts);
442
+ void writeKvValue(FontManager.LOADED_FONTS_KEY, fontsToCache);
443
+ // Add cache control headers for font files
444
+ const styleSheets = document.querySelectorAll('link[href*="fonts.googleapis.com"]');
445
+ styleSheets.forEach(sheet => {
446
+ if (sheet instanceof HTMLLinkElement) {
447
+ sheet.setAttribute('crossorigin', 'anonymous');
448
+ // Add cache control header through meta tag since we can't directly set headers
449
+ const meta = document.createElement('meta');
450
+ meta.setAttribute('http-equiv', 'Cache-Control');
451
+ meta.setAttribute('content', FontManager.CACHE_CONTROL);
452
+ document.head.appendChild(meta);
453
+ }
454
+ });
455
+ }
456
+ catch (error) {
457
+ console.error('❌ [FontManager] Error saving loaded fonts:', error);
458
+ }
459
+ }
460
+ /**
461
+ * Gets the stylesheet href for a font family with optimized parameters
462
+ */
463
+ getFontStylesheetHref(family) {
464
+ try {
465
+ const encodedFamily = encodeURIComponent(String(family).trim());
466
+ // Use text parameter to only load needed characters
467
+ const text = encodeURIComponent('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,!?@#$%^&*()');
468
+ return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@400;500;600;700&display=swap&text=${text}`;
469
+ }
470
+ catch (error) {
471
+ console.error('Error creating font stylesheet href:', error);
472
+ return '';
473
+ }
474
+ }
475
+ /**
476
+ * Checks if a font is available in Google Fonts
477
+ */
478
+ async isGoogleFont(family) {
479
+ // Ensure we have the complete font list
480
+ if (this.completeFontList.length === 0) {
481
+ const catalog = await this.fetchGoogleFontsCatalog();
482
+ if (catalog?.length) {
483
+ this.completeFontList = catalog;
484
+ }
485
+ }
486
+ return this.completeFontList.some(font => font.family.toLowerCase() === family.toLowerCase());
487
+ }
488
+ /**
489
+ * Inserts a font stylesheet into the document
490
+ * @param family - Font family name
491
+ * @param subset - Whether to use text subsetting (for preview)
492
+ */
493
+ async insertFontStylesheet(family, subset = true) {
494
+ try {
495
+ // Skip if already loaded with full character set
496
+ const fullFontLink = document.querySelector(`link[href*="${encodeURIComponent(family)}"][data-full-font="true"]`);
497
+ if (fullFontLink) {
498
+ return;
499
+ }
500
+ // Skip Google Fonts API for custom fonts
501
+ if (this.isCustomFont(family)) {
502
+ return;
503
+ }
504
+ // Add preconnect for Google Fonts
505
+ if (!document.querySelector('link[href="https://fonts.gstatic.com"][rel="preconnect"]')) {
506
+ const preconnect = document.createElement('link');
507
+ preconnect.rel = 'preconnect';
508
+ preconnect.href = 'https://fonts.gstatic.com';
509
+ preconnect.crossOrigin = 'anonymous';
510
+ document.head.appendChild(preconnect);
511
+ }
512
+ // Build URL based on whether we want subset or full font
513
+ const baseUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(family)}:wght@400&display=swap`;
514
+ const url = subset
515
+ ? `${baseUrl}&text=${encodeURIComponent('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')}`
516
+ : baseUrl;
517
+ const finalUrl = `${url}&capability=WOFF2`;
518
+ // Create and append stylesheet link first
519
+ const link = document.createElement('link');
520
+ link.rel = 'stylesheet';
521
+ link.href = finalUrl;
522
+ link.crossOrigin = 'anonymous';
523
+ if (!subset) {
524
+ link.setAttribute('data-full-font', 'true');
525
+ }
526
+ // Add to document
527
+ document.head.appendChild(link);
528
+ // Add preload hint with correct attributes after stylesheet
529
+ const preloadLink = document.createElement('link');
530
+ preloadLink.rel = 'preload';
531
+ preloadLink.as = 'style'; // Changed from 'font' to 'style'
532
+ preloadLink.href = finalUrl;
533
+ preloadLink.crossOrigin = 'anonymous';
534
+ preloadLink.setAttribute('onload', "this.onload=null;this.rel='stylesheet'");
535
+ document.head.appendChild(preloadLink);
536
+ // Wait for stylesheet to load
537
+ await new Promise((resolve, reject) => {
538
+ link.onload = () => resolve();
539
+ link.onerror = () => reject(new Error(`Failed to load stylesheet for ${family}`));
540
+ // Add a timeout to prevent hanging
541
+ setTimeout(resolve, 3000);
542
+ });
543
+ }
544
+ catch (error) {
545
+ console.warn(`⚠️ [FontManager] Error inserting stylesheet for ${family}, continuing anyway:`, error);
546
+ }
547
+ }
548
+ /**
549
+ * Loads a font with full character set
550
+ */
551
+ async loadFullFont(family) {
552
+ try {
553
+ // Skip if it's a custom font
554
+ if (this.isCustomFont(family)) {
555
+ return;
556
+ }
557
+ // console.log(`📥 [FontManager] Loading full font: ${family}`);
558
+ await this.insertFontStylesheet(family, false);
559
+ // Remove any subset version
560
+ const subsetLink = document.querySelector(`link[href*="${encodeURIComponent(family)}"][data-full-font="false"]`);
561
+ if (subsetLink) {
562
+ subsetLink.remove();
563
+ }
564
+ }
565
+ catch (error) {
566
+ console.error(`❌ [FontManager] Error loading full font: ${family}`, error);
567
+ }
568
+ }
569
+ /**
570
+ * Checks if a font is in the initial fonts list
571
+ */
572
+ isInitialFont(family) {
573
+ const initialFonts = ['Roboto', 'Open Sans', 'Montserrat', 'Poppins', 'Lato', 'Inter', 'Material Icons'];
574
+ return initialFonts.includes(family);
575
+ }
576
+ /**
577
+ * Checks if a font is in the browser cache
578
+ */
579
+ async checkFontInBrowserCache(family) {
580
+ try {
581
+ const href = this.getFontStylesheetHref(family);
582
+ const response = await fetch(href, {
583
+ method: 'HEAD',
584
+ cache: 'force-cache'
585
+ });
586
+ // Check if response came from browser cache
587
+ const fromCache = response.headers.get('cf-cache-status') === 'HIT' ||
588
+ response.headers.get('x-cache') === 'HIT' ||
589
+ response.headers.get('age') !== null;
590
+ return fromCache;
591
+ }
592
+ catch (error) {
593
+ console.warn('⚠️ [FontManager] Error checking browser cache:', error);
594
+ return false;
595
+ }
596
+ }
597
+ /**
598
+ * Maps Google Fonts categories to our simplified category system
599
+ * @param category - The category from Google Fonts API
600
+ * @returns Simplified category name
601
+ */
602
+ mapCategory(category) {
603
+ switch (category?.toLowerCase()) {
604
+ case 'serif':
605
+ return 'Serif';
606
+ case 'sans-serif':
607
+ return 'Sans Serif';
608
+ case 'handwriting':
609
+ case 'display':
610
+ return 'Script';
611
+ case 'monospace':
612
+ default:
613
+ return 'Other';
614
+ }
615
+ }
616
+ /**
617
+ * Ensures fonts are sorted by popularity
618
+ */
619
+ ensureFontSorting(fonts) {
620
+ // Keep original order from Google Fonts API as it's already sorted by popularity
621
+ return fonts;
622
+ }
623
+ /**
624
+ * Gets initial font list (initial number of fonts + recent fonts)
625
+ * @returns Promise resolving to initial font list
626
+ */
627
+ async getInitialFontList() {
628
+ try {
629
+ if (this.catalog.stage !== 'empty') {
630
+ return this.catalogFonts;
631
+ }
632
+ // Load recent fonts first (keep existing code)
633
+ const recentFonts = fontUtils.getRecentFonts()
634
+ .filter(font => !shouldSkipFont(font.family))
635
+ .map(font => ({
636
+ family: font.family,
637
+ category: fontCategories.normalize(font.category),
638
+ isCustom: font.isCustom
639
+ }));
640
+ // Add custom fonts from fonts.json
641
+ const customFonts = await this.loadCustomFonts();
642
+ const catalog = await this.fetchGoogleFontsCatalog();
643
+ const googleFonts = (catalog && catalog.length > 0) ? catalog : FontManager.getFallbackCatalog();
644
+ // Combine all fonts. A full Google catalog means the list is already
645
+ // complete; otherwise keep the 'initial' stage so a later fetch can
646
+ // still upgrade it.
647
+ const fonts = [
648
+ ...recentFonts,
649
+ ...customFonts,
650
+ ...googleFonts.filter((font) => !recentFonts.some((recent) => recent.family === font.family) &&
651
+ !customFonts.some((custom) => custom.family === font.family))
652
+ ];
653
+ this.catalog = catalog?.length
654
+ ? { stage: 'complete', fonts }
655
+ : { stage: 'initial', fonts };
656
+ return this.catalogFonts;
657
+ }
658
+ catch (error) {
659
+ console.error('Error getting initial fonts:', error);
660
+ return FontManager.getFallbackCatalog();
661
+ }
662
+ }
663
+ /**
664
+ * Saves font list to the durable L1 cache.
665
+ * Only 'complete' catalogs are persisted — a partial ('initial') catalog
666
+ * written here used to be served for the full 24h cache duration.
667
+ */
668
+ saveFontListToCache(fonts) {
669
+ try {
670
+ if (this.catalog.stage !== 'complete') {
671
+ console.warn('⚠️ [FontManager] Skipping cache write: catalog is not complete yet');
672
+ return;
673
+ }
674
+ const cacheData = {
675
+ timestamp: Date.now(),
676
+ fonts: fonts
677
+ };
678
+ void writeKvValue(FontManager.CACHE_KEY, cacheData);
679
+ }
680
+ catch (error) {
681
+ console.error('❌ [FontManager] Error caching font list:', error);
682
+ }
683
+ }
684
+ /**
685
+ * Gets font list from the durable L1 cache (legacy localStorage fallback)
686
+ */
687
+ async getFontListFromCache() {
688
+ try {
689
+ let cache = await readKvValue(FontManager.CACHE_KEY, FontManager.CACHE_DURATION);
690
+ if (!cache) {
691
+ const legacyRaw = localStorage.getItem(FontManager.LEGACY_CACHE_KEY);
692
+ if (legacyRaw) {
693
+ try {
694
+ cache = JSON.parse(legacyRaw);
695
+ }
696
+ catch {
697
+ /* ignore malformed legacy data */
698
+ }
699
+ localStorage.removeItem(FontManager.LEGACY_CACHE_KEY);
700
+ }
701
+ }
702
+ if (!cache)
703
+ return null;
704
+ // Check if cache is expired
705
+ if (Date.now() - cache.timestamp > FontManager.CACHE_DURATION) {
706
+ console.log('⚠️ [FontManager] Cache expired, removing');
707
+ void deleteKvValue(FontManager.CACHE_KEY);
708
+ return null;
709
+ }
710
+ // console.log('📦 [FontManager] Using cached font list:', {
711
+ // fonts: cache.fonts.length,
712
+ // age: Math.round((now - cache.timestamp) / 1000 / 60) + ' minutes'
713
+ // });
714
+ return cache.fonts;
715
+ }
716
+ catch (error) {
717
+ console.error('❌ [FontManager] Error reading font cache:', error);
718
+ return null;
719
+ }
720
+ }
721
+ /**
722
+ * Gets complete font list (called when dropdown opens)
723
+ */
724
+ async getCompleteFontList(force = false) {
725
+ try {
726
+ if (this.catalog.stage === 'complete' && !force) {
727
+ return this.catalogFonts;
728
+ }
729
+ if (!force) {
730
+ const cachedFonts = await this.getFontListFromCache();
731
+ if (cachedFonts) {
732
+ this.catalog = { stage: 'complete', fonts: cachedFonts };
733
+ return cachedFonts;
734
+ }
735
+ }
736
+ this.fontListPromise = (async () => {
737
+ const catalog = await this.fetchGoogleFontsCatalog();
738
+ if (!catalog?.length) {
739
+ return this.catalogFonts.length ? this.catalogFonts : [];
740
+ }
741
+ const allFonts = catalog;
742
+ this.catalog = { stage: 'complete', fonts: allFonts };
743
+ this.saveFontListToCache(allFonts);
744
+ // console.log('✅ [FontManager] Complete font list loaded:', {
745
+ // total: allFonts.length,
746
+ // filtered: data.items.length - allFonts.length,
747
+ // format: 'WOFF2'
748
+ // });
749
+ return allFonts;
750
+ })();
751
+ return this.fontListPromise;
752
+ }
753
+ catch (error) {
754
+ console.error('❌ [FontManager] Error getting complete font list:', error);
755
+ return this.catalogFonts;
756
+ }
757
+ }
758
+ /**
759
+ * Preloads initial fonts with optimized loading strategy
760
+ * Returns existing promise if preload is in progress
761
+ */
762
+ async preloadInitialFonts() {
763
+ // Return existing promise if preload is in progress
764
+ if (FontManager.preloadPromise) {
765
+ return FontManager.preloadPromise;
766
+ }
767
+ // Return immediately if preload is already complete
768
+ if (FontManager.initialPreloadComplete) {
769
+ console.log('✅ [FontManager] Initial fonts already preloaded');
770
+ return;
771
+ }
772
+ // Create new preload promise
773
+ FontManager.preloadPromise = (async () => {
774
+ try {
775
+ console.log('🚀 [FontManager] Preloading initial fonts...');
776
+ const initialFonts = await this.getInitialFontList();
777
+ const loadPromises = initialFonts.map(font => {
778
+ this.insertFontStylesheet(font.family);
779
+ return this.loadFont(font.family);
780
+ });
781
+ await Promise.all(loadPromises);
782
+ FontManager.initialPreloadComplete = true;
783
+ console.log('✅ [FontManager] Initial fonts preloaded');
784
+ }
785
+ catch (error) {
786
+ console.error('❌ [FontManager] Error preloading initial fonts:', error);
787
+ }
788
+ finally {
789
+ FontManager.preloadPromise = null;
790
+ }
791
+ })();
792
+ return FontManager.preloadPromise;
793
+ }
794
+ /**
795
+ * Loads initial visible fonts
796
+ */
797
+ async loadInitialVisibleFonts() {
798
+ const visibleFonts = Array.from(this.visibleFonts);
799
+ // Get initial fonts list
800
+ const initialFonts = this.catalogFonts.slice(0, FontManager.initialFontNumber);
801
+ const initialFontFamilies = new Set(initialFonts.map(font => font.family));
802
+ // Filter visible fonts to only include those in the initial batch
803
+ const fontsToLoad = visibleFonts.filter(font => initialFontFamilies.has(font));
804
+ if (fontsToLoad.length > 0) {
805
+ // console.log('🔄 [FontManager] Loading initial visible fonts:', {
806
+ // visible: visibleFonts.length,
807
+ // toLoad: fontsToLoad.length,
808
+ // initialBatchSize: initialFonts.length
809
+ // });
810
+ const loadPromises = fontsToLoad.map(async (family) => {
811
+ try {
812
+ await this.loadFont(family, ['400', '500', '600', '700']);
813
+ }
814
+ catch (error) {
815
+ console.warn(`⚠️ [FontManager] Failed to load font: ${family}`, error);
816
+ }
817
+ });
818
+ await Promise.all(loadPromises);
819
+ }
820
+ }
821
+ /**
822
+ * Creates or retrieves an intersection observer for a container
823
+ * @param container - The HTML element to observe
824
+ * @returns IntersectionObserver instance or null if container is invalid
825
+ */
826
+ getObserver(container) {
827
+ if (!container) {
828
+ console.warn('❌ [FontManager] No container provided for observer');
829
+ return null;
830
+ }
831
+ // Generate or retrieve unique ID for container
832
+ const key = container.dataset.observerId || `font-observer-${Math.random().toString(36).slice(2)}`;
833
+ container.dataset.observerId = key;
834
+ // Return existing observer if one exists
835
+ if (this.observers.has(key)) {
836
+ return this.observers.get(key) || null;
837
+ }
838
+ // Create new observer
839
+ // console.log('🔧 [FontManager] Creating new observer for container:', key);
840
+ const observer = new IntersectionObserver((entries) => {
841
+ entries.forEach(entry => {
842
+ const fontFamily = entry.target.getAttribute('data-font');
843
+ if (fontFamily) {
844
+ if (entry.isIntersecting) {
845
+ this.addVisibleFont(fontFamily);
846
+ // Load initial fonts immediately when they become visible
847
+ if (!this.isScrolling()) {
848
+ this.loadInitialVisibleFonts();
849
+ }
850
+ }
851
+ else {
852
+ this.removeVisibleFont(fontFamily);
853
+ }
854
+ }
855
+ });
856
+ }, {
857
+ root: container,
858
+ rootMargin: '50px' // Pre-load fonts slightly before they come into view
859
+ });
860
+ this.observers.set(key, observer);
861
+ return observer;
862
+ }
863
+ /**
864
+ * Gets the font sources for a given family, including fallbacks
865
+ * @param fontFamily - Font family name
866
+ * @returns CSS src string with font sources
867
+ */
868
+ getFontSources(fontFamily) {
869
+ try {
870
+ const encodedFamily = encodeURIComponent(fontFamily);
871
+ // Use Google Fonts CSS API instead of direct file access
872
+ return `url(https://fonts.googleapis.com/css2?family=${encodedFamily}&display=swap)`;
873
+ }
874
+ catch (error) {
875
+ console.error('Error generating font sources:', error);
876
+ return '';
877
+ }
878
+ }
879
+ /**
880
+ * Generates a complete @font-face declaration
881
+ * @param fontFamily - Font family name
882
+ * @param options - Font face configuration options
883
+ * @returns Complete @font-face CSS rule
884
+ */
885
+ generateFontFaceRule(fontFamily, options = {}) {
886
+ const weight = options.weight || 'normal';
887
+ const style = options.style || 'normal';
888
+ return `
889
+ @font-face {
890
+ font-family: '${fontFamily}';
891
+ src: ${this.getFontSources(fontFamily)};
892
+ font-weight: ${weight};
893
+ font-style: ${style};
894
+ font-display: swap;
895
+ }
896
+ `;
897
+ }
898
+ /**
899
+ * Loads a font with specified weights using FontFaceObserver
900
+ * Simplified version with better error handling
901
+ */
902
+ async loadFont(family, weights = ['400']) {
903
+ if (this.loadedFonts.has(family)) {
904
+ return;
905
+ }
906
+ try {
907
+ // First ensure the stylesheet is loaded
908
+ await this.insertFontStylesheet(family);
909
+ // Wait a bit for stylesheet to be properly processed
910
+ await new Promise(resolve => setTimeout(resolve, 100));
911
+ // Load only 400 weight initially for faster loading
912
+ const observer = new FontFaceObserver(family);
913
+ await observer.load(null, 3000); // Reduced timeout
914
+ // Mark as loaded after basic weight is loaded
915
+ this.loadedFonts.add(family);
916
+ this.saveLoadedFonts();
917
+ // Load additional weights in the background
918
+ if (weights.length > 1) {
919
+ Promise.all(weights
920
+ .filter(weight => weight !== '400')
921
+ .map(weight => {
922
+ const observer = new FontFaceObserver(family, { weight });
923
+ return observer.load(null, 3000).catch(err => {
924
+ console.warn(`⚠️ [FontManager] Non-critical weight ${weight} failed for ${family}:`, err);
925
+ return false;
926
+ });
927
+ })).then(results => {
928
+ const loadedCount = results.filter(Boolean).length;
929
+ // console.log(`📦 [FontManager] Additional weights loaded for ${family}:`, loadedCount);
930
+ });
931
+ }
932
+ // console.log('✅ [FontManager] Font loaded successfully:', family);
933
+ }
934
+ catch (error) {
935
+ console.warn(`⚠️ [FontManager] Error loading font ${family}, falling back to system fonts:`, error);
936
+ // Still mark as loaded to prevent retries
937
+ this.loadedFonts.add(family);
938
+ this.saveLoadedFonts();
939
+ }
940
+ }
941
+ /**
942
+ * Handles scroll optimization for font loading
943
+ * Implements debouncing to prevent excessive font loading
944
+ * @param callback - Optional callback after fonts are loaded
945
+ */
946
+ handleScroll(callback) {
947
+ if (this.scrollTimeout) {
948
+ window.clearTimeout(this.scrollTimeout);
949
+ this.scrollTimeout = null;
950
+ }
951
+ this.remainingTime = FontManager.WAIT_TIME;
952
+ this.scrollTimeout = window.setTimeout(async () => {
953
+ const visibleFonts = Array.from(this.visibleFonts);
954
+ // console.log('🔄 [FontManager] Loading visible fonts:', {
955
+ // total: visibleFonts.length,
956
+ // fonts: visibleFonts
957
+ // });
958
+ try {
959
+ // Load all visible fonts in parallel
960
+ const loadPromises = visibleFonts.map(async (family) => {
961
+ if (this.loadedFonts.has(family)) {
962
+ const status = await this.verifyFontCache(family);
963
+ if (!status.cached) {
964
+ // console.log('⚠️ [FontManager] Reloading uncached font:', family);
965
+ await this.insertFontStylesheet(family);
966
+ return this.loadFont(family);
967
+ }
968
+ return Promise.resolve();
969
+ }
970
+ // console.log('📥 [FontManager] Loading new font:', family);
971
+ await this.insertFontStylesheet(family);
972
+ return this.loadFont(family);
973
+ });
974
+ await Promise.all(loadPromises);
975
+ // Log final cache status
976
+ const finalStatus = await Promise.all(visibleFonts.map(async (family) => ({
977
+ font: family,
978
+ status: await this.verifyFontCache(family)
979
+ })));
980
+ // console.log('✅ [FontManager] Font loading complete:', {
981
+ // total: finalStatus.length,
982
+ // fonts: finalStatus.map(({ font, status }) => ({
983
+ // font,
984
+ // cached: status.cached,
985
+ // stylesheet: status.stylesheet,
986
+ // fontFace: status.fontFace
987
+ // }))
988
+ // });
989
+ }
990
+ catch (error) {
991
+ console.error('❌ [FontManager] Error loading fonts:', error);
992
+ }
993
+ this.visibleFonts.clear();
994
+ this.scrollTimeout = null;
995
+ if (callback)
996
+ callback();
997
+ }, FontManager.WAIT_TIME);
998
+ }
999
+ /**
1000
+ * Checks if a font is already loaded
1001
+ */
1002
+ isFontLoaded(family) {
1003
+ return this.loadedFonts.has(family);
1004
+ }
1005
+ /**
1006
+ * Gets stats about loaded fonts
1007
+ */
1008
+ getLoadedFontsStats() {
1009
+ return {
1010
+ total: this.loadedFonts.size,
1011
+ fonts: Array.from(this.loadedFonts)
1012
+ };
1013
+ }
1014
+ /**
1015
+ * Cleans up resources when a font container is removed
1016
+ * Prevents memory leaks from orphaned observers
1017
+ * @param containerId - ID of the container being cleaned up
1018
+ */
1019
+ cleanup(containerId) {
1020
+ const observer = this.observers.get(containerId);
1021
+ if (observer) {
1022
+ observer.disconnect();
1023
+ this.observers.delete(containerId);
1024
+ }
1025
+ }
1026
+ /**
1027
+ * Categorizes a font based on its family name
1028
+ * Delegates to fontUtils for consistent categorization
1029
+ * @param family - Font family name to categorize
1030
+ */
1031
+ categorizeFont(family) {
1032
+ return fontUtils.categorizeFont(family);
1033
+ }
1034
+ // Add public method to check scroll state
1035
+ isScrolling() {
1036
+ return this.scrollTimeout !== null;
1037
+ }
1038
+ // Method to track fonts that become visible
1039
+ addVisibleFont(fontFamily) {
1040
+ if (!this.visibleFonts.has(fontFamily)) {
1041
+ this.visibleFonts.add(fontFamily);
1042
+ // console.log(`👁️ [FontManager] Font visible: ${fontFamily}`);
1043
+ }
1044
+ }
1045
+ removeVisibleFont(family) {
1046
+ if (this.visibleFonts.has(family)) {
1047
+ this.visibleFonts.delete(family);
1048
+ // console.log('👋 [FontManager] Font hidden:', family);
1049
+ }
1050
+ }
1051
+ /**
1052
+ * Verifies if a font is actually cached in the browser
1053
+ */
1054
+ async verifyFontCache(family) {
1055
+ const status = {
1056
+ family,
1057
+ cached: false,
1058
+ headers: {},
1059
+ stylesheet: false,
1060
+ fontFace: false
1061
+ };
1062
+ try {
1063
+ // Check the durable loaded-fonts registry (legacy localStorage fallback)
1064
+ let cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
1065
+ if (!cachedFonts) {
1066
+ const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
1067
+ cachedFonts = legacyRaw ? JSON.parse(legacyRaw) : [];
1068
+ }
1069
+ const loadedFonts = Array.isArray(cachedFonts) ? cachedFonts : [];
1070
+ const isCached = loadedFonts.includes(family);
1071
+ // Check if stylesheet exists in DOM
1072
+ status.stylesheet = !!document.querySelector(`link[href*="${encodeURIComponent(family)}"]`);
1073
+ // Consider font loaded if it's both in localStorage and has a stylesheet
1074
+ status.fontFace = isCached;
1075
+ status.cached = status.stylesheet && status.fontFace;
1076
+ // Set standard cache headers
1077
+ status.headers = {
1078
+ cacheControl: 'max-age=31536000', // 1 year
1079
+ age: '0',
1080
+ cfCache: null,
1081
+ xCache: null
1082
+ };
1083
+ return status;
1084
+ }
1085
+ catch (error) {
1086
+ console.warn('⚠️ [FontManager] Error verifying font cache:', family, error);
1087
+ return status;
1088
+ }
1089
+ }
1090
+ /**
1091
+ * Gets list of fonts cached in browser with detailed status
1092
+ */
1093
+ async getBrowserCachedFonts() {
1094
+ try {
1095
+ // Get fonts from the durable loaded-fonts registry (legacy fallback)
1096
+ let cachedFonts = await readKvValue(FontManager.LOADED_FONTS_KEY);
1097
+ if (!cachedFonts) {
1098
+ const legacyRaw = localStorage.getItem(FontManager.LEGACY_LOADED_FONTS_KEY);
1099
+ cachedFonts = legacyRaw ? JSON.parse(legacyRaw) : [];
1100
+ }
1101
+ cachedFonts = Array.isArray(cachedFonts) ? cachedFonts : [];
1102
+ // console.log('📦 [FontManager] Fonts in durable registry:', cachedFonts);
1103
+ // Verify each font has its stylesheet in the DOM
1104
+ const verifiedFonts = await Promise.all(cachedFonts.map(async (font) => {
1105
+ const status = await this.verifyFontCache(font);
1106
+ return { font, status };
1107
+ }));
1108
+ // Filter to only include fonts that are properly cached
1109
+ const validCachedFonts = verifiedFonts
1110
+ .filter(({ status }) => status.cached)
1111
+ .map(({ font }) => font);
1112
+ // console.log('✅ [FontManager] Valid cached fonts:', {
1113
+ // total: validCachedFonts.length,
1114
+ // fonts: validCachedFonts
1115
+ // });
1116
+ return validCachedFonts;
1117
+ }
1118
+ catch (error) {
1119
+ console.error('❌ [FontManager] Error getting browser cached fonts:', error);
1120
+ return [];
1121
+ }
1122
+ }
1123
+ /**
1124
+ * Gets the cached font list without making an API call
1125
+ */
1126
+ getCachedFontList() {
1127
+ return this.catalogFonts;
1128
+ }
1129
+ /**
1130
+ * Gets the next batch of fonts
1131
+ */
1132
+ getNextBatch(controllerId, category) {
1133
+ // Get or initialize batch index for this controller
1134
+ let currentIndex = this.batchIndices.get(controllerId) || FontManager.initialFontNumber;
1135
+ // Filter fonts by category first
1136
+ const categoryFonts = category === 'All'
1137
+ ? this.catalogFonts
1138
+ : category === 'Mine'
1139
+ ? Array.from(this.customFonts.values())
1140
+ : this.catalogFonts.filter(font => fontCategories.normalize(font.category) === category);
1141
+ const start = currentIndex;
1142
+ const end = start + FontManager.BATCH_SIZE;
1143
+ // Get next batch from category-filtered list
1144
+ const batch = categoryFonts.slice(start, end);
1145
+ // Only update index if we actually got more fonts
1146
+ if (batch.length > 0) {
1147
+ this.batchIndices.set(controllerId, end);
1148
+ }
1149
+ // console.log('📦 [FontManager] Loading next batch:', {
1150
+ // controllerId,
1151
+ // category,
1152
+ // start,
1153
+ // end,
1154
+ // batchSize: batch.length,
1155
+ // totalInCategory: categoryFonts.length,
1156
+ // remainingFonts: categoryFonts.length - end
1157
+ // });
1158
+ return batch;
1159
+ }
1160
+ /**
1161
+ * Checks if there are more fonts to load
1162
+ */
1163
+ hasMoreFonts(controllerId, category) {
1164
+ const currentIndex = this.batchIndices.get(controllerId) || FontManager.initialFontNumber;
1165
+ // Filter fonts by category first
1166
+ const categoryFonts = category === 'All'
1167
+ ? this.catalogFonts
1168
+ : category === 'Mine'
1169
+ ? Array.from(this.customFonts.values())
1170
+ : this.catalogFonts.filter(font => fontCategories.normalize(font.category) === category);
1171
+ const remainingItems = categoryFonts.length - currentIndex;
1172
+ console.log('🔍 [FontManager] Checking remaining fonts:', {
1173
+ category,
1174
+ currentIndex,
1175
+ totalInCategory: categoryFonts.length,
1176
+ remainingItems
1177
+ });
1178
+ // Only return true if we have more items in this category
1179
+ return remainingItems > 0;
1180
+ }
1181
+ /**
1182
+ * Resets the batch index to initial number
1183
+ * This allows each font input controller to start from the beginning
1184
+ */
1185
+ resetBatchIndex(controllerId) {
1186
+ this.batchIndices.set(controllerId, FontManager.initialFontNumber);
1187
+ // console.log('🔄 [FontManager] Reset batch index for:', controllerId);
1188
+ }
1189
+ // Add method to get preloaded fonts for a category
1190
+ getPreloadedFonts(category) {
1191
+ if (category === 'Mine') {
1192
+ return Array.from(this.customFonts.values());
1193
+ }
1194
+ const preloadedFonts = this.categoryFonts.get(category) || [];
1195
+ // Ensure fonts are loaded
1196
+ preloadedFonts.forEach(font => {
1197
+ if (!this.loadedFonts.has(font.family)) {
1198
+ this.insertFontStylesheet(font.family)
1199
+ .then(() => this.loadFont(font.family, ['400']))
1200
+ .catch(error => console.warn(`⚠️ [FontManager] Failed to load font: ${font.family}`, error));
1201
+ }
1202
+ });
1203
+ return preloadedFonts;
1204
+ }
1205
+ /**
1206
+ * Gets the complete list of Google Fonts
1207
+ * @returns Promise resolving to FontData array
1208
+ */
1209
+ async getGoogleFontsList() {
1210
+ const catalog = await this.fetchGoogleFontsCatalog();
1211
+ return catalog || [];
1212
+ }
1213
+ // Add new method to handle custom font removal
1214
+ async removeCustomFont(family) {
1215
+ try {
1216
+ if (this.customFonts.has(family)) {
1217
+ this.customFonts.delete(family);
1218
+ this.loadedFonts.delete(family);
1219
+ // Update cached font list
1220
+ if (this.catalog.stage !== 'empty') {
1221
+ this.catalog = {
1222
+ ...this.catalog,
1223
+ fonts: this.catalog.fonts.filter(font => font.family !== family || font.category !== 'Mine'),
1224
+ };
1225
+ }
1226
+ console.log('✅ [FontManager] Custom font removed:', family);
1227
+ }
1228
+ }
1229
+ catch (error) {
1230
+ console.error('❌ [FontManager] Error removing custom font:', error);
1231
+ }
1232
+ }
1233
+ async loadCustomFonts() {
1234
+ try {
1235
+ const fontsData = fontsJson;
1236
+ const families = Array.isArray(fontsData.fontFamilies) ? fontsData.fontFamilies : [];
1237
+ const customFonts = [];
1238
+ for (const familyData of families) {
1239
+ try {
1240
+ const fontData = {
1241
+ family: familyData.family,
1242
+ category: 'Mine',
1243
+ isCustom: true
1244
+ };
1245
+ this.customFonts.set(familyData.family, fontData);
1246
+ customFonts.push(fontData);
1247
+ // Process each variant
1248
+ for (const variant of familyData.variants || []) {
1249
+ try {
1250
+ const isItalic = variant.weight.includes('Italic');
1251
+ // Skip local/relative font files — they only exist in standalone mode
1252
+ const fontUrl = variant.url;
1253
+ if (fontUrl.startsWith('/') && !fontUrl.startsWith('//')) {
1254
+ continue;
1255
+ }
1256
+ const fontFace = new FontFace(familyData.family, `url(${fontUrl})`, {
1257
+ style: isItalic ? 'italic' : 'normal',
1258
+ display: 'swap'
1259
+ });
1260
+ await document.fonts.add(fontFace);
1261
+ await fontFace.load();
1262
+ this.loadedFonts.add(familyData.family);
1263
+ // console.log(`✅ [FontManager] Added font face with swap:`, {
1264
+ // family: familyData.family,
1265
+ // weight: variant.weight,
1266
+ // style: isItalic ? 'italic' : 'normal'
1267
+ // });
1268
+ }
1269
+ catch (error) {
1270
+ console.warn(`⚠️ [FontManager] Failed to add font face:`, {
1271
+ family: familyData.family,
1272
+ weight: variant.weight,
1273
+ error: error instanceof Error ? error.message : String(error)
1274
+ });
1275
+ }
1276
+ }
1277
+ }
1278
+ catch (error) {
1279
+ console.warn(`⚠️ [FontManager] Failed to load font family: ${familyData.family}`, error);
1280
+ }
1281
+ }
1282
+ return customFonts;
1283
+ }
1284
+ catch (error) {
1285
+ console.error('❌ [FontManager] Error loading custom fonts:', error);
1286
+ return [];
1287
+ }
1288
+ }
1289
+ /**
1290
+ * Checks if a font is a custom font
1291
+ */
1292
+ isCustomFont(family) {
1293
+ return this.customFonts.has(family);
1294
+ }
1295
+ /**
1296
+ * Gets custom font data if available
1297
+ */
1298
+ getCustomFont(family) {
1299
+ return this.customFonts.get(family);
1300
+ }
1301
+ }
1302
+ export default FontManager;