@widgetic/editor 3.11.3 → 4.0.1

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