@widgetic/editor 3.11.4 → 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.
- package/README.md +245 -0
- package/dist/assets/fonts.json +331 -0
- package/dist/assets/icons/ic-browser/audio.svg +7 -0
- package/dist/assets/icons/ic-browser/document.svg +11 -0
- package/dist/assets/icons/ic-browser/error.svg +5 -0
- package/dist/assets/icons/ic-browser/folder.svg +28 -0
- package/dist/assets/icons/ic-browser/player-pause.svg +1 -0
- package/dist/assets/icons/ic-browser/player-play.svg +1 -0
- package/dist/assets/icons/ic-browser/trash.svg +1 -0
- package/dist/assets/icons/ic-fixed-position/background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-left-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-left-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-left-icon.svg +4 -0
- package/dist/assets/icons/ic-fixed-position/bottom-right-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-right-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/bottom-right-icon.svg +4 -0
- package/dist/assets/icons/ic-fixed-position/center-bottom-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/center-center-icon.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/center-left-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/center-right-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/center-top-icon.svg +5 -0
- package/dist/assets/icons/ic-fixed-position/top-left-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-left-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-left-icon.svg +4 -0
- package/dist/assets/icons/ic-fixed-position/top-right-background-deselected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-right-background-selected.svg +3 -0
- package/dist/assets/icons/ic-fixed-position/top-right-icon.svg +4 -0
- package/dist/assets/icons/trash.svg +1 -0
- package/dist/components/MockWidget.svelte +108 -0
- package/dist/components/MockWidget.svelte.d.ts +22 -0
- package/dist/components/WidgetPreview.svelte +852 -0
- package/dist/components/WidgetPreview.svelte.d.ts +100 -0
- package/dist/components/color-select/ColorSelect.svelte +258 -0
- package/dist/components/color-select/ColorSelect.svelte.d.ts +42 -0
- package/dist/components/color-select/color.d.ts +25 -0
- package/dist/components/color-select/color.js +21 -0
- package/dist/components/color-select/constants.d.ts +5 -0
- package/dist/components/color-select/constants.js +5 -0
- package/dist/components/color-select/render.d.ts +5 -0
- package/dist/components/color-select/render.js +77 -0
- package/dist/components/color-select/shaders.d.ts +3 -0
- package/dist/components/color-select/shaders.js +8 -0
- package/dist/components/editors/EmbedEditor.svelte +700 -0
- package/dist/components/editors/EmbedEditor.svelte.d.ts +50 -0
- package/dist/components/editors/PropsEditor.svelte +3952 -0
- package/dist/components/editors/PropsEditor.svelte.d.ts +62 -0
- package/dist/components/editors/props-editor.css +73 -0
- package/dist/components/ic/AngleInputController.svelte +276 -0
- package/dist/components/ic/AngleInputController.svelte.d.ts +26 -0
- package/dist/components/ic/BrowserInputController.svelte +724 -0
- package/dist/components/ic/BrowserInputController.svelte.d.ts +39 -0
- package/dist/components/ic/CheckboxInputController.svelte +203 -0
- package/dist/components/ic/CheckboxInputController.svelte.d.ts +41 -0
- package/dist/components/ic/ColorPickerInputController.svelte +1584 -0
- package/dist/components/ic/ColorPickerInputController.svelte.d.ts +50 -0
- package/dist/components/ic/DateFieldInputController.svelte +453 -0
- package/dist/components/ic/DateFieldInputController.svelte.d.ts +35 -0
- package/dist/components/ic/DatePickerInputController.svelte +364 -0
- package/dist/components/ic/DatePickerInputController.svelte.d.ts +28 -0
- package/dist/components/ic/DropdownInputController.svelte +226 -0
- package/dist/components/ic/DropdownInputController.svelte.d.ts +43 -0
- package/dist/components/ic/FixedPositionInputController.svelte +353 -0
- package/dist/components/ic/FixedPositionInputController.svelte.d.ts +23 -0
- package/dist/components/ic/FontInputController.svelte +2062 -0
- package/dist/components/ic/FontInputController.svelte.d.ts +47 -0
- package/dist/components/ic/InputControllerHeader.svelte +147 -0
- package/dist/components/ic/InputControllerHeader.svelte.d.ts +23 -0
- package/dist/components/ic/NumberControls.svelte +299 -0
- package/dist/components/ic/NumberControls.svelte.d.ts +30 -0
- package/dist/components/ic/NumberInputController.svelte +397 -0
- package/dist/components/ic/NumberInputController.svelte.d.ts +30 -0
- package/dist/components/ic/PositionInputController.svelte +252 -0
- package/dist/components/ic/PositionInputController.svelte.d.ts +37 -0
- package/dist/components/ic/RadioGroupInputController.svelte +162 -0
- package/dist/components/ic/RadioGroupInputController.svelte.d.ts +38 -0
- package/dist/components/ic/RangeCalendarInputController.svelte +262 -0
- package/dist/components/ic/RangeCalendarInputController.svelte.d.ts +32 -0
- package/dist/components/ic/RoundedCornersInputController.svelte +469 -0
- package/dist/components/ic/RoundedCornersInputController.svelte.d.ts +37 -0
- package/dist/components/ic/SliderInputController.svelte +411 -0
- package/dist/components/ic/SliderInputController.svelte.d.ts +46 -0
- package/dist/components/ic/SliderRangeInputController.svelte +155 -0
- package/dist/components/ic/SliderRangeInputController.svelte.d.ts +33 -0
- package/dist/components/ic/SwitchInputController.svelte +107 -0
- package/dist/components/ic/SwitchInputController.svelte.d.ts +24 -0
- package/dist/components/ic/TagsInputController.svelte +125 -0
- package/dist/components/ic/TagsInputController.svelte.d.ts +27 -0
- package/dist/components/ic/TextAreaInputController.svelte +1804 -0
- package/dist/components/ic/TextAreaInputController.svelte.d.ts +13 -0
- package/dist/components/ic/TextAreaPreviewInputController.svelte +476 -0
- package/dist/components/ic/TextAreaPreviewInputController.svelte.d.ts +28 -0
- package/dist/components/ic/TextInputController.svelte +70 -0
- package/dist/components/ic/TextInputController.svelte.d.ts +30 -0
- package/dist/components/ic/TimeInputController.svelte +434 -0
- package/dist/components/ic/TimeInputController.svelte.d.ts +25 -0
- package/dist/components/ic/VectorInputController.svelte +1826 -0
- package/dist/components/ic/VectorInputController.svelte.d.ts +47 -0
- package/dist/components/ic/index.d.ts +39 -0
- package/dist/components/ic/index.js +24 -0
- package/dist/components/ic/shared/CategoryTabs.svelte +40 -0
- package/dist/components/ic/shared/CategoryTabs.svelte.d.ts +25 -0
- package/dist/components/ic/shared/ItemGrid.svelte +181 -0
- package/dist/components/ic/shared/ItemGrid.svelte.d.ts +35 -0
- package/dist/components/ic/shared/RecentItemsSection.svelte +260 -0
- package/dist/components/ic/shared/RecentItemsSection.svelte.d.ts +35 -0
- package/dist/components/ic/shared/index.d.ts +8 -0
- package/dist/components/ic/shared/index.js +9 -0
- package/dist/components/icons/addnew.svelte +21 -0
- package/dist/components/icons/addnew.svelte.d.ts +20 -0
- package/dist/components/icons/drag.svelte +29 -0
- package/dist/components/icons/drag.svelte.d.ts +20 -0
- package/dist/components/icons/drag.svg +1 -0
- package/dist/components/icons/locked.svelte +4 -0
- package/dist/components/icons/locked.svelte.d.ts +26 -0
- package/dist/components/icons/locked.svg +4 -0
- package/dist/components/icons/ok.svelte +20 -0
- package/dist/components/icons/ok.svelte.d.ts +20 -0
- package/dist/components/icons/ok.svg +11 -0
- package/dist/components/icons/unlocked.svelte +4 -0
- package/dist/components/icons/unlocked.svelte.d.ts +26 -0
- package/dist/components/icons/unlocked.svg +4 -0
- package/dist/components/icons/x.svelte +14 -0
- package/dist/components/icons/x.svelte.d.ts +20 -0
- package/dist/components/icons/x.svg +5 -0
- package/dist/components/ui/arrows/OpacitySliderArrows.svelte +43 -0
- package/dist/components/ui/arrows/OpacitySliderArrows.svelte.d.ts +23 -0
- package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte +15 -0
- package/dist/components/ui/checkerboard-background/CheckerboardBackground.svelte.d.ts +20 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +19 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +8 -0
- package/dist/stores/userSession.d.ts +7 -0
- package/dist/stores/userSession.js +28 -0
- package/dist/utils/FontManager.d.ts +276 -0
- package/dist/utils/FontManager.js +1283 -0
- package/dist/utils/MediaManager.d.ts +35 -0
- package/dist/utils/MediaManager.js +59 -0
- package/dist/utils/VectorManager.d.ts +190 -0
- package/dist/utils/VectorManager.js +754 -0
- package/dist/utils/colorAutocomplete.d.ts +10 -0
- package/dist/utils/colorAutocomplete.js +28 -0
- package/dist/utils/colorNames.d.ts +3 -0
- package/dist/utils/colorNames.js +152 -0
- package/dist/utils/colorTransparency.d.ts +52 -0
- package/dist/utils/colorTransparency.js +114 -0
- package/dist/utils/fontUtils.d.ts +34 -0
- package/dist/utils/fontUtils.js +132 -0
- package/dist/utils/passiveEvents.js +20 -0
- package/dist/utils/recentColors.d.ts +10 -0
- package/dist/utils/recentColors.js +63 -0
- package/dist/utils/skipFonts.d.ts +3 -0
- package/dist/utils/skipFonts.js +49 -0
- package/dist/utils/vectorUtils.d.ts +34 -0
- package/dist/utils/vectorUtils.js +71 -0
- package/dist/utils.d.ts +11 -0
- package/dist/utils.js +38 -0
- package/package.json +134 -74
- package/.babelrc +0 -10
- package/.bowerrc +0 -4
- package/.nvmrc +0 -1
- package/Gruntfile.coffee +0 -69
- package/bower.json +0 -28
- package/css/browser/entries/activateable.styl +0 -29
- package/css/browser/entries/audio.styl +0 -39
- package/css/browser/entries/folder.styl +0 -79
- package/css/browser/entries/image.styl +0 -26
- package/css/browser/entries/index.styl +0 -66
- package/css/browser/entries/rss.styl +0 -11
- package/css/browser/entries/video.styl +0 -11
- package/css/browser/index.styl +0 -86
- package/css/browser/intent/index.styl +0 -61
- package/css/browser/nav/index.styl +0 -73
- package/css/browser/social-browser.styl +0 -23
- package/css/controls/clock.styl +0 -110
- package/css/controls/config.styl +0 -6
- package/css/controls/control-audio-details.styl +0 -63
- package/css/controls/control-audio.styl +0 -28
- package/css/controls/control-browser.styl +0 -48
- package/css/controls/control-color.styl +0 -423
- package/css/controls/control-date_time.styl +0 -191
- package/css/controls/control-dropdown.styl +0 -79
- package/css/controls/control-font.styl +0 -147
- package/css/controls/control-image-details.styl +0 -35
- package/css/controls/control-image.styl +0 -43
- package/css/controls/control-order-picker.styl +0 -29
- package/css/controls/control-position.styl +0 -390
- package/css/controls/control-range.styl +0 -68
- package/css/controls/control-scale.styl +0 -30
- package/css/controls/control-slider.styl +0 -38
- package/css/controls/control-spinner.styl +0 -39
- package/css/controls/control-textarea.styl +0 -52
- package/css/controls/control-toggle.styl +0 -34
- package/css/controls/control-url.styl +0 -32
- package/css/controls/control-video-details.styl +0 -85
- package/css/controls/control-video.styl +0 -43
- package/css/controls/general.styl +0 -98
- package/css/controls/index.styl +0 -25
- package/css/editor/colors.styl +0 -7
- package/css/editor/content/directinput.styl +0 -47
- package/css/editor/content/index.styl +0 -111
- package/css/editor/content/message.styl +0 -53
- package/css/editor/content/tabs.styl +0 -75
- package/css/editor/copied-from-old-style.styl +0 -197
- package/css/editor/details/bulk-editor.styl +0 -104
- package/css/editor/details/index.styl +0 -21
- package/css/editor/details/item.styl +0 -153
- package/css/editor/embed.styl +0 -106
- package/css/editor/footer.styl +0 -180
- package/css/editor/full-editor.styl +0 -16
- package/css/editor/index.styl +0 -124
- package/css/editor/login-popup.styl +0 -13
- package/css/editor/preview.styl +0 -257
- package/css/editor/skin/editor/index.styl +0 -54
- package/css/editor/skin/editor/tabs.styl +0 -111
- package/css/editor/skin/index.styl +0 -138
- package/css/editor/skin/popup.styl +0 -152
- package/css/editor/skin/presets/dropdown.styl +0 -61
- package/css/editor/skin/presets/index.styl +0 -86
- package/css/editor/skin/presets/list.styl +0 -244
- package/css/editor/tooltips.styl +0 -85
- package/css/fonts.styl +0 -47
- package/css/icons/BlogvioEditor.dev.svg +0 -112
- package/css/icons.styl +0 -16
- package/css/img/arrow-right-rounded-v-small.png +0 -0
- package/css/img/browser-folder-separator.png +0 -0
- package/css/img/calendar-input-icons.png +0 -0
- package/css/img/check.png +0 -0
- package/css/img/close.png +0 -0
- package/css/img/composition/edit-icon.png +0 -0
- package/css/img/content/audio-icon.png +0 -0
- package/css/img/content/folder-icon.png +0 -0
- package/css/img/content/messages/error-background.png +0 -0
- package/css/img/content/messages/error-icon.png +0 -0
- package/css/img/content/messages/notification-background.png +0 -0
- package/css/img/content/messages/notification-icon.png +0 -0
- package/css/img/content/no-files-audio.png +0 -0
- package/css/img/content/no-files-image.png +0 -0
- package/css/img/content/no-files-video.png +0 -0
- package/css/img/content/no-files.png +0 -0
- package/css/img/content/rss-icon.png +0 -0
- package/css/img/content/selected.png +0 -0
- package/css/img/content/video-icon.png +0 -0
- package/css/img/content/with-control-text.png +0 -0
- package/css/img/control-dropdown-arrow.png +0 -0
- package/css/img/control-image-valid.png +0 -0
- package/css/img/control-position-icon.png +0 -0
- package/css/img/control-scale-lock-icon.png +0 -0
- package/css/img/control-slider-arrows-hover.png +0 -0
- package/css/img/control-slider-arrows.png +0 -0
- package/css/img/control-spinner-buttons-hover.png +0 -0
- package/css/img/control-spinner-buttons.png +0 -0
- package/css/img/control-textarea-bold-active.png +0 -0
- package/css/img/control-textarea-bold.png +0 -0
- package/css/img/control-textarea-italic-active.png +0 -0
- package/css/img/control-textarea-italic.png +0 -0
- package/css/img/control-textarea-underline-active.png +0 -0
- package/css/img/control-textarea-underline.png +0 -0
- package/css/img/custom-checkbox-sprite.png +0 -0
- package/css/img/details/item-collapse-icon.png +0 -0
- package/css/img/details/item-expand-icon.png +0 -0
- package/css/img/details/item-sort-icon.png +0 -0
- package/css/img/done-arrow.png +0 -0
- package/css/img/fb-logo-icon-w.png +0 -0
- package/css/img/grid-icons.png +0 -0
- package/css/img/icon-reference.png +0 -0
- package/css/img/indicator.png +0 -0
- package/css/img/loader.gif +0 -0
- package/css/img/loaders/audio-white.png +0 -0
- package/css/img/loaders/audio.png +0 -0
- package/css/img/loaders/photo-white.png +0 -0
- package/css/img/loaders/photo.png +0 -0
- package/css/img/loaders/util-white.png +0 -0
- package/css/img/loaders/util.png +0 -0
- package/css/img/loaders/video-white.png +0 -0
- package/css/img/loaders/video.png +0 -0
- package/css/img/maximize.png +0 -0
- package/css/img/minimize.png +0 -0
- package/css/img/next-btn.png +0 -0
- package/css/img/search-icon-sprite.png +0 -0
- package/css/img/services/blogvio.png +0 -0
- package/css/img/services/drive.png +0 -0
- package/css/img/services/dropbox-login.png +0 -0
- package/css/img/services/dropbox.png +0 -0
- package/css/img/services/facebook-login.png +0 -0
- package/css/img/services/facebook.png +0 -0
- package/css/img/services/flickr-login.png +0 -0
- package/css/img/services/flickr.png +0 -0
- package/css/img/services/instagram-login.png +0 -0
- package/css/img/services/instagram.png +0 -0
- package/css/img/services/text.png +0 -0
- package/css/img/services/uploads.png +0 -0
- package/css/img/services/uploads.svg +0 -689
- package/css/img/services/webaddress-bw.png +0 -0
- package/css/img/services/webaddress.png +0 -0
- package/css/img/services/webadress.png +0 -0
- package/css/img/services/websearch.png +0 -0
- package/css/img/services/wix.png +0 -0
- package/css/img/skin/item-active-indicator.png +0 -0
- package/css/img/skin/item-hover-icon.png +0 -0
- package/css/img/skin/overwrite.png +0 -0
- package/css/img/skin/popup-name-icon.png +0 -0
- package/css/img/skin/popup-save.png +0 -0
- package/css/img/skin/preset.png +0 -0
- package/css/img/skin/presets-arrow-purple.png +0 -0
- package/css/img/tw-icon-w.png +0 -0
- package/css/img/warning.png +0 -0
- package/css/img/white-rounded-check.png +0 -0
- package/css/index.styl +0 -9
- package/css/mixins.styl +0 -62
- package/css/scrollable.styl +0 -101
- package/karma.conf.js +0 -73
- package/lib/control.js +0 -1
- package/lib/controls.js +0 -1
- package/lib/core.editor.js +0 -4
- package/lib/core.js +0 -1
- package/lib/core.vendor.js +0 -3
- package/lib/dev/control.js +0 -845
- package/lib/dev/controls.js +0 -7774
- package/lib/dev/core.editor.js +0 -6439
- package/lib/dev/core.js +0 -1486
- package/lib/dev/core.vendor.js +0 -10708
- package/lib/dev/editor.css +0 -166
- package/lib/dev/editor.js +0 -29710
- package/lib/dev/editor.vendor.js +0 -15119
- package/lib/dev/jquery.nanoscroller.js.map +0 -10
- package/lib/editor.css +0 -167
- package/lib/editor.js +0 -49
- package/lib/editor.vendor.js +0 -11
- package/src/api/index.coffee +0 -23
- package/src/api/mixin.coffee +0 -89
- package/src/browser/controller/entry/audio.coffee +0 -38
- package/src/browser/controller/entry/button.coffee +0 -16
- package/src/browser/controller/entry/file.coffee +0 -35
- package/src/browser/controller/entry/folder.coffee +0 -14
- package/src/browser/controller/entry/image.coffee +0 -24
- package/src/browser/controller/entry/index.coffee +0 -18
- package/src/browser/controller/entry/rss.coffee +0 -5
- package/src/browser/controller/entry/video.coffee +0 -4
- package/src/browser/controller/index.coffee +0 -289
- package/src/browser/controller/instagram.coffee +0 -129
- package/src/browser/controller/navbar.coffee +0 -48
- package/src/browser/controller/social.coffee +0 -213
- package/src/browser/model/entry.coffee +0 -18
- package/src/browser/model/index.coffee +0 -34
- package/src/core/controller/font-loader.coffee +0 -65
- package/src/core/controller/lazyload.coffee +0 -14
- package/src/core/controller/overlay.coffee +0 -146
- package/src/core/controller/popup.coffee +0 -36
- package/src/core/controller/scrollable.coffee +0 -68
- package/src/core/controls/audio.coffee +0 -56
- package/src/core/controls/browser.coffee +0 -271
- package/src/core/controls/color.coffee +0 -121
- package/src/core/controls/date-time/calendar/index.coffee +0 -99
- package/src/core/controls/date-time/calendar/months.coffee +0 -52
- package/src/core/controls/date-time/calendar/years.coffee +0 -58
- package/src/core/controls/date-time/clock.coffee +0 -62
- package/src/core/controls/date-time/helper.coffee +0 -31
- package/src/core/controls/date-time/index.coffee +0 -62
- package/src/core/controls/date-time/popup.coffee +0 -56
- package/src/core/controls/details/audio.coffee +0 -33
- package/src/core/controls/details/image.coffee +0 -19
- package/src/core/controls/details/video.coffee +0 -14
- package/src/core/controls/dropdown.coffee +0 -100
- package/src/core/controls/font/font-item.coffee +0 -22
- package/src/core/controls/font/font-list.coffee +0 -108
- package/src/core/controls/font/fonts-popup.coffee +0 -91
- package/src/core/controls/font/index.coffee +0 -66
- package/src/core/controls/image.coffee +0 -37
- package/src/core/controls/index.coffee +0 -88
- package/src/core/controls/orderPicker.coffee +0 -93
- package/src/core/controls/position/grid-panel.coffee +0 -75
- package/src/core/controls/position/index.coffee +0 -98
- package/src/core/controls/position/panel.coffee +0 -68
- package/src/core/controls/position/pixel-panel.coffee +0 -54
- package/src/core/controls/position/popup.coffee +0 -63
- package/src/core/controls/position/reference-panel.coffee +0 -26
- package/src/core/controls/range.coffee +0 -191
- package/src/core/controls/rss.coffee +0 -16
- package/src/core/controls/scale.coffee +0 -78
- package/src/core/controls/slider.coffee +0 -122
- package/src/core/controls/stepper.coffee +0 -97
- package/src/core/controls/text.coffee +0 -54
- package/src/core/controls/textarea.coffee +0 -78
- package/src/core/controls/toggle.coffee +0 -29
- package/src/core/controls/url.coffee +0 -88
- package/src/core/controls/video.coffee +0 -9
- package/src/core/embed/api.js +0 -178
- package/src/core/embed/controller.js +0 -331
- package/src/core/models/composition.coffee +0 -178
- package/src/core/models/content.coffee +0 -79
- package/src/core/models/skin.coffee +0 -91
- package/src/core/models/user.coffee +0 -6
- package/src/core/models/widget.coffee +0 -88
- package/src/core/router/index.coffee +0 -44
- package/src/core/services/container/index.js +0 -18
- package/src/core/services/container/instance.js +0 -3
- package/src/core/services/content.js +0 -80
- package/src/core/services/instagram.coffee +0 -47
- package/src/core/services/mixin/events.js +0 -17
- package/src/core/services/service.js +0 -1
- package/src/core/services/sounds.js +0 -81
- package/src/core/services/uploads.js +0 -299
- package/src/core/utils/popup.coffee +0 -67
- package/src/core/utils/should-show.coffee +0 -12
- package/src/core/views/audio-details.pug +0 -9
- package/src/core/views/audio.pug +0 -7
- package/src/core/views/browser.pug +0 -4
- package/src/core/views/color.pug +0 -8
- package/src/core/views/date-time/calendar.pug +0 -3
- package/src/core/views/date-time/clock.pug +0 -12
- package/src/core/views/date-time/grid.pug +0 -6
- package/src/core/views/date-time/input.pug +0 -4
- package/src/core/views/date-time/table.pug +0 -6
- package/src/core/views/dropdown.pug +0 -9
- package/src/core/views/font/font-item.pug +0 -3
- package/src/core/views/font/fonts-list.pug +0 -7
- package/src/core/views/font/fonts-popup.pug +0 -5
- package/src/core/views/font/index.pug +0 -7
- package/src/core/views/image-details.pug +0 -6
- package/src/core/views/image.pug +0 -5
- package/src/core/views/input-layout.pug +0 -15
- package/src/core/views/order-items.pug +0 -7
- package/src/core/views/order-picker.pug +0 -7
- package/src/core/views/pixelpanel.pug +0 -69
- package/src/core/views/position.pug +0 -10
- package/src/core/views/range.pug +0 -9
- package/src/core/views/scale.pug +0 -7
- package/src/core/views/slider.pug +0 -6
- package/src/core/views/stepper.pug +0 -6
- package/src/core/views/text.pug +0 -4
- package/src/core/views/textarea.pug +0 -8
- package/src/core/views/toggle.pug +0 -5
- package/src/core/views/video-details.pug +0 -7
- package/src/core/views/video.pug +0 -4
- package/src/core/widget/index.coffee +0 -66
- package/src/editor/content/browser/dropzone.html +0 -268
- package/src/editor/content/browser/file.html +0 -124
- package/src/editor/content/browser/scroll-pane.html +0 -32
- package/src/editor/content/browser/service.html +0 -53
- package/src/editor/content/browser/upload.coffee +0 -44
- package/src/editor/content/browser/upload.html +0 -439
- package/src/editor/content/index.coffee +0 -294
- package/src/editor/content/input/field.coffee +0 -45
- package/src/editor/content/input/index.coffee +0 -115
- package/src/editor/content/service/base/photoitem.coffee +0 -49
- package/src/editor/content/service/cache.coffee +0 -20
- package/src/editor/content/service/default.coffee +0 -59
- package/src/editor/content/service/dropbox.coffee +0 -71
- package/src/editor/content/service/facebook.coffee +0 -143
- package/src/editor/content/service/flickr.coffee +0 -85
- package/src/editor/content/service/google-drive-sdk.coffee +0 -18
- package/src/editor/content/service/google-drive.coffee +0 -144
- package/src/editor/content/service/instagram-social.coffee +0 -84
- package/src/editor/content/service/instagram.coffee +0 -168
- package/src/editor/content/service/share-popup.coffee +0 -15
- package/src/editor/content/service/soundcloud.coffee +0 -163
- package/src/editor/content/tab.coffee +0 -27
- package/src/editor/details/bulk-editor/attributes-panel.coffee +0 -11
- package/src/editor/details/bulk-editor/index.coffee +0 -86
- package/src/editor/details/index.coffee +0 -156
- package/src/editor/details/item/attributes.coffee +0 -45
- package/src/editor/details/item/index.coffee +0 -82
- package/src/editor/editor.coffee +0 -274
- package/src/editor/footer/breadcrumb.coffee +0 -17
- package/src/editor/footer/index.coffee +0 -36
- package/src/editor/index.coffee +0 -79
- package/src/editor/listeners/embed-listener.coffee +0 -143
- package/src/editor/login-popup.coffee +0 -29
- package/src/editor/notification-controller.coffee +0 -35
- package/src/editor/popup.coffee +0 -68
- package/src/editor/remote-preview.coffee +0 -68
- package/src/editor/save/index.coffee +0 -62
- package/src/editor/skin/delete.coffee +0 -33
- package/src/editor/skin/editor/index.coffee +0 -188
- package/src/editor/skin/editor/panel.coffee +0 -37
- package/src/editor/skin/index.coffee +0 -126
- package/src/editor/skin/save.coffee +0 -58
- package/src/editor/skin/selector/index.coffee +0 -59
- package/src/editor/skin/selector/item.coffee +0 -123
- package/src/editor/skin/selector/list.coffee +0 -97
- package/src/editor/views/embed.pug +0 -10
- package/src/editor/views/loginbutton.pug +0 -8
- package/src/editor/views/popup.pug +0 -17
- package/src/editor/views/presets.pug +0 -8
- package/src/editor/views/presetslist-header.pug +0 -8
- package/src/editor/views/skinitem.pug +0 -13
- package/src/modules/control.js +0 -3
- package/src/modules/controls.js +0 -29
- package/src/modules/core.js +0 -19
- package/src/modules/editor.js +0 -80
- package/test/config.js +0 -3
- package/test/core/models/skin-spec.js +0 -209
- package/webpack.config.js +0 -46
- package/webpack.config.prod.js +0 -9
- /package/{src/core/views/date-time/popup.pug → dist/utils/passiveEvents.d.ts} +0 -0
|
@@ -0,0 +1,2062 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* FontInputController Component
|
|
3
|
+
* A dual-input controller for font family and weight selection with Google Fonts integration.
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - Font family selection with preview
|
|
7
|
+
* - Font weight selection based on available variants
|
|
8
|
+
* - Real-time search with highlighting
|
|
9
|
+
* - Category filtering (Serif, Sans Serif, Script, Other)
|
|
10
|
+
* - Recently used fonts tracking (up to 3 fonts)
|
|
11
|
+
* - Performance-optimized font loading
|
|
12
|
+
* - Custom font support via file browser
|
|
13
|
+
*
|
|
14
|
+
* Performance optimizations:
|
|
15
|
+
* - Lazy loads fonts only when visible in viewport
|
|
16
|
+
* - Delays font loading until scrolling stops (1 second delay)
|
|
17
|
+
* - Preloads default font on mount
|
|
18
|
+
* - Uses font-display: swap for better loading UX
|
|
19
|
+
-->
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
// Svelte imports
|
|
23
|
+
import { onMount, onDestroy, tick } from 'svelte';
|
|
24
|
+
const browser = typeof window !== 'undefined';
|
|
25
|
+
|
|
26
|
+
// Shadcn Compound Components
|
|
27
|
+
import { Popover, Command, EmptyState } from '@widgetic/design-system/components';
|
|
28
|
+
|
|
29
|
+
// Tabler Icons
|
|
30
|
+
import { Check, IconCloudUpload, IconCaretDownFilled, IconArrowDown, IconArrowUp } from '@widgetic/design-system/icons';
|
|
31
|
+
|
|
32
|
+
// Asset imports
|
|
33
|
+
import icTrash from '../../assets/icons/trash.svg?url';
|
|
34
|
+
import fontsJsonUrl from '../../assets/fonts.json?url';
|
|
35
|
+
|
|
36
|
+
// Config imports
|
|
37
|
+
import { config as appConfig } from '../../config';
|
|
38
|
+
import {
|
|
39
|
+
fontUtils,
|
|
40
|
+
fontCategories,
|
|
41
|
+
type FontItem,
|
|
42
|
+
type RecentFont,
|
|
43
|
+
recentFontsStore
|
|
44
|
+
} from '../../utils/fontUtils';
|
|
45
|
+
import type { FontData } from '../../utils/FontManager';
|
|
46
|
+
import FontManager, { mapGoogleVariantIdsToWeightNames } from '../../utils/FontManager';
|
|
47
|
+
|
|
48
|
+
import { shouldSkipFont } from '../../utils/skipFonts';
|
|
49
|
+
|
|
50
|
+
import { InputControllerHeader } from './index';
|
|
51
|
+
|
|
52
|
+
// Import shared components
|
|
53
|
+
import { SearchInput } from '@widgetic/design-system/components';
|
|
54
|
+
import CategoryTabs from './shared/CategoryTabs.svelte';
|
|
55
|
+
import { RecentItemsSection, ItemGrid } from './shared';
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
const FONT_FILTER_KEY = (id: string) => `font-specific-filter-state-${id}`;
|
|
59
|
+
const FONT_WEIGHT_KEY = (id: string) => `font-weight-state-${id}`;
|
|
60
|
+
const MAX_RECENT_FONTS = 3;
|
|
61
|
+
|
|
62
|
+
// Then continue with the rest of the declarations
|
|
63
|
+
type FilterType = typeof fontCategories.types[number];
|
|
64
|
+
let currentFilter: FilterType = 'All'; // Default to 'All'
|
|
65
|
+
let lastActiveFilter: FilterType = 'All'; // Add this declaration
|
|
66
|
+
let filterOptions: FilterType[] = [...fontCategories.types];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Component Props
|
|
70
|
+
* @prop {string} label - Display label for the font selector
|
|
71
|
+
* @prop {string} value - Currently selected font family
|
|
72
|
+
* @prop {Function} onChange - Callback when font selection changes
|
|
73
|
+
* @prop {InputControllerConfig} config - Configuration options
|
|
74
|
+
* @prop {string} description - Optional help text
|
|
75
|
+
* @prop {string} controllerId - Unique identifier for this controller
|
|
76
|
+
* @prop {any} openDropdowns - Store tracking open dropdown states
|
|
77
|
+
* @prop {Function} toggleDropdown - Function to toggle dropdown state
|
|
78
|
+
* @prop {Function} onFocus - Optional focus handler
|
|
79
|
+
*/
|
|
80
|
+
export let label: string;
|
|
81
|
+
export let value: { family: string; weight: string; category: string } | null = null;
|
|
82
|
+
export let onChange: ((newValue: { family: string; weight: string; category: string }) => void) | undefined = undefined;
|
|
83
|
+
export let description: string | undefined = undefined;
|
|
84
|
+
export let controllerId: string;
|
|
85
|
+
export let openDropdowns: any;
|
|
86
|
+
export let toggleDropdown: (id: string) => void;
|
|
87
|
+
|
|
88
|
+
// Initialize timeouts
|
|
89
|
+
let scrollTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
// Font weight state - remove localStorage check on init
|
|
93
|
+
let selectedWeight = `${controllerId}-Regular`;
|
|
94
|
+
let availableWeights: string[] = [];
|
|
95
|
+
|
|
96
|
+
// Font search and filter state
|
|
97
|
+
let allFonts: FontItem[] = [];
|
|
98
|
+
let recentFonts: FontItem[] = [];
|
|
99
|
+
let filteredFonts: FontData[] = [];
|
|
100
|
+
let searchTerm = '';
|
|
101
|
+
let initialFontNumber = FontManager.initialFontNumber;
|
|
102
|
+
const SEARCH_BATCH_SIZE = FontManager.BATCH_SIZE;
|
|
103
|
+
|
|
104
|
+
// Font loading optimization
|
|
105
|
+
let fontListContainer: HTMLElement;
|
|
106
|
+
let fontObserver = new IntersectionObserver(() => {});
|
|
107
|
+
|
|
108
|
+
// Initialize FontManager instance
|
|
109
|
+
let fontManagerInstance: FontManager;
|
|
110
|
+
let buttonWidth = 0;
|
|
111
|
+
|
|
112
|
+
// Add these variables for search
|
|
113
|
+
let activeSearchIndex = -1;
|
|
114
|
+
|
|
115
|
+
// Add loading state
|
|
116
|
+
let isLoadingBatch = false;
|
|
117
|
+
let isInitialLoading = true; // Start with loading state true
|
|
118
|
+
|
|
119
|
+
// Add new prop for controlling font list behavior
|
|
120
|
+
export let hideRecentFromList: boolean = false;
|
|
121
|
+
export let hideTrashIcon: boolean = true;
|
|
122
|
+
|
|
123
|
+
// Add scroll state tracking
|
|
124
|
+
let isScrolled = false;
|
|
125
|
+
|
|
126
|
+
// Add a timeout to track long-running initial loads
|
|
127
|
+
let initialLoadTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
128
|
+
|
|
129
|
+
// Function to update scroll state
|
|
130
|
+
function updateScrollState() {
|
|
131
|
+
if (fontListContainer) {
|
|
132
|
+
const wasScrolled = isScrolled;
|
|
133
|
+
isScrolled = fontListContainer.scrollTop > 10;
|
|
134
|
+
|
|
135
|
+
// If we're transitioning from scrolled to not scrolled, remove transition
|
|
136
|
+
if (wasScrolled && !isScrolled) {
|
|
137
|
+
const stickyHeader = document.querySelector(`#font-header-${controllerId}`);
|
|
138
|
+
if (stickyHeader) {
|
|
139
|
+
stickyHeader.classList.remove('transition-border');
|
|
140
|
+
// Force a reflow to ensure the transition class removal takes effect
|
|
141
|
+
void (stickyHeader as HTMLElement).offsetWidth;
|
|
142
|
+
}
|
|
143
|
+
} else if (!wasScrolled && isScrolled) {
|
|
144
|
+
// Add transition class when starting to scroll
|
|
145
|
+
const stickyHeader = document.querySelector(`#font-header-${controllerId}`);
|
|
146
|
+
if (stickyHeader) {
|
|
147
|
+
stickyHeader.classList.add('transition-border');
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Subscribe to store changes
|
|
154
|
+
$: {
|
|
155
|
+
recentFonts = $recentFontsStore
|
|
156
|
+
.filter(font => !shouldSkipFont(font.family))
|
|
157
|
+
.map(font => ({
|
|
158
|
+
family: font.family,
|
|
159
|
+
category: normalizeCategory(font.category) as FontItem['category'],
|
|
160
|
+
isCustom: font.isCustom
|
|
161
|
+
}));
|
|
162
|
+
|
|
163
|
+
// Update filtered fonts when recent fonts change
|
|
164
|
+
if (hideRecentFromList) {
|
|
165
|
+
updateFilteredFonts();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Add debounce function at the top of the script section
|
|
170
|
+
function debounce<T extends (...args: any[]) => Promise<void>>(
|
|
171
|
+
func: T,
|
|
172
|
+
wait: number
|
|
173
|
+
): (...args: Parameters<T>) => void {
|
|
174
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
175
|
+
return function(this: any, ...args: Parameters<T>): void {
|
|
176
|
+
const context = this;
|
|
177
|
+
if (timeout) clearTimeout(timeout);
|
|
178
|
+
timeout = setTimeout(() => {
|
|
179
|
+
timeout = null;
|
|
180
|
+
func.apply(context, args);
|
|
181
|
+
}, wait);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Add a variable to store the debounced search function
|
|
186
|
+
let debouncedSearch: (event: Event) => void;
|
|
187
|
+
|
|
188
|
+
onMount(async () => {
|
|
189
|
+
try {
|
|
190
|
+
if (browser) {
|
|
191
|
+
// If we have an initial value, use its weight
|
|
192
|
+
if (value?.weight) {
|
|
193
|
+
selectedWeight = `${controllerId}-${value.weight}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Load saved filter
|
|
197
|
+
const savedFilter = localStorage.getItem(FONT_FILTER_KEY(controllerId));
|
|
198
|
+
if (savedFilter) {
|
|
199
|
+
currentFilter = savedFilter as FilterType;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Load saved weight
|
|
203
|
+
const savedWeight = localStorage.getItem(FONT_WEIGHT_KEY(controllerId));
|
|
204
|
+
if (savedWeight) {
|
|
205
|
+
selectedWeight = savedWeight;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// console.log('🚀 [FontInput] Initializing controller:', {
|
|
210
|
+
// controllerId,
|
|
211
|
+
// initialFilter: currentFilter
|
|
212
|
+
// });
|
|
213
|
+
|
|
214
|
+
// Initialize the debounced search function early
|
|
215
|
+
debouncedSearch = debounce(async (event: Event) => {
|
|
216
|
+
await handleSearch(event);
|
|
217
|
+
}, 300); // 300ms debounce time
|
|
218
|
+
|
|
219
|
+
// Start font manager initialization in parallel
|
|
220
|
+
const fontManagerPromise = FontManager.getInstance(appConfig.GOOGLE_FONTS_API_KEY);
|
|
221
|
+
|
|
222
|
+
// Load and verify recent fonts while waiting for font manager
|
|
223
|
+
const loadedRecentFonts = fontUtils.getRecentFonts();
|
|
224
|
+
// console.log('📝 [FontInput] Loading recent fonts:', loadedRecentFonts);
|
|
225
|
+
|
|
226
|
+
// Filter and validate recent fonts
|
|
227
|
+
const validRecentFonts = loadedRecentFonts
|
|
228
|
+
.filter(font => !shouldSkipFont(font.family))
|
|
229
|
+
.slice(0, MAX_RECENT_FONTS);
|
|
230
|
+
|
|
231
|
+
// Update store with validated fonts
|
|
232
|
+
recentFontsStore.set(validRecentFonts);
|
|
233
|
+
|
|
234
|
+
// Update local state
|
|
235
|
+
recentFonts = validRecentFonts.map(font => ({
|
|
236
|
+
family: font.family,
|
|
237
|
+
category: normalizeCategory(font.category) as FontItem['category'],
|
|
238
|
+
isCustom: font.isCustom
|
|
239
|
+
}));
|
|
240
|
+
|
|
241
|
+
// console.log('✅ [FontInput] Recent fonts initialized:', {
|
|
242
|
+
// loaded: loadedRecentFonts.length,
|
|
243
|
+
// valid: validRecentFonts.length,
|
|
244
|
+
// fonts: validRecentFonts.map(f => f.family)
|
|
245
|
+
// });
|
|
246
|
+
|
|
247
|
+
// Now wait for font manager to be ready
|
|
248
|
+
fontManagerInstance = await fontManagerPromise;
|
|
249
|
+
|
|
250
|
+
// Try to get preloaded fonts immediately
|
|
251
|
+
let preloadedFonts: FontData[] = [];
|
|
252
|
+
|
|
253
|
+
// Check if we can get preloaded fonts for the current category
|
|
254
|
+
if (currentFilter !== 'All' && currentFilter !== 'Mine') {
|
|
255
|
+
// console.log(`🔍 [FontInput:${controllerId}] Getting preloaded fonts for category:`, currentFilter);
|
|
256
|
+
preloadedFonts = fontManagerInstance.getPreloadedFonts(currentFilter as FontItem['category']);
|
|
257
|
+
|
|
258
|
+
if (preloadedFonts.length > 0) {
|
|
259
|
+
// console.log(`✅ [FontInput:${controllerId}] Using preloaded fonts for initial display:`, {
|
|
260
|
+
// category: currentFilter,
|
|
261
|
+
// count: preloadedFonts.length,
|
|
262
|
+
// fonts: preloadedFonts.slice(0, 3).map(f => f.family)
|
|
263
|
+
// });
|
|
264
|
+
|
|
265
|
+
// Filter out fonts that should be skipped
|
|
266
|
+
const filteredPreloadedFonts = preloadedFonts.filter(font => !shouldSkipFont(font.family));
|
|
267
|
+
|
|
268
|
+
// Update allFonts with preloaded fonts
|
|
269
|
+
allFonts = filteredPreloadedFonts;
|
|
270
|
+
|
|
271
|
+
// Update filtered fonts immediately
|
|
272
|
+
updateFilteredFonts();
|
|
273
|
+
|
|
274
|
+
// Only set loading to false if we have fonts to display
|
|
275
|
+
if (filteredFonts.length > 0) {
|
|
276
|
+
isInitialLoading = false;
|
|
277
|
+
// console.log(`✅ [FontInput:${controllerId}] Initial loading complete with preloaded fonts:`, {
|
|
278
|
+
// filteredFonts: filteredFonts.length,
|
|
279
|
+
// category: currentFilter
|
|
280
|
+
// });
|
|
281
|
+
|
|
282
|
+
// Clear the timeout since we've loaded successfully
|
|
283
|
+
if (initialLoadTimeout) {
|
|
284
|
+
clearTimeout(initialLoadTimeout);
|
|
285
|
+
initialLoadTimeout = null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
// console.log(`⚠️ [FontInput:${controllerId}] No preloaded fonts available for category:`, currentFilter);
|
|
290
|
+
}
|
|
291
|
+
} else if (currentFilter === 'All') {
|
|
292
|
+
// console.log(`🔍 [FontInput:${controllerId}] Getting initial batch for All category`);
|
|
293
|
+
// For All category, get the first batch
|
|
294
|
+
preloadedFonts = fontManagerInstance.getNextBatch(controllerId, 'All');
|
|
295
|
+
|
|
296
|
+
if (preloadedFonts.length > 0) {
|
|
297
|
+
// console.log(`✅ [FontInput:${controllerId}] Using initial batch for All category:`, {
|
|
298
|
+
// count: preloadedFonts.length,
|
|
299
|
+
// fonts: preloadedFonts.slice(0, 3).map(f => f.family)
|
|
300
|
+
// });
|
|
301
|
+
|
|
302
|
+
// Filter out fonts that should be skipped
|
|
303
|
+
const filteredPreloadedFonts = preloadedFonts.filter(font => !shouldSkipFont(font.family));
|
|
304
|
+
|
|
305
|
+
// Update allFonts with preloaded fonts
|
|
306
|
+
allFonts = filteredPreloadedFonts;
|
|
307
|
+
|
|
308
|
+
// Update filtered fonts immediately
|
|
309
|
+
updateFilteredFonts();
|
|
310
|
+
|
|
311
|
+
// Only set loading to false if we have fonts to display
|
|
312
|
+
if (filteredFonts.length > 0) {
|
|
313
|
+
isInitialLoading = false;
|
|
314
|
+
// console.log(`✅ [FontInput:${controllerId}] Initial loading complete for All category:`, {
|
|
315
|
+
// filteredFonts: filteredFonts.length
|
|
316
|
+
// });
|
|
317
|
+
|
|
318
|
+
// Clear the timeout since we've loaded successfully
|
|
319
|
+
if (initialLoadTimeout) {
|
|
320
|
+
clearTimeout(initialLoadTimeout);
|
|
321
|
+
initialLoadTimeout = null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} else if (currentFilter === 'Mine') {
|
|
326
|
+
// console.log(`🔍 [FontInput:${controllerId}] Getting custom fonts for Mine category`);
|
|
327
|
+
// For Mine category, first check if we already have preloaded fonts
|
|
328
|
+
preloadedFonts = fontManagerInstance.getPreloadedFonts('Mine');
|
|
329
|
+
|
|
330
|
+
if (preloadedFonts.length > 0) {
|
|
331
|
+
console.log(`✅ [FontInput:${controllerId}] Using preloaded custom fonts for Mine category:`, {
|
|
332
|
+
count: preloadedFonts.length,
|
|
333
|
+
fonts: preloadedFonts.slice(0, 3).map(f => f.family)
|
|
334
|
+
});
|
|
335
|
+
} else {
|
|
336
|
+
// If no preloaded fonts found, load them now
|
|
337
|
+
console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
|
|
338
|
+
preloadedFonts = await fontManagerInstance.loadCustomFonts();
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if (preloadedFonts.length > 0) {
|
|
342
|
+
// Update allFonts with custom fonts
|
|
343
|
+
allFonts = preloadedFonts;
|
|
344
|
+
|
|
345
|
+
// Update filtered fonts immediately
|
|
346
|
+
updateFilteredFonts();
|
|
347
|
+
|
|
348
|
+
// Only set loading to false if we have fonts to display
|
|
349
|
+
if (filteredFonts.length > 0) {
|
|
350
|
+
isInitialLoading = false;
|
|
351
|
+
// console.log(`✅ [FontInput:${controllerId}] Initial loading complete for Mine category:`, {
|
|
352
|
+
// filteredFonts: filteredFonts.length
|
|
353
|
+
// });
|
|
354
|
+
|
|
355
|
+
// Clear the timeout since we've loaded successfully
|
|
356
|
+
if (initialLoadTimeout) {
|
|
357
|
+
clearTimeout(initialLoadTimeout);
|
|
358
|
+
initialLoadTimeout = null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Only set loading state if we couldn't get preloaded fonts
|
|
365
|
+
if (preloadedFonts.length === 0) {
|
|
366
|
+
// console.log(`⚠️ [FontInput:${controllerId}] No preloaded fonts available, will load asynchronously`);
|
|
367
|
+
// Keep isInitialLoading as true
|
|
368
|
+
|
|
369
|
+
// Set a timeout to show loading indicator if initialization takes too long
|
|
370
|
+
initialLoadTimeout = setTimeout(() => {
|
|
371
|
+
// If we're still loading after 1 second, show the loading indicator
|
|
372
|
+
if (isInitialLoading) {
|
|
373
|
+
// console.log(`⚠️ [FontInput:${controllerId}] Initial font loading taking longer than expected`);
|
|
374
|
+
}
|
|
375
|
+
}, 1000); // Reduced from 2000ms to 1000ms
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Initialize fonts list with saved filter - use a separate async function
|
|
379
|
+
// to avoid blocking the UI
|
|
380
|
+
const initializeFontsAsync = async () => {
|
|
381
|
+
try {
|
|
382
|
+
// console.log(`🔄 [FontInput:${controllerId}] Starting font initialization for controller:`, controllerId);
|
|
383
|
+
|
|
384
|
+
// First, ensure the FontManager is fully initialized
|
|
385
|
+
if (!FontManager.isInitialized) {
|
|
386
|
+
// console.log(`⏳ [FontInput:${controllerId}] Waiting for FontManager to initialize...`);
|
|
387
|
+
// This will wait for the initialization promise to resolve
|
|
388
|
+
await FontManager.getInstance(appConfig.GOOGLE_FONTS_API_KEY);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Skip loading if we already have preloaded fonts
|
|
392
|
+
if (allFonts.length === 0) {
|
|
393
|
+
// console.log(`🔍 [FontInput:${controllerId}] No fonts loaded yet, loading initial batch for ${currentFilter}`);
|
|
394
|
+
// Instead of loading all fonts, just get the first batch
|
|
395
|
+
let initialFonts: FontData[] = [];
|
|
396
|
+
|
|
397
|
+
if (currentFilter === 'Mine') {
|
|
398
|
+
// For Mine category, first check if we have preloaded fonts
|
|
399
|
+
initialFonts = fontManagerInstance.getPreloadedFonts('Mine');
|
|
400
|
+
|
|
401
|
+
if (initialFonts.length > 0) {
|
|
402
|
+
console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts:`, {
|
|
403
|
+
count: initialFonts.length,
|
|
404
|
+
fonts: initialFonts.slice(0, 3).map(f => f.family)
|
|
405
|
+
});
|
|
406
|
+
} else {
|
|
407
|
+
// If no preloaded fonts, load them now
|
|
408
|
+
console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
|
|
409
|
+
initialFonts = await fontManagerInstance.loadCustomFonts();
|
|
410
|
+
}
|
|
411
|
+
} else if (currentFilter === 'All') {
|
|
412
|
+
// For All category, get the first batch
|
|
413
|
+
initialFonts = fontManagerInstance.getNextBatch(controllerId, 'All');
|
|
414
|
+
// console.log(`📚 [FontInput:${controllerId}] Loaded initial batch for All:`, {
|
|
415
|
+
// count: initialFonts.length,
|
|
416
|
+
// fonts: initialFonts.slice(0, 3).map(f => f.family)
|
|
417
|
+
// });
|
|
418
|
+
} else {
|
|
419
|
+
// For specific categories, first try to get preloaded fonts
|
|
420
|
+
initialFonts = fontManagerInstance.getPreloadedFonts(currentFilter as FontItem['category']);
|
|
421
|
+
|
|
422
|
+
if (initialFonts.length === 0) {
|
|
423
|
+
// If no preloaded fonts, get the first batch filtered by category
|
|
424
|
+
// console.log(`📚 [FontInput:${controllerId}] No preloaded fonts for ${currentFilter}, getting batch`);
|
|
425
|
+
initialFonts = fontManagerInstance.getNextBatch(controllerId, currentFilter);
|
|
426
|
+
} else {
|
|
427
|
+
// console.log(`📚 [FontInput:${controllerId}] Using preloaded fonts for ${currentFilter}:`, {
|
|
428
|
+
// count: initialFonts.length,
|
|
429
|
+
// fonts: initialFonts.slice(0, 3).map(f => f.family)
|
|
430
|
+
// });
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Filter out fonts that should be skipped
|
|
435
|
+
const filteredFonts = initialFonts.filter(font => !shouldSkipFont(font.family));
|
|
436
|
+
// console.log(`🔍 [FontInput:${controllerId}] Filtered initial fonts:`, {
|
|
437
|
+
// before: initialFonts.length,
|
|
438
|
+
// after: filteredFonts.length,
|
|
439
|
+
// skipped: initialFonts.length - filteredFonts.length
|
|
440
|
+
// });
|
|
441
|
+
|
|
442
|
+
// Update allFonts with just the initial batch
|
|
443
|
+
allFonts = filteredFonts;
|
|
444
|
+
|
|
445
|
+
// Update filtered fonts immediately
|
|
446
|
+
updateFilteredFonts();
|
|
447
|
+
} else {
|
|
448
|
+
// console.log(`✅ [FontInput:${controllerId}] Already have fonts loaded:`, {
|
|
449
|
+
// count: allFonts.length,
|
|
450
|
+
// category: currentFilter
|
|
451
|
+
// });
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Set loading to false after fonts are loaded
|
|
455
|
+
// Only set loading to false if we have fonts to display
|
|
456
|
+
if (filteredFonts.length > 0) {
|
|
457
|
+
isInitialLoading = false;
|
|
458
|
+
// console.log(`✅ [FontInput:${controllerId}] Initialization complete with fonts:`, {
|
|
459
|
+
// count: filteredFonts.length,
|
|
460
|
+
// category: currentFilter
|
|
461
|
+
// });
|
|
462
|
+
} else {
|
|
463
|
+
// console.log(`⚠️ [FontInput:${controllerId}] No fonts found after initialization`);
|
|
464
|
+
// Still set loading to false to show empty state
|
|
465
|
+
isInitialLoading = false;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Clear the timeout since we've loaded successfully
|
|
469
|
+
if (initialLoadTimeout) {
|
|
470
|
+
clearTimeout(initialLoadTimeout);
|
|
471
|
+
initialLoadTimeout = null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// console.log(`✅ [FontInput:${controllerId}] Controller initialized with fonts:`, {
|
|
475
|
+
// controllerId,
|
|
476
|
+
// allFonts: allFonts.length,
|
|
477
|
+
// filteredFonts: filteredFonts.length,
|
|
478
|
+
// recentFonts: recentFonts.length,
|
|
479
|
+
// currentFilter,
|
|
480
|
+
// customFonts: allFonts.filter(f => f.category === 'Mine').length
|
|
481
|
+
// });
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error(`❌ [FontInput:${controllerId}] Error during async font initialization:`, error);
|
|
484
|
+
isInitialLoading = false; // Make sure to set loading to false even on error
|
|
485
|
+
|
|
486
|
+
// Clear the timeout on error
|
|
487
|
+
if (initialLoadTimeout) {
|
|
488
|
+
clearTimeout(initialLoadTimeout);
|
|
489
|
+
initialLoadTimeout = null;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// Start the async initialization without waiting for it
|
|
495
|
+
initializeFontsAsync();
|
|
496
|
+
|
|
497
|
+
} catch (error) {
|
|
498
|
+
console.error('❌ [FontInput] Error during initialization:', error);
|
|
499
|
+
isInitialLoading = false; // Make sure to set loading to false even on error
|
|
500
|
+
|
|
501
|
+
// Clear the timeout on error
|
|
502
|
+
if (initialLoadTimeout) {
|
|
503
|
+
clearTimeout(initialLoadTimeout);
|
|
504
|
+
initialLoadTimeout = null;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Initialize fonts based on the current filter
|
|
511
|
+
*/
|
|
512
|
+
async function initializeFonts() {
|
|
513
|
+
if (currentFilter === 'Mine') {
|
|
514
|
+
// First check if we have preloaded fonts
|
|
515
|
+
const preloadedCustomFonts = fontManagerInstance.getPreloadedFonts('Mine');
|
|
516
|
+
|
|
517
|
+
if (preloadedCustomFonts.length > 0) {
|
|
518
|
+
console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts in initializeFonts:`, {
|
|
519
|
+
count: preloadedCustomFonts.length,
|
|
520
|
+
fonts: preloadedCustomFonts.slice(0, 3).map(f => f.family)
|
|
521
|
+
});
|
|
522
|
+
return preloadedCustomFonts;
|
|
523
|
+
} else {
|
|
524
|
+
// If no preloaded fonts, load them now
|
|
525
|
+
console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found in initializeFonts, loading them now...`);
|
|
526
|
+
const customFonts = await fontManagerInstance.loadCustomFonts();
|
|
527
|
+
return customFonts;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// For All category, load Google Fonts first, then get custom fonts (already preloaded or load them)
|
|
532
|
+
const googleFonts = await fontManagerInstance.getInitialFontList();
|
|
533
|
+
|
|
534
|
+
// First check if we have preloaded custom fonts
|
|
535
|
+
let customFonts = fontManagerInstance.getPreloadedFonts('Mine');
|
|
536
|
+
|
|
537
|
+
if (customFonts.length === 0) {
|
|
538
|
+
// If no preloaded custom fonts, load them now
|
|
539
|
+
customFonts = await fontManagerInstance.loadCustomFonts();
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (currentFilter === 'All') {
|
|
543
|
+
return [
|
|
544
|
+
...googleFonts.filter(font => !font.isCustom),
|
|
545
|
+
...customFonts
|
|
546
|
+
];
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// For other categories, filter out custom fonts
|
|
550
|
+
return googleFonts.filter(font => !font.isCustom);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Fetches available fonts from Google Fonts API
|
|
555
|
+
*/
|
|
556
|
+
async function fetchGoogleFonts() {
|
|
557
|
+
try {
|
|
558
|
+
// console.log('🔍 [FontInput] Fetching Google Fonts...');
|
|
559
|
+
fontManagerInstance = await FontManager.getInstance(appConfig.GOOGLE_FONTS_API_KEY);
|
|
560
|
+
allFonts = await fontManagerInstance.getCompleteFontList();
|
|
561
|
+
updateFilteredFonts();
|
|
562
|
+
|
|
563
|
+
// console.log('✅ [FontInput] Fonts available:', allFonts.length);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
console.error('❌ [FontInput] Error getting Google Fonts:', error);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Opens the file browser for font selection
|
|
571
|
+
* Receives font files in the format matching fonts
|
|
572
|
+
*/
|
|
573
|
+
async function openFileBrowser() {
|
|
574
|
+
|
|
575
|
+
// console.log('🔍 [FontInput] Opening file browser for fonts:');
|
|
576
|
+
|
|
577
|
+
// try {
|
|
578
|
+
// // Get the current font's ID if it exists in our custom fonts
|
|
579
|
+
// const selectedId = value?.family ?
|
|
580
|
+
// fontManagerInstance?.getCachedFontList()
|
|
581
|
+
// .find(f => f.family === value.family && f.category === 'Mine')?.id
|
|
582
|
+
// : undefined;
|
|
583
|
+
|
|
584
|
+
// console.log('🔍 [FontInput] Opening file browser for fonts:', {
|
|
585
|
+
// fileTypes: ['font'],
|
|
586
|
+
// selectedId
|
|
587
|
+
// });
|
|
588
|
+
|
|
589
|
+
// const result = await window.uiSDK?.mountComponent('file-browser', {
|
|
590
|
+
// container: '#file-browser',
|
|
591
|
+
// config: {
|
|
592
|
+
// fileTypes: ['font'],
|
|
593
|
+
// selectedId,
|
|
594
|
+
// batch: 1000 // allow for a large number of fonts to be selected
|
|
595
|
+
// },
|
|
596
|
+
// callbacks: {
|
|
597
|
+
// onSelect: async (fontData: any) => {
|
|
598
|
+
// if (!fontData) return;
|
|
599
|
+
|
|
600
|
+
// const fontValue = {
|
|
601
|
+
// family: fontData.family,
|
|
602
|
+
// weight: fontData.weight,
|
|
603
|
+
// category: 'Mine' // Set category to Mine for uploaded fonts
|
|
604
|
+
// };
|
|
605
|
+
|
|
606
|
+
// console.log('✅ [FontInput] Font selected:', {
|
|
607
|
+
// ...fontValue,
|
|
608
|
+
// format: fontData.url.split('.').pop()
|
|
609
|
+
// });
|
|
610
|
+
|
|
611
|
+
// if (onChange) {
|
|
612
|
+
// onChange(fontValue);
|
|
613
|
+
// }
|
|
614
|
+
// },
|
|
615
|
+
// onError: (error: Error) => {
|
|
616
|
+
// console.error('❌ [FontInput] File Browser error:', error);
|
|
617
|
+
// }
|
|
618
|
+
// }
|
|
619
|
+
// });
|
|
620
|
+
|
|
621
|
+
// } catch (error) {
|
|
622
|
+
// console.error('❌ [FontInput] Error opening file browser:', error);
|
|
623
|
+
// }
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* Updates the filtered fonts list based on search term and category
|
|
629
|
+
* Also handles setting up the observer after updating
|
|
630
|
+
*
|
|
631
|
+
* Logic flow:
|
|
632
|
+
* 1. Start with all fonts
|
|
633
|
+
* 2. Apply search filter if term exists
|
|
634
|
+
* 3. Apply category filter if not 'All'
|
|
635
|
+
*/
|
|
636
|
+
function updateFilteredFonts() {
|
|
637
|
+
let fonts = [...allFonts];
|
|
638
|
+
|
|
639
|
+
// Clear both filters when search is active
|
|
640
|
+
if (searchTerm) {
|
|
641
|
+
const term = searchTerm.toLowerCase();
|
|
642
|
+
fonts = fonts.filter(font =>
|
|
643
|
+
font.family.toLowerCase().includes(term)
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
// Force category to All when searching
|
|
647
|
+
currentFilter = 'All';
|
|
648
|
+
} else {
|
|
649
|
+
if (currentFilter !== 'All') {
|
|
650
|
+
fonts = fonts.filter(font => {
|
|
651
|
+
if (currentFilter === 'Mine') return font.category === 'Mine';
|
|
652
|
+
return normalizeCategory(font.category) === currentFilter;
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Optionally filter out recent fonts
|
|
658
|
+
if (hideRecentFromList) {
|
|
659
|
+
const beforeRecent = fonts.length;
|
|
660
|
+
fonts = fonts.filter(font =>
|
|
661
|
+
!recentFonts.some(recent => recent.family === font.family)
|
|
662
|
+
);
|
|
663
|
+
// console.log(`🔍 [FontInput:${controllerId}] Filtered out recent fonts:`, {
|
|
664
|
+
// beforeFilter: beforeRecent,
|
|
665
|
+
// afterFilter: fonts.length,
|
|
666
|
+
// recentFontsRemoved: beforeRecent - fonts.length
|
|
667
|
+
// });
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Update filtered fonts
|
|
671
|
+
filteredFonts = fonts;
|
|
672
|
+
|
|
673
|
+
// console.log(`✅ [FontInput:${controllerId}] Filtered fonts result:`, {
|
|
674
|
+
// total: fonts.length,
|
|
675
|
+
// filter: currentFilter,
|
|
676
|
+
// searchTerm,
|
|
677
|
+
// hideRecentFromList,
|
|
678
|
+
// recentFontsHidden: hideRecentFromList ? recentFonts.length : 0,
|
|
679
|
+
// initialFontNumber
|
|
680
|
+
// });
|
|
681
|
+
|
|
682
|
+
// Setup observer after updating filtered fonts
|
|
683
|
+
// Use a small timeout to ensure the DOM has updated
|
|
684
|
+
if (scrollTimeout) {
|
|
685
|
+
clearTimeout(scrollTimeout);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
scrollTimeout = setTimeout(() => {
|
|
689
|
+
setupFontObserver();
|
|
690
|
+
}, 50);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Handles font selection and updates recent fonts
|
|
695
|
+
*/
|
|
696
|
+
function handleFontSelect(font: FontItem | RecentFont) {
|
|
697
|
+
if (onChange) {
|
|
698
|
+
// Reset weight to Regular or first available weight
|
|
699
|
+
fetchFontWeights(font.family).then(async () => {
|
|
700
|
+
const newWeight = availableWeights.includes('Regular') ?
|
|
701
|
+
'Regular' : availableWeights[0];
|
|
702
|
+
|
|
703
|
+
selectedWeight = `${controllerId}-${newWeight}`;
|
|
704
|
+
if (browser) {
|
|
705
|
+
localStorage.setItem(FONT_WEIGHT_KEY(controllerId), selectedWeight);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Load the full font when selected
|
|
709
|
+
if (!fontManagerInstance?.isCustomFont(font.family)) {
|
|
710
|
+
await fontManagerInstance?.loadFullFont(font.family);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Create the font value object with reset weight
|
|
714
|
+
const fontValue = {
|
|
715
|
+
family: font.family,
|
|
716
|
+
weight: newWeight,
|
|
717
|
+
category: font.category
|
|
718
|
+
};
|
|
719
|
+
|
|
720
|
+
// Pass the font value object to onChange
|
|
721
|
+
onChange(fontValue);
|
|
722
|
+
|
|
723
|
+
// console.log('✅ [FontInput] Font selected:', {
|
|
724
|
+
// font: fontValue
|
|
725
|
+
// });
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Manages real-time search functionality
|
|
732
|
+
* Prioritizes recent fonts in search results
|
|
733
|
+
* Enhanced to better handle partial matches
|
|
734
|
+
*/
|
|
735
|
+
async function handleSearch(event: Event) {
|
|
736
|
+
const input = event.target as HTMLInputElement;
|
|
737
|
+
const newSearchTerm = input.value;
|
|
738
|
+
|
|
739
|
+
// Skip processing if the search term hasn't changed
|
|
740
|
+
if (newSearchTerm === searchTerm) return;
|
|
741
|
+
|
|
742
|
+
// Use debounced version only for typing
|
|
743
|
+
debouncedSearch(event);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Add paste handler
|
|
747
|
+
function handlePaste(event: ClipboardEvent) {
|
|
748
|
+
const pastedText = event.clipboardData?.getData('text/plain') || '';
|
|
749
|
+
|
|
750
|
+
// Don't prevent default - let the paste happen naturally
|
|
751
|
+
// Then trigger an immediate search update with the pasted content
|
|
752
|
+
// searchTerm will be updated through binding
|
|
753
|
+
|
|
754
|
+
// Schedule the handleSearchImmediate for the next tick to ensure
|
|
755
|
+
// the input value is updated with the pasted content first
|
|
756
|
+
setTimeout(() => {
|
|
757
|
+
handleSearchImmediate(pastedText);
|
|
758
|
+
}, 0);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Separate immediate search handler (keep this)
|
|
762
|
+
async function handleSearchImmediate(value: string) {
|
|
763
|
+
// Store current filter before overriding it
|
|
764
|
+
if (!searchTerm && value) {
|
|
765
|
+
lastActiveFilter = currentFilter;
|
|
766
|
+
currentFilter = 'All';
|
|
767
|
+
localStorage.setItem(FONT_FILTER_KEY(controllerId), 'All');
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Apply filtering with the new search term
|
|
771
|
+
updateFilteredFonts();
|
|
772
|
+
|
|
773
|
+
// Reset scroll position if needed
|
|
774
|
+
if (fontListContainer) {
|
|
775
|
+
fontListContainer.scrollTop = 0;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Add a derived store for search results
|
|
780
|
+
let currentSearchResults: FontData[] = [];
|
|
781
|
+
|
|
782
|
+
// Update the reactive statement for search
|
|
783
|
+
$: if (searchTerm) {
|
|
784
|
+
const searchValue = searchTerm.toLowerCase().trim();
|
|
785
|
+
// Only process if the search value is not empty
|
|
786
|
+
if (searchValue) {
|
|
787
|
+
// Get both Google Fonts and custom fonts from cache
|
|
788
|
+
const googleFonts = fontManagerInstance?.getCachedFontList().filter(font => !font.isCustom) || [];
|
|
789
|
+
const customFonts = fontManagerInstance?.getCachedFontList().filter(font => font.isCustom) || [];
|
|
790
|
+
|
|
791
|
+
// Merge both lists
|
|
792
|
+
const allCachedFonts = [...googleFonts, ...customFonts];
|
|
793
|
+
|
|
794
|
+
// Modified search algorithm to use exact matching
|
|
795
|
+
const newSearchResults = allCachedFonts
|
|
796
|
+
.filter(font => {
|
|
797
|
+
const familyLower = font.family.toLowerCase().trim();
|
|
798
|
+
|
|
799
|
+
// Skip fonts that should be excluded
|
|
800
|
+
if (shouldSkipFont(font.family)) {
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Check if the search value is found in the font family
|
|
805
|
+
return familyLower.includes(searchValue);
|
|
806
|
+
})
|
|
807
|
+
.sort((a, b) => {
|
|
808
|
+
const aFamily = a.family.toLowerCase();
|
|
809
|
+
const bFamily = b.family.toLowerCase();
|
|
810
|
+
|
|
811
|
+
// Prioritize fonts that start with the search term
|
|
812
|
+
const aStartsWith = aFamily.startsWith(searchValue);
|
|
813
|
+
const bStartsWith = bFamily.startsWith(searchValue);
|
|
814
|
+
|
|
815
|
+
if (aStartsWith && !bStartsWith) return -1;
|
|
816
|
+
if (!aStartsWith && bStartsWith) return 1;
|
|
817
|
+
|
|
818
|
+
// Then prioritize fonts where search term appears earlier
|
|
819
|
+
const aIndex = aFamily.indexOf(searchValue);
|
|
820
|
+
const bIndex = bFamily.indexOf(searchValue);
|
|
821
|
+
|
|
822
|
+
if (aIndex !== -1 && bIndex !== -1) {
|
|
823
|
+
return aIndex - bIndex;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// Fall back to alphabetical sorting
|
|
827
|
+
return aFamily.localeCompare(bFamily);
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// Only update results if they've changed
|
|
831
|
+
if (JSON.stringify(newSearchResults) !== JSON.stringify(currentSearchResults)) {
|
|
832
|
+
// Store all search results for batching
|
|
833
|
+
currentSearchResults = newSearchResults;
|
|
834
|
+
|
|
835
|
+
// Select first item automatically only if we have results and no item is currently selected
|
|
836
|
+
if (currentSearchResults.length > 0 && activeSearchIndex === -1) {
|
|
837
|
+
activeSearchIndex = 0;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Show more initial results (double the default)
|
|
841
|
+
const initialResultsCount = Math.min(FontManager.initialFontNumber * 2, currentSearchResults.length);
|
|
842
|
+
filteredFonts = currentSearchResults.slice(0, initialResultsCount);
|
|
843
|
+
|
|
844
|
+
// Reset scroll position
|
|
845
|
+
if (fontListContainer) {
|
|
846
|
+
fontListContainer.scrollTop = 0;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Preload only the first batch of fonts
|
|
850
|
+
filteredFonts.forEach(font => {
|
|
851
|
+
fontManagerInstance?.loadFullFont(font.family);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// console.log('📝 [FontSearch] Updated results:', {
|
|
855
|
+
// searchValue,
|
|
856
|
+
// totalMatches: currentSearchResults.length,
|
|
857
|
+
// displayedResults: filteredFonts.length,
|
|
858
|
+
// activeIndex: activeSearchIndex
|
|
859
|
+
// });
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Handle empty search term separately to avoid reactivity loop
|
|
865
|
+
$: if (searchTerm === '') {
|
|
866
|
+
// Only reset if we actually had search results before or if we have no filtered fonts
|
|
867
|
+
if (currentSearchResults.length > 0 || filteredFonts.length === 0) {
|
|
868
|
+
// console.log('🔄 [FontSearch] Handling empty search term');
|
|
869
|
+
|
|
870
|
+
// Reset search state
|
|
871
|
+
currentSearchResults = [];
|
|
872
|
+
activeSearchIndex = -1; // Reset active index
|
|
873
|
+
|
|
874
|
+
// Reset batch index to start fresh
|
|
875
|
+
if (fontManagerInstance) {
|
|
876
|
+
fontManagerInstance.resetBatchIndex(controllerId);
|
|
877
|
+
|
|
878
|
+
// Get fonts for current category
|
|
879
|
+
let fonts: FontData[] = [];
|
|
880
|
+
|
|
881
|
+
if (currentFilter === 'All') {
|
|
882
|
+
// Get initial batch of fonts instead of all fonts
|
|
883
|
+
fonts = fontManagerInstance.getNextBatch(controllerId, currentFilter);
|
|
884
|
+
// Update all fonts
|
|
885
|
+
allFonts = fonts;
|
|
886
|
+
// Update filtered fonts
|
|
887
|
+
filteredFonts = [];
|
|
888
|
+
updateFilteredFonts();
|
|
889
|
+
} else if (currentFilter === 'Mine') {
|
|
890
|
+
// For Mine category, first check if we have preloaded fonts
|
|
891
|
+
const preloadedCustomFonts = fontManagerInstance.getPreloadedFonts('Mine');
|
|
892
|
+
|
|
893
|
+
if (preloadedCustomFonts.length > 0) {
|
|
894
|
+
// Use preloaded custom fonts
|
|
895
|
+
console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts for empty search:`, {
|
|
896
|
+
count: preloadedCustomFonts.length,
|
|
897
|
+
fonts: preloadedCustomFonts.slice(0, 3).map(f => f.family)
|
|
898
|
+
});
|
|
899
|
+
allFonts = preloadedCustomFonts;
|
|
900
|
+
filteredFonts = [];
|
|
901
|
+
updateFilteredFonts();
|
|
902
|
+
} else {
|
|
903
|
+
// If no preloaded fonts, load them asynchronously
|
|
904
|
+
console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found for empty search, loading them now...`);
|
|
905
|
+
fontManagerInstance.loadCustomFonts().then(customFonts => {
|
|
906
|
+
allFonts = customFonts || [];
|
|
907
|
+
filteredFonts = [];
|
|
908
|
+
updateFilteredFonts();
|
|
909
|
+
});
|
|
910
|
+
}
|
|
911
|
+
} else {
|
|
912
|
+
// For other categories (Serif, Sans Serif, etc.), get initial batch by category
|
|
913
|
+
fonts = fontManagerInstance.getNextBatch(controllerId, currentFilter);
|
|
914
|
+
// Update all fonts
|
|
915
|
+
allFonts = fonts;
|
|
916
|
+
// Update filtered fonts
|
|
917
|
+
filteredFonts = [];
|
|
918
|
+
updateFilteredFonts();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Reset scroll position when clearing search (for non-Mine categories)
|
|
923
|
+
if (currentFilter !== 'Mine' && fontListContainer) {
|
|
924
|
+
fontListContainer.scrollTop = 0;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Setup observer in next tick for non-Mine categories
|
|
928
|
+
if (currentFilter !== 'Mine') {
|
|
929
|
+
if (scrollTimeout) {
|
|
930
|
+
clearTimeout(scrollTimeout);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
scrollTimeout = setTimeout(() => {
|
|
934
|
+
if (fontListContainer) {
|
|
935
|
+
setupFontObserver();
|
|
936
|
+
}
|
|
937
|
+
}, 50);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// console.log('🔄 [FontSearch] Reset search state:', {
|
|
941
|
+
// currentFilter,
|
|
942
|
+
// allFonts: allFonts.length,
|
|
943
|
+
// filteredFonts: filteredFonts.length
|
|
944
|
+
// });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Update keyboard navigation to use filteredFonts
|
|
949
|
+
function handleSearchKeydown(event: KeyboardEvent | CustomEvent<KeyboardEvent>) {
|
|
950
|
+
// Ensure we have a KeyboardEvent
|
|
951
|
+
const keyEvent = event instanceof KeyboardEvent ? event : event.detail;
|
|
952
|
+
|
|
953
|
+
// Prevent handling backspace when search is already empty
|
|
954
|
+
if (keyEvent.key === 'Backspace' && !searchTerm) {
|
|
955
|
+
keyEvent.preventDefault();
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (!filteredFonts.length) return;
|
|
960
|
+
|
|
961
|
+
if (keyEvent.key === 'ArrowDown') {
|
|
962
|
+
keyEvent.preventDefault();
|
|
963
|
+
handleArrowClick('down');
|
|
964
|
+
} else if (keyEvent.key === 'ArrowUp') {
|
|
965
|
+
keyEvent.preventDefault();
|
|
966
|
+
handleArrowClick('up');
|
|
967
|
+
} else if (keyEvent.key === 'Enter' && activeSearchIndex !== -1) {
|
|
968
|
+
keyEvent.preventDefault();
|
|
969
|
+
handleFontSelect(filteredFonts[activeSearchIndex]);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
async function handleFilterChange(filter: FilterType) {
|
|
974
|
+
if (currentFilter !== filter) {
|
|
975
|
+
lastActiveFilter = filter; // Store the new filter as last active
|
|
976
|
+
currentFilter = filter;
|
|
977
|
+
|
|
978
|
+
if (browser) {
|
|
979
|
+
localStorage.setItem(FONT_FILTER_KEY(controllerId), filter);
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (fontManagerInstance) {
|
|
983
|
+
fontManagerInstance.resetBatchIndex(controllerId);
|
|
984
|
+
|
|
985
|
+
try {
|
|
986
|
+
// console.log(`🔄 [FontInput:${controllerId}] Category changed to:`, filter);
|
|
987
|
+
let fonts: FontData[] = [];
|
|
988
|
+
|
|
989
|
+
if (filter === 'All') {
|
|
990
|
+
// Get initial batch of fonts instead of all fonts
|
|
991
|
+
fonts = fontManagerInstance.getNextBatch(controllerId, filter);
|
|
992
|
+
// console.log(`📚 [FontInput:${controllerId}] Loaded initial batch for All category:`, {
|
|
993
|
+
// count: fonts.length,
|
|
994
|
+
// fonts: fonts.slice(0, 3).map(f => f.family)
|
|
995
|
+
// });
|
|
996
|
+
} else if (filter === 'Mine') {
|
|
997
|
+
// Check if custom fonts are already loaded by getting them from the FontManager instance
|
|
998
|
+
fonts = fontManagerInstance.getPreloadedFonts('Mine');
|
|
999
|
+
|
|
1000
|
+
if (fonts.length === 0) {
|
|
1001
|
+
// If no preloaded custom fonts found, load them
|
|
1002
|
+
console.log(`📚 [FontInput:${controllerId}] No preloaded custom fonts found, loading them now...`);
|
|
1003
|
+
fonts = await fontManagerInstance.loadCustomFonts();
|
|
1004
|
+
} else {
|
|
1005
|
+
console.log(`📚 [FontInput:${controllerId}] Using preloaded custom fonts:`, {
|
|
1006
|
+
count: fonts.length,
|
|
1007
|
+
fonts: fonts.slice(0, 3).map(f => f.family)
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
} else {
|
|
1011
|
+
// For other categories, first try to get preloaded fonts
|
|
1012
|
+
fonts = fontManagerInstance.getPreloadedFonts(filter as FontItem['category']);
|
|
1013
|
+
|
|
1014
|
+
if (fonts.length > 0) {
|
|
1015
|
+
// console.log(`📚 [FontInput:${controllerId}] Using preloaded fonts for ${filter}:`, {
|
|
1016
|
+
// count: fonts.length,
|
|
1017
|
+
// fonts: fonts.slice(0, 3).map(f => f.family)
|
|
1018
|
+
// });
|
|
1019
|
+
} else {
|
|
1020
|
+
// If no preloaded fonts, get the first batch filtered by category
|
|
1021
|
+
// console.log(`📚 [FontInput:${controllerId}] No preloaded fonts for ${filter}, getting batch`);
|
|
1022
|
+
fonts = fontManagerInstance.getNextBatch(controllerId, filter);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Filter out fonts that should be skipped
|
|
1027
|
+
const filteredFonts = fonts.filter(font => !shouldSkipFont(font.family));
|
|
1028
|
+
// console.log(`🔍 [FontInput:${controllerId}] Filtered fonts after category change:`, {
|
|
1029
|
+
// before: fonts.length,
|
|
1030
|
+
// after: filteredFonts.length,
|
|
1031
|
+
// skipped: fonts.length - filteredFonts.length
|
|
1032
|
+
// });
|
|
1033
|
+
|
|
1034
|
+
allFonts = filteredFonts;
|
|
1035
|
+
updateFilteredFonts();
|
|
1036
|
+
|
|
1037
|
+
// Setup observer in next tick
|
|
1038
|
+
scrollTimeout = setTimeout(() => {
|
|
1039
|
+
if (fontListContainer) {
|
|
1040
|
+
setupFontObserver();
|
|
1041
|
+
}
|
|
1042
|
+
}, 0);
|
|
1043
|
+
|
|
1044
|
+
if (fontListContainer) {
|
|
1045
|
+
fontListContainer.scrollTop = 0;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// console.log(`✅ [FontInput:${controllerId}] Category change complete:`, {
|
|
1049
|
+
// filter,
|
|
1050
|
+
// fonts: allFonts.length,
|
|
1051
|
+
// filteredFonts: filteredFonts.length,
|
|
1052
|
+
// customFonts: allFonts.filter(f => f.isCustom).length
|
|
1053
|
+
// });
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
console.error(`❌ [FontInput:${controllerId}] Error changing category:`, error);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Manages intersection observer for lazy loading fonts
|
|
1062
|
+
* Only loads fonts when they become visible in viewport
|
|
1063
|
+
*/
|
|
1064
|
+
function setupFontObserver() {
|
|
1065
|
+
if (!fontListContainer) {
|
|
1066
|
+
// console.log(`⚠️ [FontInput:${controllerId}] Font list container not found, retrying...`);
|
|
1067
|
+
return scrollTimeout = setTimeout(() => {
|
|
1068
|
+
if (fontListContainer) {
|
|
1069
|
+
setupFontObserver();
|
|
1070
|
+
}
|
|
1071
|
+
}, 50);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// If we have no fonts to observe, try to update filtered fonts first
|
|
1075
|
+
if (!filteredFonts.length) {
|
|
1076
|
+
// console.log(`⚠️ [FontInput:${controllerId}] No fonts available to observe, updating filtered fonts`);
|
|
1077
|
+
updateFilteredFonts();
|
|
1078
|
+
|
|
1079
|
+
// If we still have no fonts, log a warning and return
|
|
1080
|
+
if (!filteredFonts.length) {
|
|
1081
|
+
// console.warn(`❌ [FontInput:${controllerId}] No fonts available to observe after update`);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// console.log(`🔍 [FontInput:${controllerId}] Setting up font observer for`, filteredFonts.length, 'fonts');
|
|
1087
|
+
|
|
1088
|
+
// Get the observer from FontManager
|
|
1089
|
+
const observer = fontManagerInstance?.getObserver(fontListContainer);
|
|
1090
|
+
if (!observer) {
|
|
1091
|
+
// console.warn(`❌ [FontInput:${controllerId}] Failed to create observer`);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
// Disconnect existing observer if it exists
|
|
1096
|
+
if (fontObserver) {
|
|
1097
|
+
fontObserver.disconnect();
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
fontObserver = observer;
|
|
1101
|
+
|
|
1102
|
+
// Use a more efficient approach to observe elements
|
|
1103
|
+
// First, get all font elements that need to be observed
|
|
1104
|
+
const fontElements = fontListContainer.querySelectorAll('[data-font]');
|
|
1105
|
+
|
|
1106
|
+
// If there are no elements to observe, try again after a short delay
|
|
1107
|
+
if (fontElements.length === 0) {
|
|
1108
|
+
// console.log(`⚠️ [FontInput:${controllerId}] No font elements found to observe, will retry`);
|
|
1109
|
+
return scrollTimeout = setTimeout(() => {
|
|
1110
|
+
setupFontObserver();
|
|
1111
|
+
}, 100);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
// console.log(`📝 [FontInput:${controllerId}] Observing`, fontElements.length, 'font elements');
|
|
1115
|
+
|
|
1116
|
+
// Use a more efficient approach to observe elements
|
|
1117
|
+
// Batch the observation process to reduce performance impact
|
|
1118
|
+
const batchSize = 20; // Process 20 elements at a time
|
|
1119
|
+
let currentIndex = 0;
|
|
1120
|
+
|
|
1121
|
+
const observeBatch = () => {
|
|
1122
|
+
const endIndex = Math.min(currentIndex + batchSize, fontElements.length);
|
|
1123
|
+
|
|
1124
|
+
for (let i = currentIndex; i < endIndex; i++) {
|
|
1125
|
+
observer.observe(fontElements[i]);
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
currentIndex = endIndex;
|
|
1129
|
+
|
|
1130
|
+
if (currentIndex < fontElements.length) {
|
|
1131
|
+
// Process next batch in the next animation frame
|
|
1132
|
+
requestAnimationFrame(observeBatch);
|
|
1133
|
+
} else {
|
|
1134
|
+
// console.log(`✅ [FontInput:${controllerId}] Finished observing all font elements:`, {
|
|
1135
|
+
// total: fontElements.length,
|
|
1136
|
+
// batchSize,
|
|
1137
|
+
// batches: Math.ceil(fontElements.length / batchSize)
|
|
1138
|
+
// });
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// Start the batched observation process
|
|
1143
|
+
observeBatch();
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
/**
|
|
1147
|
+
* Handles font list scrolling
|
|
1148
|
+
*/
|
|
1149
|
+
function handleFontListScroll(event: Event) {
|
|
1150
|
+
// Ensure target is an HTMLElement
|
|
1151
|
+
if (!(event.target instanceof HTMLElement)) {
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Prevent event propagation to stop global scroll handler from closing the modal
|
|
1156
|
+
event.stopPropagation();
|
|
1157
|
+
|
|
1158
|
+
// Handle font loading for visible fonts
|
|
1159
|
+
fontManagerInstance?.handleScroll();
|
|
1160
|
+
|
|
1161
|
+
const container = event.target;
|
|
1162
|
+
const { scrollTop, scrollHeight, clientHeight } = container;
|
|
1163
|
+
// Increase the threshold to detect when we're closer to the bottom
|
|
1164
|
+
const scrolledToBottom = (scrollHeight - (scrollTop + clientHeight)) <= 100;
|
|
1165
|
+
|
|
1166
|
+
// Add debug log to help diagnose scrolling issues
|
|
1167
|
+
// console.log(`📜 [FontInput:${controllerId}] Scroll position:`, {
|
|
1168
|
+
// scrollTop,
|
|
1169
|
+
// scrollHeight,
|
|
1170
|
+
// clientHeight,
|
|
1171
|
+
// remainingScroll: scrollHeight - (scrollTop + clientHeight),
|
|
1172
|
+
// scrolledToBottom,
|
|
1173
|
+
// hasSearchTerm: Boolean(searchTerm),
|
|
1174
|
+
// searchResultsCount: currentSearchResults.length,
|
|
1175
|
+
// displayedResults: filteredFonts.length,
|
|
1176
|
+
// currentFilter
|
|
1177
|
+
// });
|
|
1178
|
+
|
|
1179
|
+
if (scrolledToBottom) {
|
|
1180
|
+
if (searchTerm && currentSearchResults.length > 0) {
|
|
1181
|
+
// Calculate next batch using SEARCH_BATCH_SIZE
|
|
1182
|
+
const nextBatchStart = filteredFonts.length;
|
|
1183
|
+
const nextBatch = currentSearchResults.slice(
|
|
1184
|
+
nextBatchStart,
|
|
1185
|
+
nextBatchStart + SEARCH_BATCH_SIZE
|
|
1186
|
+
);
|
|
1187
|
+
|
|
1188
|
+
if (nextBatch.length > 0) {
|
|
1189
|
+
// console.log(`🔄 [FontInput:${controllerId}] Loading next search batch:`, {
|
|
1190
|
+
// batchSize: nextBatch.length,
|
|
1191
|
+
// startIndex: nextBatchStart,
|
|
1192
|
+
// endIndex: nextBatchStart + nextBatch.length,
|
|
1193
|
+
// totalSearchResults: currentSearchResults.length
|
|
1194
|
+
// });
|
|
1195
|
+
|
|
1196
|
+
// Add new batch to filtered fonts
|
|
1197
|
+
filteredFonts = [...filteredFonts, ...nextBatch];
|
|
1198
|
+
|
|
1199
|
+
// Setup observer for new fonts
|
|
1200
|
+
scrollTimeout = setTimeout(() => {
|
|
1201
|
+
if (fontListContainer) {
|
|
1202
|
+
setupFontObserver();
|
|
1203
|
+
}
|
|
1204
|
+
}, 0);
|
|
1205
|
+
|
|
1206
|
+
// console.log(`📝 [FontInput:${controllerId}] Loaded next search batch:`, {
|
|
1207
|
+
// batchSize: nextBatch.length,
|
|
1208
|
+
// totalDisplayed: filteredFonts.length,
|
|
1209
|
+
// remainingFonts: currentSearchResults.length - filteredFonts.length,
|
|
1210
|
+
// nextBatchSize: SEARCH_BATCH_SIZE
|
|
1211
|
+
// });
|
|
1212
|
+
} else {
|
|
1213
|
+
// console.log(`ℹ️ [FontInput:${controllerId}] No more search results to load`);
|
|
1214
|
+
}
|
|
1215
|
+
} else {
|
|
1216
|
+
// Normal font list scrolling
|
|
1217
|
+
if (!isLoadingBatch && fontManagerInstance?.hasMoreFonts(controllerId, currentFilter)) {
|
|
1218
|
+
// console.log(`🔄 [FontInput:${controllerId}] Reached bottom, loading next batch for ${currentFilter}`);
|
|
1219
|
+
loadNextBatch();
|
|
1220
|
+
} else if (isLoadingBatch) {
|
|
1221
|
+
// console.log(`⏳ [FontInput:${controllerId}] Already loading a batch, skipping`);
|
|
1222
|
+
} else if (!fontManagerInstance?.hasMoreFonts(controllerId, currentFilter)) {
|
|
1223
|
+
// console.log(`ℹ️ [FontInput:${controllerId}] No more fonts to load for ${currentFilter}`);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Loads the next batch of fonts
|
|
1231
|
+
*/
|
|
1232
|
+
async function loadNextBatch() {
|
|
1233
|
+
if (isLoadingBatch) return;
|
|
1234
|
+
|
|
1235
|
+
try {
|
|
1236
|
+
isLoadingBatch = true;
|
|
1237
|
+
// console.log(`🔄 [FontInput:${controllerId}] Loading next batch for ${currentFilter}`);
|
|
1238
|
+
|
|
1239
|
+
// Get next batch for this controller
|
|
1240
|
+
const newFonts = fontManagerInstance.getNextBatch(controllerId, currentFilter);
|
|
1241
|
+
// console.log(`📚 [FontInput:${controllerId}] Received next batch:`, {
|
|
1242
|
+
// count: newFonts.length,
|
|
1243
|
+
// category: currentFilter,
|
|
1244
|
+
// fonts: newFonts.slice(0, 3).map(f => f.family)
|
|
1245
|
+
// });
|
|
1246
|
+
|
|
1247
|
+
// Add to existing fonts, ensuring uniqueness
|
|
1248
|
+
const combinedFonts = [...allFonts, ...newFonts];
|
|
1249
|
+
allFonts = Array.from(
|
|
1250
|
+
new Map(combinedFonts.map(font => [font.family, font])).values()
|
|
1251
|
+
);
|
|
1252
|
+
|
|
1253
|
+
// Update filtered fonts while preserving current filter
|
|
1254
|
+
updateFilteredFonts();
|
|
1255
|
+
|
|
1256
|
+
// console.log(`✅ [FontInput:${controllerId}] Loaded new batch:`, {
|
|
1257
|
+
// controllerId,
|
|
1258
|
+
// filter: currentFilter,
|
|
1259
|
+
// newFonts: newFonts.length,
|
|
1260
|
+
// totalFonts: allFonts.length,
|
|
1261
|
+
// filteredFonts: filteredFonts.length,
|
|
1262
|
+
// hasMoreFonts: fontManagerInstance.hasMoreFonts(controllerId, currentFilter)
|
|
1263
|
+
// });
|
|
1264
|
+
|
|
1265
|
+
} catch (error) {
|
|
1266
|
+
console.error(`❌ [FontInput:${controllerId}] Error loading font batch:`, error);
|
|
1267
|
+
} finally {
|
|
1268
|
+
isLoadingBatch = false;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function handleToggle() {
|
|
1273
|
+
// console.log('Font dropdown toggled:', controllerId);
|
|
1274
|
+
toggleDropdown(controllerId);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
function closeDropdown() {
|
|
1278
|
+
if (open) {
|
|
1279
|
+
handleToggle();
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Create a custom action for font observation
|
|
1284
|
+
function observeFont(node: HTMLElement) {
|
|
1285
|
+
if (fontObserver) {
|
|
1286
|
+
fontObserver.observe(node);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
return {
|
|
1290
|
+
destroy() {
|
|
1291
|
+
if (fontObserver) {
|
|
1292
|
+
fontObserver.unobserve(node);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
$: open = $openDropdowns.has(controllerId);
|
|
1299
|
+
|
|
1300
|
+
// Add a reactive statement to set up observer when fonts are loaded
|
|
1301
|
+
$: if (filteredFonts.length > 0 && fontListContainer) {
|
|
1302
|
+
// console.log('📝 [FontInput] Fonts loaded, setting up observer...');
|
|
1303
|
+
setupFontObserver();
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
$: popoverStyle = `width: ${buttonWidth}px`;
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* Fetches and processes font weights from Google Fonts API
|
|
1310
|
+
* or from fonts.json for custom fonts
|
|
1311
|
+
*/
|
|
1312
|
+
async function fetchFontWeights(fontFamily: string) {
|
|
1313
|
+
try {
|
|
1314
|
+
// First check if it's a custom font using FontManager
|
|
1315
|
+
if (fontManagerInstance?.isCustomFont(fontFamily)) {
|
|
1316
|
+
// Get custom font data
|
|
1317
|
+
const customFontResponse = await fetch(fontsJsonUrl);
|
|
1318
|
+
if (customFontResponse.ok) {
|
|
1319
|
+
const customFontsData = await customFontResponse.json();
|
|
1320
|
+
const customFont = customFontsData.fontFamilies.find(
|
|
1321
|
+
(f: any) => f.family === fontFamily
|
|
1322
|
+
);
|
|
1323
|
+
|
|
1324
|
+
if (customFont) {
|
|
1325
|
+
// For custom fonts, get all variants from fonts.json
|
|
1326
|
+
availableWeights = customFont.variants
|
|
1327
|
+
.map((v: any) => v.weight)
|
|
1328
|
+
// Remove duplicates if any
|
|
1329
|
+
.filter((weight: string, index: number, self: string[]) =>
|
|
1330
|
+
self.indexOf(weight) === index
|
|
1331
|
+
)
|
|
1332
|
+
// Sort with Regular first, then alphabetically
|
|
1333
|
+
.sort((a: string, b: string) => {
|
|
1334
|
+
if (a === 'Regular') return -1;
|
|
1335
|
+
if (b === 'Regular') return 1;
|
|
1336
|
+
return a.localeCompare(b);
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
// Set default weight only if not already set for this controller
|
|
1340
|
+
if (!selectedWeight.startsWith(controllerId)) {
|
|
1341
|
+
selectedWeight = `${controllerId}-${availableWeights.includes('Regular') ?
|
|
1342
|
+
'Regular' : availableWeights[0]}`;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Google font: use catalog cached by FontManager (no extra API call per font)
|
|
1351
|
+
const variantIds = fontManagerInstance?.getGoogleFontVariantIds(fontFamily);
|
|
1352
|
+
if (variantIds?.length) {
|
|
1353
|
+
availableWeights = mapGoogleVariantIdsToWeightNames(variantIds);
|
|
1354
|
+
if (!availableWeights.includes(selectedWeight.replace(`${controllerId}-`, ''))) {
|
|
1355
|
+
selectedWeight = `${controllerId}-${availableWeights.includes('Regular') ? 'Regular' : availableWeights[0]}`;
|
|
1356
|
+
}
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// API unavailable (403 / no catalog): sensible defaults, still loads via fonts.googleapis.com CSS
|
|
1361
|
+
console.warn('[FontInput] Using default weights for font (Google catalog not loaded):', fontFamily);
|
|
1362
|
+
availableWeights = ['Regular', 'Bold'];
|
|
1363
|
+
selectedWeight = `${controllerId}-Regular`;
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
console.error('Error fetching font weights:', error);
|
|
1366
|
+
availableWeights = ['Regular'];
|
|
1367
|
+
selectedWeight = `${controllerId}-Regular`;
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
$: if (value?.family) {
|
|
1372
|
+
fetchFontWeights(value.family);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Highlights search term matches in font names
|
|
1377
|
+
* @param name Font name
|
|
1378
|
+
* @param term Search term
|
|
1379
|
+
* @returns HTML string with highlighted match
|
|
1380
|
+
*
|
|
1381
|
+
* Note: Returns safe HTML as we're only wrapping with <strong>
|
|
1382
|
+
*/
|
|
1383
|
+
function highlightMatch(name: string, term: string) {
|
|
1384
|
+
// Check if this font is the selected one
|
|
1385
|
+
const isSelected = value?.family === name;
|
|
1386
|
+
|
|
1387
|
+
if (!term) {
|
|
1388
|
+
// If no search term, just return the name with appropriate color
|
|
1389
|
+
return isSelected
|
|
1390
|
+
? `<span style="color: white">${name}</span>`
|
|
1391
|
+
: `<span style="color: hsl(var(--grey7))">${name}</span>`;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
const nameLower = name.toLowerCase();
|
|
1395
|
+
const termLower = term.toLowerCase().trim();
|
|
1396
|
+
|
|
1397
|
+
// If the name contains the search term, highlight that part
|
|
1398
|
+
if (nameLower.includes(termLower)) {
|
|
1399
|
+
const index = nameLower.indexOf(termLower);
|
|
1400
|
+
const beforeMatch = name.slice(0, index);
|
|
1401
|
+
const match = name.slice(index, index + termLower.length);
|
|
1402
|
+
const afterMatch = name.slice(index + termLower.length);
|
|
1403
|
+
|
|
1404
|
+
if (isSelected) {
|
|
1405
|
+
// For selected items: text is white, highlight is grey7
|
|
1406
|
+
return `<span style="color: white">${beforeMatch}<span style="color: hsl(var(--grey7))">${match}</span>${afterMatch}</span>`;
|
|
1407
|
+
} else {
|
|
1408
|
+
// For non-selected items: text is grey7, highlight is sage-green
|
|
1409
|
+
return `<span style="color: hsl(var(--grey7))">${beforeMatch}<span style="color: hsl(var(--sage-green))">${match}</span>${afterMatch}</span>`;
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Default case - just return the name with appropriate color
|
|
1414
|
+
return isSelected
|
|
1415
|
+
? `<span style="color: white">${name}</span>`
|
|
1416
|
+
: `<span style="color: hsl(var(--grey7))">${name}</span>`;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Handles global scroll events
|
|
1421
|
+
*/
|
|
1422
|
+
function handleGlobalScroll(event: Event) {
|
|
1423
|
+
// Ensure target is an HTMLElement before using closest
|
|
1424
|
+
if (!(event.target instanceof HTMLElement)) {
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const target = event.target;
|
|
1429
|
+
|
|
1430
|
+
// Check if scroll happened inside our containers
|
|
1431
|
+
const isInsideFontList = target === fontListContainer;
|
|
1432
|
+
const isInsidePopover = target.closest('.font-popover-content');
|
|
1433
|
+
const isInsideCommand = target.closest('[cmdk-root]');
|
|
1434
|
+
|
|
1435
|
+
// If scrolling is inside any of our containers, don't close
|
|
1436
|
+
if (isInsideFontList || isInsidePopover || isInsideCommand) {
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// If scrolling outside our containers, close dropdowns
|
|
1441
|
+
if ($openDropdowns?.has(controllerId)) {
|
|
1442
|
+
toggleDropdown(controllerId);
|
|
1443
|
+
}
|
|
1444
|
+
if ($openDropdowns?.has('weight-' + controllerId)) {
|
|
1445
|
+
toggleDropdown('weight-' + controllerId);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Update onMount to add scroll and touch listeners with passive option
|
|
1450
|
+
onMount(() => {
|
|
1451
|
+
// Add scroll listener with passive option
|
|
1452
|
+
window.addEventListener('scroll', handleGlobalScroll, {
|
|
1453
|
+
capture: true,
|
|
1454
|
+
passive: true
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
// Add touch listeners with passive option
|
|
1458
|
+
window.addEventListener('touchstart', () => {}, { passive: true });
|
|
1459
|
+
window.addEventListener('touchmove', () => {}, { passive: true });
|
|
1460
|
+
});
|
|
1461
|
+
|
|
1462
|
+
// Update onDestroy to remove all listeners
|
|
1463
|
+
onDestroy(() => {
|
|
1464
|
+
window.removeEventListener('scroll', handleGlobalScroll, true);
|
|
1465
|
+
window.removeEventListener('touchstart', () => {});
|
|
1466
|
+
window.removeEventListener('touchmove', () => {});
|
|
1467
|
+
|
|
1468
|
+
if (fontObserver) {
|
|
1469
|
+
fontObserver.disconnect();
|
|
1470
|
+
}
|
|
1471
|
+
// Clear any timeouts
|
|
1472
|
+
if (scrollTimeout) {
|
|
1473
|
+
clearTimeout(scrollTimeout);
|
|
1474
|
+
}
|
|
1475
|
+
if (initialLoadTimeout) {
|
|
1476
|
+
clearTimeout(initialLoadTimeout);
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
console.log('🧹 [FontInput] Cleanup complete:', {
|
|
1480
|
+
controllerId,
|
|
1481
|
+
lastFilter: currentFilter
|
|
1482
|
+
});
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
// Add this function to the script section
|
|
1486
|
+
function handleDeleteCustomFont(font: FontItem | RecentFont, event: Event) {
|
|
1487
|
+
event.stopPropagation();
|
|
1488
|
+
// Convert returned RecentFonts to FontItems
|
|
1489
|
+
recentFonts = fontUtils.removeCustomFont(font.family).map(font => ({
|
|
1490
|
+
family: font.family,
|
|
1491
|
+
category: font.category as FontItem['category'],
|
|
1492
|
+
isCustom: font.isCustom
|
|
1493
|
+
}));
|
|
1494
|
+
updateFilteredFonts();
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// Add this function after the script tag
|
|
1498
|
+
function normalizeCategory(category: string): FontItem['category'] {
|
|
1499
|
+
return fontCategories.normalize(category);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
async function removeFont(fontFamily: string) {
|
|
1503
|
+
try {
|
|
1504
|
+
// Remove from manager
|
|
1505
|
+
await fontManagerInstance?.removeCustomFont(fontFamily);
|
|
1506
|
+
|
|
1507
|
+
// If the removed font was selected, clear the selection
|
|
1508
|
+
if (value?.family === fontFamily && onChange) {
|
|
1509
|
+
onChange({
|
|
1510
|
+
family: '',
|
|
1511
|
+
weight: 'Regular',
|
|
1512
|
+
category: ''
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
// Remove from recent fonts if present
|
|
1517
|
+
const updatedRecentFonts = recentFonts.filter(f => f.family !== fontFamily);
|
|
1518
|
+
recentFonts = updatedRecentFonts;
|
|
1519
|
+
|
|
1520
|
+
// Add lastUsed property before updating store
|
|
1521
|
+
const recentFontsWithLastUsed = updatedRecentFonts.map(f => ({
|
|
1522
|
+
...f,
|
|
1523
|
+
lastUsed: Date.now()
|
|
1524
|
+
}));
|
|
1525
|
+
recentFontsStore.set(recentFontsWithLastUsed);
|
|
1526
|
+
|
|
1527
|
+
// Remove from all fonts
|
|
1528
|
+
allFonts = allFonts.filter(f => f.family !== fontFamily);
|
|
1529
|
+
|
|
1530
|
+
// Update filtered fonts
|
|
1531
|
+
updateFilteredFonts();
|
|
1532
|
+
|
|
1533
|
+
console.log('✅ [FontInput] Font removed:', {
|
|
1534
|
+
fontFamily,
|
|
1535
|
+
remainingFonts: allFonts.length,
|
|
1536
|
+
filteredFonts: filteredFonts.length,
|
|
1537
|
+
recentFonts: recentFonts.length
|
|
1538
|
+
});
|
|
1539
|
+
} catch (error) {
|
|
1540
|
+
console.error('❌ [FontInput] Error removing font:', error);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
// Update the weight selection handler
|
|
1545
|
+
function handleWeightSelect(weight: string) {
|
|
1546
|
+
selectedWeight = `${controllerId}-${weight}`;
|
|
1547
|
+
if (browser) {
|
|
1548
|
+
localStorage.setItem(FONT_WEIGHT_KEY(controllerId), selectedWeight);
|
|
1549
|
+
}
|
|
1550
|
+
if (onChange) {
|
|
1551
|
+
onChange({
|
|
1552
|
+
family: value?.family || '',
|
|
1553
|
+
weight: weight,
|
|
1554
|
+
category: value?.category || ''
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
// Update the template to use the modified weight
|
|
1560
|
+
$: displayWeight = selectedWeight.replace(`${controllerId}-`, '');
|
|
1561
|
+
|
|
1562
|
+
// Update arrow click handler to use filteredFonts
|
|
1563
|
+
function handleArrowClick(direction: 'up' | 'down') {
|
|
1564
|
+
if (!filteredFonts.length) return;
|
|
1565
|
+
|
|
1566
|
+
if (direction === 'down') {
|
|
1567
|
+
activeSearchIndex = (activeSearchIndex + 1) % filteredFonts.length;
|
|
1568
|
+
} else {
|
|
1569
|
+
activeSearchIndex = activeSearchIndex <= 0 ? filteredFonts.length - 1 : activeSearchIndex - 1;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
// Ensure the selected item is visible
|
|
1573
|
+
const selectedElement = fontListContainer?.querySelector(`[data-font="${filteredFonts[activeSearchIndex].family}"]`);
|
|
1574
|
+
selectedElement?.scrollIntoView({ block: 'nearest' });
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
// Add a function to handle mouse enter events on font items
|
|
1578
|
+
function handleMouseEnter(index: number) {
|
|
1579
|
+
// Update the active index to the hovered item
|
|
1580
|
+
activeSearchIndex = index;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// Functions for RecentItemsSection
|
|
1584
|
+
function renderFontItem(font: FontItem) {
|
|
1585
|
+
return {
|
|
1586
|
+
html: `
|
|
1587
|
+
<div class="flex items-center flex-1 min-w-0">
|
|
1588
|
+
<span
|
|
1589
|
+
class="text-[24px] truncate"
|
|
1590
|
+
style="font-family: '${font.family}', sans-serif; font-display: swap;"
|
|
1591
|
+
>
|
|
1592
|
+
${font.family}
|
|
1593
|
+
</span>
|
|
1594
|
+
</div>
|
|
1595
|
+
`
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
function isFontSelected(font: FontItem) {
|
|
1600
|
+
return value?.family === font.family;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function isCustomFont(font: FontItem) {
|
|
1604
|
+
return font.category === 'Mine' && Boolean(font.isCustom);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
function getFontKey(font: FontItem) {
|
|
1608
|
+
// Create a unique key by combining family with category
|
|
1609
|
+
// This prevents duplicate keys when two fonts have the same family name
|
|
1610
|
+
return `${font.family}-${font.category}`;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function handleFontDelete(font: FontItem, event: Event) {
|
|
1614
|
+
event.stopPropagation();
|
|
1615
|
+
event.preventDefault();
|
|
1616
|
+
removeFont(font.family);
|
|
1617
|
+
}
|
|
1618
|
+
</script>
|
|
1619
|
+
|
|
1620
|
+
<!-- Font Input Container -->
|
|
1621
|
+
<div class="flex flex-col widgetic-colors">
|
|
1622
|
+
<!-- Font Family Section -->
|
|
1623
|
+
<div class="flex flex-col gap-[8px]">
|
|
1624
|
+
<div class="flex items-center w-full relative h-[48px]">
|
|
1625
|
+
<div class="w-[185px] flex-shrink-0 ml-auto">
|
|
1626
|
+
<InputControllerHeader
|
|
1627
|
+
label={label}
|
|
1628
|
+
helpText={description}
|
|
1629
|
+
/>
|
|
1630
|
+
</div>
|
|
1631
|
+
|
|
1632
|
+
<div class="absolute right-0">
|
|
1633
|
+
<div class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green">
|
|
1634
|
+
<!-- Font Family Button -->
|
|
1635
|
+
<Popover.Root
|
|
1636
|
+
open={$openDropdowns?.has(controllerId)}
|
|
1637
|
+
onOpenChange={(isOpen) => {
|
|
1638
|
+
if (isOpen !== $openDropdowns?.has(controllerId)) {
|
|
1639
|
+
if (!isOpen && value?.family && !shouldSkipFont(value.family)) {
|
|
1640
|
+
fontUtils.addRecentFont({
|
|
1641
|
+
family: value.family,
|
|
1642
|
+
category: normalizeCategory(value.category) as FontItem['category'],
|
|
1643
|
+
isCustom: fontManagerInstance?.isCustomFont(value.family) || false
|
|
1644
|
+
});
|
|
1645
|
+
} else if (isOpen) {
|
|
1646
|
+
// Reset active search index when opening the dropdown
|
|
1647
|
+
activeSearchIndex = -1;
|
|
1648
|
+
|
|
1649
|
+
// When opening, ensure we have fonts ready
|
|
1650
|
+
if (filteredFonts.length === 0 && allFonts.length > 0) {
|
|
1651
|
+
// If we have allFonts but no filtered fonts, update them
|
|
1652
|
+
updateFilteredFonts();
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// Use a small timeout to ensure the DOM has updated before setting up the observer
|
|
1656
|
+
// This prevents the initial delay when opening the popover
|
|
1657
|
+
setTimeout(() => {
|
|
1658
|
+
if (fontListContainer) {
|
|
1659
|
+
setupFontObserver();
|
|
1660
|
+
}
|
|
1661
|
+
}, 50);
|
|
1662
|
+
|
|
1663
|
+
toggleDropdown(controllerId);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}}
|
|
1667
|
+
>
|
|
1668
|
+
<Popover.Trigger class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group"
|
|
1669
|
+
onmouseenter={() => {
|
|
1670
|
+
// Preload fonts when hovering over the button
|
|
1671
|
+
// This reduces the delay when actually clicking to open
|
|
1672
|
+
if (!$openDropdowns?.has(controllerId) && filteredFonts.length === 0 && allFonts.length > 0) {
|
|
1673
|
+
updateFilteredFonts();
|
|
1674
|
+
}
|
|
1675
|
+
}}
|
|
1676
|
+
>
|
|
1677
|
+
<span
|
|
1678
|
+
class="truncate flex-1 text-left subtext text-grey7"
|
|
1679
|
+
style="font-family: '{value?.family || 'system-ui'}', sans-serif; font-display: swap;"
|
|
1680
|
+
>
|
|
1681
|
+
{value?.family || "Select font..."}
|
|
1682
|
+
</span>
|
|
1683
|
+
<IconCaretDownFilled class="w-5 h-5 shrink-0 text-grey5 group-hover:text-grey7" />
|
|
1684
|
+
</Popover.Trigger>
|
|
1685
|
+
|
|
1686
|
+
<Popover.Content class="font-input-popover w-[374px] h-[565px] !p-0 widgetic-colors widgetic-radius rounded-medium modal-shadow border border-grey2">
|
|
1687
|
+
<div class="flex flex-col h-full font-popover-content">
|
|
1688
|
+
<!-- Title and upload icon -->
|
|
1689
|
+
<div class="relative pt-6 pb-[11px] px-5">
|
|
1690
|
+
<div class="h5 text-grey7 widgetic-colors">Fonts</div>
|
|
1691
|
+
<button
|
|
1692
|
+
type="button"
|
|
1693
|
+
class="absolute right-6 top-[26px] bg-transparent border-0 cursor-pointer p-0 rounded-full hover:bg-grey3 transition-all duration-300"
|
|
1694
|
+
onclick={() => {
|
|
1695
|
+
try {
|
|
1696
|
+
openFileBrowser();
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
console.error('Error opening file browser:', error);
|
|
1699
|
+
}
|
|
1700
|
+
}}
|
|
1701
|
+
>
|
|
1702
|
+
<IconCloudUpload class="w-6 h-6 text-grey7 hover:text-grey7 transition-colors duration-300 widgetic-colors" />
|
|
1703
|
+
</button>
|
|
1704
|
+
</div>
|
|
1705
|
+
|
|
1706
|
+
{#if description}
|
|
1707
|
+
<p class="px-4 -mt-2 mb-2 subtext text-grey5">{description}</p>
|
|
1708
|
+
{/if}
|
|
1709
|
+
|
|
1710
|
+
<!-- Search input -->
|
|
1711
|
+
<div class="relative px-4">
|
|
1712
|
+
<SearchInput
|
|
1713
|
+
placeholder="Search fonts..."
|
|
1714
|
+
bind:value={searchTerm}
|
|
1715
|
+
onInput={(e: Event) => {
|
|
1716
|
+
// Use debounced search only for typing
|
|
1717
|
+
debouncedSearch(e);
|
|
1718
|
+
}}
|
|
1719
|
+
onPaste={handlePaste}
|
|
1720
|
+
onKeydown={handleSearchKeydown}
|
|
1721
|
+
onClear={() => {
|
|
1722
|
+
searchTerm = ''; // Reset search term
|
|
1723
|
+
|
|
1724
|
+
// Restore the previous filter when clearing search
|
|
1725
|
+
currentFilter = lastActiveFilter;
|
|
1726
|
+
if (browser) {
|
|
1727
|
+
localStorage.setItem(FONT_FILTER_KEY(controllerId), lastActiveFilter);
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// ... rest of your onClear code ...
|
|
1731
|
+
}}
|
|
1732
|
+
/>
|
|
1733
|
+
</div>
|
|
1734
|
+
<!-- Filter toggle group - sticky header -->
|
|
1735
|
+
{#if !searchTerm}
|
|
1736
|
+
<div class="sticky top-0 bg-white z-10">
|
|
1737
|
+
<CategoryTabs
|
|
1738
|
+
categories={filterOptions}
|
|
1739
|
+
activeCategory={currentFilter}
|
|
1740
|
+
onCategorySelect={handleFilterChange}
|
|
1741
|
+
filterVisible={!searchTerm}
|
|
1742
|
+
/>
|
|
1743
|
+
</div>
|
|
1744
|
+
{/if}
|
|
1745
|
+
|
|
1746
|
+
<!-- Scrollable content -->
|
|
1747
|
+
<div
|
|
1748
|
+
class="flex-1 {filteredFonts.length > 0 ? 'overflow-y-auto' : 'overflow-hidden'} min-h-0 font-list-container"
|
|
1749
|
+
bind:this={fontListContainer}
|
|
1750
|
+
onscroll={(e) => {
|
|
1751
|
+
e.stopPropagation();
|
|
1752
|
+
handleFontListScroll(e);
|
|
1753
|
+
updateScrollState();
|
|
1754
|
+
}}
|
|
1755
|
+
id={`font-list-${controllerId}`}
|
|
1756
|
+
role="listbox"
|
|
1757
|
+
aria-label="Font list"
|
|
1758
|
+
>
|
|
1759
|
+
<div class="space-y-0">
|
|
1760
|
+
<!-- Recently used section - only show when not searching -->
|
|
1761
|
+
{#if recentFonts.length > 0 && !searchTerm}
|
|
1762
|
+
<RecentItemsSection
|
|
1763
|
+
recentItems={recentFonts}
|
|
1764
|
+
title="Recently used"
|
|
1765
|
+
onItemSelect={handleFontSelect}
|
|
1766
|
+
onItemDelete={handleFontDelete}
|
|
1767
|
+
isItemSelected={isFontSelected}
|
|
1768
|
+
renderItem={renderFontItem}
|
|
1769
|
+
isCustomItem={isCustomFont}
|
|
1770
|
+
getItemKey={getFontKey}
|
|
1771
|
+
observeItem={observeFont}
|
|
1772
|
+
displayMode="list"
|
|
1773
|
+
hideTrashIcon={hideTrashIcon}
|
|
1774
|
+
itemType="font"
|
|
1775
|
+
/>
|
|
1776
|
+
{/if}
|
|
1777
|
+
|
|
1778
|
+
<!-- All fonts section -->
|
|
1779
|
+
<div class="mt-6 px-4">
|
|
1780
|
+
{#if filteredFonts.length > 0}
|
|
1781
|
+
<div id="font-header-{controllerId}" class="sticky top-0 -mt-2 bg-white z-10 pt-2 {isScrolled ? 'border-b border-grey2' : 'border-b border-transparent'} transition-border">
|
|
1782
|
+
<h3 class="subtext text-grey7 pb-2 flex items-center">
|
|
1783
|
+
{#if isInitialLoading}
|
|
1784
|
+
<div class="w-4 h-4 border-2 border-t-sage-green border-r-sage-green border-b-grey2 border-l-grey2 rounded-full animate-spin mr-2"></div>
|
|
1785
|
+
{/if}
|
|
1786
|
+
{searchTerm ? 'Search results' : 'More'}
|
|
1787
|
+
</h3>
|
|
1788
|
+
</div>
|
|
1789
|
+
{/if}
|
|
1790
|
+
|
|
1791
|
+
{#if filteredFonts.length === 0}
|
|
1792
|
+
<div class="empty-state-wrapper">
|
|
1793
|
+
{#if isInitialLoading}
|
|
1794
|
+
<!-- Show compact loading indicator while fonts are loading -->
|
|
1795
|
+
<div class="flex items-center">
|
|
1796
|
+
<div class="text-grey5 subtext mr-2">Loading fonts</div>
|
|
1797
|
+
<div class="w-4 h-4 border-2 border-t-sage-green border-r-sage-green border-b-grey2 border-l-grey2 rounded-full animate-spin"></div>
|
|
1798
|
+
</div>
|
|
1799
|
+
{:else}
|
|
1800
|
+
<!-- Only show empty state when loading is complete and no fonts were found -->
|
|
1801
|
+
<EmptyState
|
|
1802
|
+
message={searchTerm ? 'No fonts found' : 'No fonts available'}
|
|
1803
|
+
height="auto"
|
|
1804
|
+
/>
|
|
1805
|
+
{/if}
|
|
1806
|
+
</div>
|
|
1807
|
+
{:else}
|
|
1808
|
+
<div class="pb-4"> <!-- Add a container with bottom padding -->
|
|
1809
|
+
{#each filteredFonts as font, index (`${font.family}-${searchTerm ? 'search' : currentFilter}-${index}`)}
|
|
1810
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1811
|
+
<div
|
|
1812
|
+
class="w-full py-1 px-4 cursor-pointer overflow-hidden relative rounded-small h-[42px] flex items-center
|
|
1813
|
+
{value?.family === font.family ? 'bg-sage-green text-white selected-item' :
|
|
1814
|
+
index === activeSearchIndex && searchTerm ? 'bg-light-green text-sage-green' :
|
|
1815
|
+
'hover:bg-light-green'}"
|
|
1816
|
+
data-font={font.family}
|
|
1817
|
+
use:observeFont
|
|
1818
|
+
onclick={() => handleFontSelect(font)}
|
|
1819
|
+
onmouseenter={() => handleMouseEnter(index)}
|
|
1820
|
+
role="button"
|
|
1821
|
+
tabindex="0"
|
|
1822
|
+
>
|
|
1823
|
+
<div class="flex items-center flex-1 min-w-0 group">
|
|
1824
|
+
<Check class="mr-2 h-6 w-6 flex-shrink-0 {value?.family === font.family ? 'text-white' : 'text-sage-green opacity-0'}"/>
|
|
1825
|
+
<span
|
|
1826
|
+
class="text-[24px] truncate {index === activeSearchIndex && searchTerm ? 'font-medium' : ''}"
|
|
1827
|
+
style="font-family: '{font.family}', sans-serif; font-display: swap;"
|
|
1828
|
+
>
|
|
1829
|
+
{@html highlightMatch(font.family, searchTerm)}
|
|
1830
|
+
</span>
|
|
1831
|
+
|
|
1832
|
+
{#if font.category === 'Mine' && font.isCustom && !hideTrashIcon}
|
|
1833
|
+
<div
|
|
1834
|
+
role="button"
|
|
1835
|
+
tabindex="-1"
|
|
1836
|
+
class="ml-auto transition-opacity {value?.family === font.family ? 'opacity-0 pointer-events-none' : 'opacity-0 group-hover:opacity-100'}"
|
|
1837
|
+
onclick={(e) => {
|
|
1838
|
+
e.stopPropagation();
|
|
1839
|
+
e.preventDefault();
|
|
1840
|
+
removeFont(font.family);
|
|
1841
|
+
}}
|
|
1842
|
+
>
|
|
1843
|
+
<img
|
|
1844
|
+
src={icTrash}
|
|
1845
|
+
alt="Remove font"
|
|
1846
|
+
class="w-4 h-4 text-grey5 hover:text-error"
|
|
1847
|
+
draggable="false"
|
|
1848
|
+
/>
|
|
1849
|
+
<span class="sr-only">Remove font</span>
|
|
1850
|
+
</div>
|
|
1851
|
+
{/if}
|
|
1852
|
+
</div>
|
|
1853
|
+
</div>
|
|
1854
|
+
{/each}
|
|
1855
|
+
</div>
|
|
1856
|
+
{/if}
|
|
1857
|
+
</div>
|
|
1858
|
+
</div>
|
|
1859
|
+
</div>
|
|
1860
|
+
</div>
|
|
1861
|
+
</Popover.Content>
|
|
1862
|
+
</Popover.Root>
|
|
1863
|
+
</div>
|
|
1864
|
+
</div>
|
|
1865
|
+
</div>
|
|
1866
|
+
|
|
1867
|
+
<!-- Font Weight Section -->
|
|
1868
|
+
<div class="flex items-center w-full relative h-[48px]">
|
|
1869
|
+
<div class="w-[185px]"></div>
|
|
1870
|
+
<div class="absolute right-0">
|
|
1871
|
+
<div class="h-[48px] w-[185px] rounded-large border border-grey2 flex items-center bg-grey1 transition-colors duration-200 hover:border-forest-green">
|
|
1872
|
+
<!-- Font Weight Button -->
|
|
1873
|
+
<Popover.Root
|
|
1874
|
+
open={$openDropdowns?.has('weight-' + controllerId)}
|
|
1875
|
+
onOpenChange={(isOpen) => {
|
|
1876
|
+
if (isOpen !== $openDropdowns?.has('weight-' + controllerId)) {
|
|
1877
|
+
toggleDropdown('weight-' + controllerId);
|
|
1878
|
+
}
|
|
1879
|
+
}}
|
|
1880
|
+
>
|
|
1881
|
+
<Popover.Trigger class="w-full h-full flex items-center border-0 bg-transparent hover:bg-transparent focus:bg-transparent pl-3 pr-[16px] group">
|
|
1882
|
+
<span class="truncate flex-1 text-left subtext text-grey7">
|
|
1883
|
+
{displayWeight}
|
|
1884
|
+
</span>
|
|
1885
|
+
<IconCaretDownFilled class="w-5 h-5 shrink-0 text-grey5 group-hover:text-grey7" />
|
|
1886
|
+
</Popover.Trigger>
|
|
1887
|
+
<Popover.Content class="p-0 w-[150px]">
|
|
1888
|
+
<Command.Root>
|
|
1889
|
+
<Command.Group>
|
|
1890
|
+
{#each availableWeights as weight}
|
|
1891
|
+
<Command.Item
|
|
1892
|
+
value={weight}
|
|
1893
|
+
onSelect={() => handleWeightSelect(weight)}
|
|
1894
|
+
class="px-2 py-2"
|
|
1895
|
+
>
|
|
1896
|
+
<Check
|
|
1897
|
+
class="mr-1 h-4 w-4 {displayWeight === weight ? 'opacity-100' : 'opacity-0'}"
|
|
1898
|
+
/>
|
|
1899
|
+
<span class="truncate">{weight}</span>
|
|
1900
|
+
</Command.Item>
|
|
1901
|
+
{/each}
|
|
1902
|
+
</Command.Group>
|
|
1903
|
+
</Command.Root>
|
|
1904
|
+
</Popover.Content>
|
|
1905
|
+
</Popover.Root>
|
|
1906
|
+
</div>
|
|
1907
|
+
</div>
|
|
1908
|
+
</div>
|
|
1909
|
+
</div>
|
|
1910
|
+
|
|
1911
|
+
{#if description}
|
|
1912
|
+
<p class="subtext text-grey5">{description}</p>
|
|
1913
|
+
{/if}
|
|
1914
|
+
</div>
|
|
1915
|
+
|
|
1916
|
+
<style>
|
|
1917
|
+
/* Update button styles */
|
|
1918
|
+
:global(button.bg-transparent) {
|
|
1919
|
+
background: transparent !important;
|
|
1920
|
+
border: none !important;
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
:global(button.bg-transparent:hover),
|
|
1924
|
+
:global(button.bg-transparent:focus) {
|
|
1925
|
+
background-color: transparent !important;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
/* Keep scrollbar styles */
|
|
1929
|
+
:global(.overflow-y-auto) {
|
|
1930
|
+
-webkit-overflow-scrolling: touch;
|
|
1931
|
+
scrollbar-width: thin;
|
|
1932
|
+
scrollbar-color: hsl(var(--grey3)) transparent;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
/* Ensure buttons take full width */
|
|
1936
|
+
:global(.popover-trigger),
|
|
1937
|
+
:global(.popover-trigger button) {
|
|
1938
|
+
width: 100% !important;
|
|
1939
|
+
height: 100% !important;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
/* Custom styling for font items in RecentItemsSection */
|
|
1943
|
+
:global([data-item]) {
|
|
1944
|
+
border-radius: var(--radius-small) !important;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
:global([data-item].bg-\[hsl\(var\(--forest-green-10\)\)\]) {
|
|
1948
|
+
background-color: hsl(var(--light-green)) !important;
|
|
1949
|
+
color: hsl(var(--sage-green)) !important;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
:global([data-item]:hover) {
|
|
1953
|
+
background-color: hsl(var(--light-green)) !important;
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/* Optimize font popover rendering */
|
|
1957
|
+
:global(.font-input-popover) {
|
|
1958
|
+
z-index: 50;
|
|
1959
|
+
will-change: transform;
|
|
1960
|
+
transform: translateZ(0);
|
|
1961
|
+
backface-visibility: hidden;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
:global(.font-popover-content) {
|
|
1965
|
+
will-change: transform;
|
|
1966
|
+
transform: translateZ(0);
|
|
1967
|
+
backface-visibility: hidden;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
/* Optimize font list container scrolling */
|
|
1971
|
+
:global(.font-list-container) {
|
|
1972
|
+
-webkit-overflow-scrolling: touch;
|
|
1973
|
+
scrollbar-width: thin;
|
|
1974
|
+
scrollbar-color: hsl(var(--grey3)) transparent;
|
|
1975
|
+
will-change: transform;
|
|
1976
|
+
transform: translateZ(0);
|
|
1977
|
+
backface-visibility: hidden;
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
/* Add transition only for border appearance */
|
|
1981
|
+
:global(.transition-border) {
|
|
1982
|
+
transition: border-color 0.2s ease-in;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
/* Prevent scrolling when showing empty state */
|
|
1986
|
+
.empty-state-wrapper {
|
|
1987
|
+
position: absolute;
|
|
1988
|
+
top: 0;
|
|
1989
|
+
left: 0;
|
|
1990
|
+
right: 0;
|
|
1991
|
+
bottom: 0;
|
|
1992
|
+
display: flex;
|
|
1993
|
+
align-items: center;
|
|
1994
|
+
justify-content: center;
|
|
1995
|
+
overflow: hidden;
|
|
1996
|
+
padding-top: 60px; /* Add padding to account for the header */
|
|
1997
|
+
padding-bottom: 60px; /* Balance the padding */
|
|
1998
|
+
z-index: 10; /* Ensure it appears above other content */
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/* Prevent hover effect on selected items */
|
|
2002
|
+
:global(.selected-item:hover) {
|
|
2003
|
+
background-color: hsl(var(--sage-green)) !important;
|
|
2004
|
+
color: white !important;
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
/* Ensure text is white in selected items */
|
|
2008
|
+
:global(.selected-item) {
|
|
2009
|
+
color: white !important;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
/* Style for search result highlighting */
|
|
2013
|
+
:global(.search-result-highlight) {
|
|
2014
|
+
color: hsl(var(--sage-green)) !important;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/* Style for active search item */
|
|
2018
|
+
:global(.active-search-item) {
|
|
2019
|
+
background-color: hsl(var(--light-green)) !important;
|
|
2020
|
+
color: hsl(var(--sage-green)) !important;
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/* Style for selected item text */
|
|
2024
|
+
:global(.selected-item-text) {
|
|
2025
|
+
color: white !important;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
/* Style for selected item highlight */
|
|
2029
|
+
:global(.selected-item-highlight) {
|
|
2030
|
+
color: hsl(var(--grey7)) !important;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
/* Style for checkmark icon */
|
|
2034
|
+
:global(.checkmark-icon) {
|
|
2035
|
+
margin-left: 12px !important;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
/* Style for font item padding */
|
|
2039
|
+
:global(.font-item) {
|
|
2040
|
+
padding-left: 48px !important;
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
/* Style for font item height */
|
|
2044
|
+
:global(.font-item) {
|
|
2045
|
+
height: 42px !important;
|
|
2046
|
+
display: flex !important;
|
|
2047
|
+
align-items: center !important;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
/* Style for font item text */
|
|
2051
|
+
:global(.font-item-text) {
|
|
2052
|
+
font-size: 24px !important;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/* Style for font item padding */
|
|
2056
|
+
:global(.font-item) {
|
|
2057
|
+
padding: 8px !important;
|
|
2058
|
+
}
|
|
2059
|
+
</style>
|
|
2060
|
+
|
|
2061
|
+
|
|
2062
|
+
|