@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,1584 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
* ColorPickerInputController
|
|
3
|
+
*
|
|
4
|
+
* A sophisticated color picker component that manages complex color state and user interactions.
|
|
5
|
+
* Handles multiple color formats, opacity control, and recent color history.
|
|
6
|
+
*
|
|
7
|
+
* Key Features:
|
|
8
|
+
* - Color format conversion (HEX, RGB, HSV)
|
|
9
|
+
* - Opacity control with visual feedback
|
|
10
|
+
* - Color name suggestions with autocomplete
|
|
11
|
+
* - Recent colors history with preview
|
|
12
|
+
* - Keyboard navigation support
|
|
13
|
+
*
|
|
14
|
+
* Edge Cases:
|
|
15
|
+
* - Handles invalid color inputs
|
|
16
|
+
* - Preserves opacity across color changes
|
|
17
|
+
* - Manages concurrent drag operations
|
|
18
|
+
* - Prevents recursive updates
|
|
19
|
+
-->
|
|
20
|
+
|
|
21
|
+
<script lang="ts">
|
|
22
|
+
// Svelte imports
|
|
23
|
+
import { onMount, onDestroy, tick } from 'svelte';
|
|
24
|
+
import { createEventDispatcher } from 'svelte';
|
|
25
|
+
const dispatch = createEventDispatcher();
|
|
26
|
+
|
|
27
|
+
// Config imports
|
|
28
|
+
import type { InputControllerConfig } from './index';
|
|
29
|
+
|
|
30
|
+
// Shadcn UI Components
|
|
31
|
+
import { Input } from '@widgetic/design-system/components';
|
|
32
|
+
import { InputControllerHeader } from './index';
|
|
33
|
+
|
|
34
|
+
// Icons from Widgetic Design System
|
|
35
|
+
import { CheckerBoardBackground, OpacitySliderArrows } from '@widgetic/design-system/icons';
|
|
36
|
+
|
|
37
|
+
// Third party imports
|
|
38
|
+
import { Portal } from '@widgetic/design-system/components';
|
|
39
|
+
// Remove ColorSelect import from dependencies, use local forked version only
|
|
40
|
+
// import ColorSelect from 'svelte-color-select';
|
|
41
|
+
import ColorSelect from '../color-select/ColorSelect.svelte';
|
|
42
|
+
|
|
43
|
+
// Utils imports
|
|
44
|
+
import { ColorTransparencyHandler, type RGB } from '../../utils/colorTransparency';
|
|
45
|
+
import { colorNames } from '../../utils/colorNames';
|
|
46
|
+
import { getColorSuggestions, getNextSuggestion, type AutocompleteResult } from '../../utils/colorAutocomplete';
|
|
47
|
+
import { recentColorsUtil } from '../../utils/recentColors';
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* State Management
|
|
53
|
+
* Core state variables controlling component behavior and UI
|
|
54
|
+
* Handles keyboard modifiers, visibility states, and color history
|
|
55
|
+
*/
|
|
56
|
+
let isShiftPressed = false;
|
|
57
|
+
let isSpacePressed = false;
|
|
58
|
+
let showNoRecentMessage = false;
|
|
59
|
+
let recentColors = recentColorsUtil.getRecentColors();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Component Props
|
|
63
|
+
* External properties that configure the color picker's behavior
|
|
64
|
+
*/
|
|
65
|
+
export let label: string = ''; // Display label
|
|
66
|
+
export let value = '#000000'; // Current color value
|
|
67
|
+
export let onChange: (newValue: string) => void; // Value change callback
|
|
68
|
+
export let controllerId: string = '';
|
|
69
|
+
export let id: string = '';
|
|
70
|
+
export let openDropdowns: any;
|
|
71
|
+
export let toggleDropdown: (id: string) => void;
|
|
72
|
+
export let config: InputControllerConfig = {}; // Additional configuration
|
|
73
|
+
export let helpText: string | undefined = undefined; // Help text for the input
|
|
74
|
+
|
|
75
|
+
// Use config values with fallbacks
|
|
76
|
+
export let saveRecentColors = true;
|
|
77
|
+
export let showOpacityControls = true;
|
|
78
|
+
export let autofocus: boolean = false;
|
|
79
|
+
export let portalTarget = 'editor-portal';
|
|
80
|
+
|
|
81
|
+
// Sizing props for easy adjustment (can be overridden by parent)
|
|
82
|
+
export let pickerSize = 200; // width/height of color area
|
|
83
|
+
export let sliderWidth = 24; // width of hue bar and opacity input
|
|
84
|
+
$: swatchGap = 4;
|
|
85
|
+
$: swatchCount = 5;
|
|
86
|
+
$: swatchAreaWidth = pickerSize + sliderWidth + 14;
|
|
87
|
+
$: swatchSize = Math.floor((swatchAreaWidth - (swatchCount - 1) * swatchGap) / swatchCount);
|
|
88
|
+
|
|
89
|
+
// Exported opacity with default handling
|
|
90
|
+
export let opacity = config.defaultTransparency !== undefined ? config.defaultTransparency / 100 : 1;
|
|
91
|
+
|
|
92
|
+
// Tracks the last known opacity to maintain consistency
|
|
93
|
+
let lastKnownOpacity = opacity;
|
|
94
|
+
|
|
95
|
+
// Reactive statement to keep lastKnownOpacity in sync with opacity changes
|
|
96
|
+
$: lastKnownOpacity = opacity;
|
|
97
|
+
|
|
98
|
+
// Initialize the transparency handler with the current opacity
|
|
99
|
+
const transparencyHandler = new ColorTransparencyHandler(lastKnownOpacity);
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Color State
|
|
103
|
+
* Maintains synchronized state between different color representations
|
|
104
|
+
* Uses isInternalUpdate flag to prevent recursive updates during state changes
|
|
105
|
+
*/
|
|
106
|
+
let rgb = { r: 0, g: 0, b: 0, a: lastKnownOpacity };
|
|
107
|
+
let opacityValue = lastKnownOpacity;
|
|
108
|
+
let opacityPercentage = Math.round(lastKnownOpacity * 100);
|
|
109
|
+
|
|
110
|
+
// Unique instance ID for this color picker (use controllerId, id, or generate one)
|
|
111
|
+
const instanceId = controllerId || id || `cpicker-${Math.random().toString(36).slice(2, 8)}`;
|
|
112
|
+
|
|
113
|
+
// Local open state — not derived from store
|
|
114
|
+
let open = false;
|
|
115
|
+
|
|
116
|
+
// Listen for "close all color pickers except me" events from other instances
|
|
117
|
+
function onCloseOtherPickers(e: CustomEvent<string>) {
|
|
118
|
+
if (e.detail !== instanceId && open) {
|
|
119
|
+
open = false;
|
|
120
|
+
saveToRecentColors(value, lastKnownOpacity);
|
|
121
|
+
if (isColorSelectOpen) {
|
|
122
|
+
dispatch('colorselectmodalclose');
|
|
123
|
+
isColorSelectOpen = false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let opacitySliderRef: HTMLDivElement;
|
|
129
|
+
let isDragging = false;
|
|
130
|
+
let isHueDragging = false;
|
|
131
|
+
|
|
132
|
+
let hexInput: string = value;
|
|
133
|
+
let previewColor: string = value;
|
|
134
|
+
let persistentOpacity: number = 1;
|
|
135
|
+
|
|
136
|
+
let isInternalUpdate = false;
|
|
137
|
+
|
|
138
|
+
// Tracks the position of the opacity indicator for visual feedback
|
|
139
|
+
$: indicatorLeft = `calc(${lastKnownOpacity * 100}% - ${parseInt(opacityLineWidth) / 2}px)`;
|
|
140
|
+
|
|
141
|
+
// Opacity bar dimensions for slider calculations
|
|
142
|
+
const opacityBarWidth = "257px";
|
|
143
|
+
const opacityBarHeight = "33px";
|
|
144
|
+
|
|
145
|
+
// Opacity line aesthetics and dimensions
|
|
146
|
+
const opacityLineStrokeColor = "rgb(0, 0, 0)";
|
|
147
|
+
const opacityLineStrokeOpacity = 0.1;
|
|
148
|
+
const opacityLineWidth = "2px";
|
|
149
|
+
const opacityLineColor = "rgba(255, 255, 255, 0)";
|
|
150
|
+
|
|
151
|
+
// Add to props section
|
|
152
|
+
export let showRing = true;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Generates RGBA border string based on color and opacity
|
|
156
|
+
* @param color - Base color in RGB format
|
|
157
|
+
* @param opacity - Opacity value to apply
|
|
158
|
+
* @returns CSS border string with RGBA color
|
|
159
|
+
*/
|
|
160
|
+
const getRGBABorder = (color: string, opacity: number) => {
|
|
161
|
+
return `1px solid ${color.replace('rgb', 'rgba').replace(')', `, ${opacity})`)}`;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Converts hex color to RGBA while preserving opacity
|
|
166
|
+
* Handles both 3 and 6 character hex codes
|
|
167
|
+
* Returns normalized RGB values (0-1 range)
|
|
168
|
+
* @param hex - Hexadecimal color string
|
|
169
|
+
* @returns RGB object with normalized values
|
|
170
|
+
*/
|
|
171
|
+
function hexToRgba(hex: string): RGB {
|
|
172
|
+
// Remove hash and handle both 3 and 6 character hex
|
|
173
|
+
const digits = hex.replace('#', '');
|
|
174
|
+
let r, g, b;
|
|
175
|
+
|
|
176
|
+
if (digits.length === 3) {
|
|
177
|
+
// For 3-character hex, duplicate each character
|
|
178
|
+
r = parseInt(digits[0] + digits[0], 16) / 255;
|
|
179
|
+
g = parseInt(digits[1] + digits[1], 16) / 255;
|
|
180
|
+
b = parseInt(digits[2] + digits[2], 16) / 255;
|
|
181
|
+
} else {
|
|
182
|
+
// For 6-character hex
|
|
183
|
+
r = parseInt(digits.slice(0, 2), 16) / 255;
|
|
184
|
+
g = parseInt(digits.slice(2, 4), 16) / 255;
|
|
185
|
+
b = parseInt(digits.slice(4, 6), 16) / 255;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
r,
|
|
190
|
+
g,
|
|
191
|
+
b,
|
|
192
|
+
a: lastKnownOpacity
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Converts RGB values to hex format
|
|
198
|
+
* Delegates to transparency handler for consistent color conversion
|
|
199
|
+
* Preserves alpha channel information
|
|
200
|
+
* @param r - Red component (0-1)
|
|
201
|
+
* @param g - Green component (0-1)
|
|
202
|
+
* @param b - Blue component (0-1)
|
|
203
|
+
* @param a - Alpha component (0-1)
|
|
204
|
+
* @returns Hexadecimal color string
|
|
205
|
+
*/
|
|
206
|
+
function rgbaToHex(r: number, g: number, b: number, a: number): string {
|
|
207
|
+
return transparencyHandler.rgbaToHex(r, g, b, a);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Reactive statement to synchronize value changes with internal state
|
|
211
|
+
$: {
|
|
212
|
+
if (value !== hexInput) {
|
|
213
|
+
const newRgb = hexToRgba(value);
|
|
214
|
+
rgb = newRgb; // The handler will preserve the opacity
|
|
215
|
+
hexInput = value;
|
|
216
|
+
previewColor = value;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Updates color state and triggers change events
|
|
222
|
+
* - Validates hex format
|
|
223
|
+
* - Updates preview color
|
|
224
|
+
* - Triggers onChange callback only for valid hex values
|
|
225
|
+
* - Maintains 6-digit hex format
|
|
226
|
+
* @param newColor - New hex color value
|
|
227
|
+
*/
|
|
228
|
+
function updateColor(newColor: string) {
|
|
229
|
+
previewColor = newColor;
|
|
230
|
+
if (/^#[0-9A-Fa-f]{6,8}$/.test(newColor)) {
|
|
231
|
+
value = newColor;
|
|
232
|
+
onChange(newColor);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Handles color changes from the color picker
|
|
238
|
+
* - Updates hex value while preserving opacity
|
|
239
|
+
* - Prevents recursive updates with isInternalUpdate flag
|
|
240
|
+
* - Maintains consistent state across all color representations
|
|
241
|
+
* - Triggers onChange with clean hex value
|
|
242
|
+
*/
|
|
243
|
+
function handleColorChange(eventOrDetail?: CustomEvent | Record<string, any>) {
|
|
244
|
+
try {
|
|
245
|
+
isInternalUpdate = true;
|
|
246
|
+
const detail = eventOrDetail instanceof CustomEvent ? eventOrDetail.detail : eventOrDetail;
|
|
247
|
+
if (detail && detail.rgb) {
|
|
248
|
+
rgb = detail.rgb;
|
|
249
|
+
if (typeof detail.a === 'number') {
|
|
250
|
+
lastKnownOpacity = detail.a;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Only use the 6-character hex without alpha
|
|
254
|
+
const hexValue = `#${Math.round(rgb.r * 255).toString(16).padStart(2, '0')}${Math.round(rgb.g * 255).toString(16).padStart(2, '0')}${Math.round(rgb.b * 255).toString(16).padStart(2, '0')}`;
|
|
255
|
+
value = hexValue;
|
|
256
|
+
hexInput = hexValue;
|
|
257
|
+
previewColor = getRgbaColor();
|
|
258
|
+
onChange(hexValue);
|
|
259
|
+
} finally {
|
|
260
|
+
isInternalUpdate = false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Normalizes hex color input
|
|
266
|
+
* - Handles color names conversion
|
|
267
|
+
* - Validates hex characters
|
|
268
|
+
* - Supports both 3 and 6 character formats
|
|
269
|
+
* - Returns standardized hex format
|
|
270
|
+
* @param input - User input string
|
|
271
|
+
* @returns Normalized hex color string
|
|
272
|
+
*/
|
|
273
|
+
function normalizeHexInput(input: string): string {
|
|
274
|
+
// Remove hash if present and convert to lowercase
|
|
275
|
+
let color = input.toLowerCase().replace('#', '');
|
|
276
|
+
|
|
277
|
+
// Check if it's a color name
|
|
278
|
+
if (colorNames[color]) {
|
|
279
|
+
return colorNames[color];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Only validate the characters, don't expand
|
|
283
|
+
if (!/^[0-9a-f]*$/i.test(color)) {
|
|
284
|
+
return value; // Return current value if invalid characters
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Return the current input with # prefix
|
|
288
|
+
return '#' + color;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Validates if a string is a valid hex color
|
|
293
|
+
* @param hex - Hexadecimal color string
|
|
294
|
+
* @returns Boolean indicating validity
|
|
295
|
+
*/
|
|
296
|
+
function isValidHex(hex: string): boolean {
|
|
297
|
+
const digits = hex.replace('#', '');
|
|
298
|
+
return (digits.length === 3 || digits.length === 6) && /^[0-9a-f]+$/i.test(digits);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let currentSuggestion: AutocompleteResult | null = null;
|
|
302
|
+
let suggestionVisible = false;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Handles input events for hex input field
|
|
306
|
+
* - Processes valid hex inputs
|
|
307
|
+
* - Manages color name suggestions
|
|
308
|
+
* @param event - Input event from the text field
|
|
309
|
+
*/
|
|
310
|
+
function handleHexInput(event: Event) {
|
|
311
|
+
const input = event.target as HTMLInputElement;
|
|
312
|
+
let newValue = input.value.toLowerCase();
|
|
313
|
+
|
|
314
|
+
// validate the new value
|
|
315
|
+
if (newValue.startsWith('#') || /^[0-9a-f]+$/i.test(newValue)) {
|
|
316
|
+
// if the value starts with #, keep it, otherwise add it
|
|
317
|
+
const processedValue = newValue.startsWith('#') ? newValue : `#${newValue}`;
|
|
318
|
+
|
|
319
|
+
// console.log('handleHexInput processedValue:', processedValue);
|
|
320
|
+
|
|
321
|
+
// if new value is a hex color(starts with # and has 3 or 6 characters): call the onChange callback
|
|
322
|
+
const hexRegex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
323
|
+
if (hexRegex.test(processedValue)) {
|
|
324
|
+
// console.log('Valid hex color detected:', processedValue);
|
|
325
|
+
try {
|
|
326
|
+
// set the internal update flag to true
|
|
327
|
+
isInternalUpdate = true;
|
|
328
|
+
|
|
329
|
+
// update the component's value
|
|
330
|
+
value = processedValue;
|
|
331
|
+
|
|
332
|
+
// update the rgb value
|
|
333
|
+
const newRgb = hexToRgba(processedValue);
|
|
334
|
+
rgb = { ...newRgb, a: lastKnownOpacity };
|
|
335
|
+
|
|
336
|
+
// update the preview color
|
|
337
|
+
previewColor = getRgbaColor();
|
|
338
|
+
|
|
339
|
+
// call the onChange callback
|
|
340
|
+
onChange(processedValue);
|
|
341
|
+
} finally {
|
|
342
|
+
isInternalUpdate = false;
|
|
343
|
+
}
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// console.log('handleHexInput newValue:', !/[^a-z\s]/.test(newValue));
|
|
349
|
+
|
|
350
|
+
// if newValue is empty or contains invalid characters, clear the suggestions
|
|
351
|
+
if (!newValue || /[^a-z\s]/.test(newValue)) {
|
|
352
|
+
console.log('Clearing suggestions due to invalid input');
|
|
353
|
+
suggestionVisible = false;
|
|
354
|
+
currentSuggestion = null;
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// if newValue is a valid color name, get the suggestions and show them
|
|
359
|
+
const suggestions = getColorSuggestions(newValue, colorNames);
|
|
360
|
+
console.log('Color suggestions:', { input: newValue, suggestions });
|
|
361
|
+
if (suggestions) {
|
|
362
|
+
currentSuggestion = suggestions;
|
|
363
|
+
suggestionVisible = true;
|
|
364
|
+
} else {
|
|
365
|
+
suggestionVisible = false;
|
|
366
|
+
currentSuggestion = null;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Handles the selection of a color from suggestions
|
|
372
|
+
* @param colorName - Selected color name
|
|
373
|
+
* @param isShiftPressed - Indicates if the shift key was held during selection
|
|
374
|
+
*/
|
|
375
|
+
function handleColorSelection(colorName: string, isShiftPressed: boolean) {
|
|
376
|
+
console.log('Handling color selection:', {
|
|
377
|
+
colorName,
|
|
378
|
+
isShiftPressed,
|
|
379
|
+
currentOpacity: lastKnownOpacity
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
const colorValue = colorNames[colorName];
|
|
383
|
+
value = colorValue;
|
|
384
|
+
const newRgb = hexToRgba(colorValue);
|
|
385
|
+
|
|
386
|
+
// If Shift is pressed, set opacity to 100%
|
|
387
|
+
if (isShiftPressed) {
|
|
388
|
+
console.log('Shift pressed: Setting opacity to 100%');
|
|
389
|
+
lastKnownOpacity = 1;
|
|
390
|
+
rgb = { ...newRgb, a: 1 };
|
|
391
|
+
opacityPercentage = 100;
|
|
392
|
+
} else {
|
|
393
|
+
// Keep current opacity
|
|
394
|
+
rgb = { ...newRgb, a: lastKnownOpacity };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
previewColor = getRgbaColor();
|
|
398
|
+
onChange(colorValue);
|
|
399
|
+
suggestionVisible = false;
|
|
400
|
+
currentSuggestion = null;
|
|
401
|
+
|
|
402
|
+
// Save to recent colors
|
|
403
|
+
saveToRecentColors(colorValue, rgb.a);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Handles keydown events for keyboard navigation and interactions
|
|
408
|
+
* @param event - Keyboard event
|
|
409
|
+
*/
|
|
410
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
411
|
+
const input = event.target as HTMLInputElement;
|
|
412
|
+
const currentInputValue = input.value;
|
|
413
|
+
|
|
414
|
+
// Only prevent default for special keys when popup is open
|
|
415
|
+
if (open && (event.key === 'Shift' || event.key === ' ')) {
|
|
416
|
+
event.preventDefault();
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// console.log('Key pressed:', {
|
|
421
|
+
// key: event.key,
|
|
422
|
+
// actualInputValue: currentInputValue,
|
|
423
|
+
// boundHexInput: hexInput,
|
|
424
|
+
// isEmpty: !currentInputValue.trim(),
|
|
425
|
+
// currentValue: value
|
|
426
|
+
// });
|
|
427
|
+
|
|
428
|
+
if (event.key === 'Escape') {
|
|
429
|
+
event.preventDefault();
|
|
430
|
+
closeAutosuggestions();
|
|
431
|
+
if (!currentInputValue.trim()) {
|
|
432
|
+
restoreCurrentColor();
|
|
433
|
+
}
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (event.key === 'Enter') {
|
|
438
|
+
if (!currentInputValue.trim() || currentInputValue === '#') {
|
|
439
|
+
restoreCurrentColor();
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// if the suggestions are visible, handle the key events to navigate the suggestions
|
|
445
|
+
if (suggestionVisible && currentSuggestion) {
|
|
446
|
+
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
|
|
449
|
+
const direction = event.key === 'ArrowDown' ? 'down' : 'up';
|
|
450
|
+
|
|
451
|
+
const result = getNextSuggestion(currentSuggestion, direction);
|
|
452
|
+
if (result) {
|
|
453
|
+
currentSuggestion = result;
|
|
454
|
+
}
|
|
455
|
+
} else if (event.key === 'Enter') {
|
|
456
|
+
event.preventDefault();
|
|
457
|
+
handleColorSelection(currentSuggestion.suggestion, event.shiftKey);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Handles popover open/close state changes
|
|
464
|
+
* - Saves current color when closing
|
|
465
|
+
* - Closes autosuggestions if opening
|
|
466
|
+
* - Calculates fixed position when opening
|
|
467
|
+
*/
|
|
468
|
+
function handleToggle(isOpen: boolean) {
|
|
469
|
+
// console.warn('[ColorPicker] handleToggle called:', { isOpen, currentOpen: open, instanceId, portalTarget });
|
|
470
|
+
if (isOpen === open) return;
|
|
471
|
+
|
|
472
|
+
if (!isOpen) {
|
|
473
|
+
saveToRecentColors(value, lastKnownOpacity);
|
|
474
|
+
if (isColorSelectOpen) {
|
|
475
|
+
dispatch('colorselectmodalclose');
|
|
476
|
+
isColorSelectOpen = false;
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
// Close all other color pickers before opening this one
|
|
480
|
+
window.dispatchEvent(new CustomEvent('widgetic:close-color-pickers', { detail: instanceId }));
|
|
481
|
+
closeAutosuggestions();
|
|
482
|
+
dispatch('colorselectmodalopen');
|
|
483
|
+
isColorSelectOpen = true;
|
|
484
|
+
updatePopoverPosition();
|
|
485
|
+
// Re-position after portal is mounted and layout stabilizes
|
|
486
|
+
tick().then(() => {
|
|
487
|
+
requestAnimationFrame(updatePopoverPosition);
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
open = isOpen;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Closes the color picker dropdown and autosuggestions
|
|
496
|
+
*/
|
|
497
|
+
function closeDropdown() {
|
|
498
|
+
if (open) {
|
|
499
|
+
handleToggle(false);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
closeAutosuggestions();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
function closeAutosuggestions() {
|
|
507
|
+
// Close autosuggestions if they're open
|
|
508
|
+
if (suggestionVisible) {
|
|
509
|
+
suggestionVisible = false;
|
|
510
|
+
currentSuggestion = null;
|
|
511
|
+
// Set the input to the current hex value
|
|
512
|
+
hexInput = value.slice(0, 7); // Only take the 6-character hex
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Handles scroll events to save color and close dropdown if necessary
|
|
518
|
+
* @param event - Scroll event
|
|
519
|
+
*/
|
|
520
|
+
function handleScroll(event: Event) {
|
|
521
|
+
// Don't close if scrolling happens within allowed elements
|
|
522
|
+
if ((event.target as HTMLElement).closest('[data-allow-scroll="true"]')) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Close both suggestions and color picker
|
|
527
|
+
if (suggestionVisible || open) {
|
|
528
|
+
suggestionVisible = false;
|
|
529
|
+
currentSuggestion = null;
|
|
530
|
+
closeDropdown();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Manages opacity slider interactions
|
|
536
|
+
* - Handles mouse drag events
|
|
537
|
+
* - Updates opacity state
|
|
538
|
+
* - Maintains color consistency
|
|
539
|
+
* - Prevents event bubbling
|
|
540
|
+
* @param event - Mouse event
|
|
541
|
+
*/
|
|
542
|
+
function handleOpacityMouseDown(event: MouseEvent) {
|
|
543
|
+
isDragging = true;
|
|
544
|
+
document.body.style.cursor = 'grabbing';
|
|
545
|
+
updateOpacity(event);
|
|
546
|
+
|
|
547
|
+
const handleMouseMove = (e: MouseEvent) => {
|
|
548
|
+
if (isDragging) {
|
|
549
|
+
updateOpacity(e);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
const handleMouseUp = () => {
|
|
554
|
+
isDragging = false;
|
|
555
|
+
document.body.style.cursor = '';
|
|
556
|
+
window.removeEventListener('mousemove', handleMouseMove);
|
|
557
|
+
window.removeEventListener('mouseup', handleMouseUp);
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
window.addEventListener('mousemove', handleMouseMove);
|
|
561
|
+
window.addEventListener('mouseup', handleMouseUp);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Reactive statement to handle external value changes
|
|
565
|
+
$: {
|
|
566
|
+
if (value !== hexInput && !isInternalUpdate) {
|
|
567
|
+
const newRgb = hexToRgba(value);
|
|
568
|
+
rgb = { ...newRgb, a: lastKnownOpacity }; // Preserve opacity
|
|
569
|
+
hexInput = value;
|
|
570
|
+
previewColor = value;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Reactive statement to update the style of the opacity indicator
|
|
575
|
+
$: verticalLineStyle = `
|
|
576
|
+
left: ${indicatorLeft};
|
|
577
|
+
width: ${opacityLineWidth};
|
|
578
|
+
background-color: ${opacityLineColor};
|
|
579
|
+
`;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Applies opacity to a given RGB color
|
|
583
|
+
* @param color - RGB color object
|
|
584
|
+
* @param opacity - Opacity value to apply
|
|
585
|
+
* @returns RGB color object with updated opacity
|
|
586
|
+
*/
|
|
587
|
+
function applyOpacityToColor(color: RGB, opacity: number): RGB {
|
|
588
|
+
return {
|
|
589
|
+
r: color.r,
|
|
590
|
+
g: color.g,
|
|
591
|
+
b: color.b,
|
|
592
|
+
a: opacity
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Reactive statement to apply opacity changes
|
|
597
|
+
$: {
|
|
598
|
+
if (value !== hexInput && !isInternalUpdate) {
|
|
599
|
+
const newRgb = hexToRgba(value);
|
|
600
|
+
rgb = applyOpacityToColor(newRgb, lastKnownOpacity);
|
|
601
|
+
hexInput = value;
|
|
602
|
+
previewColor = rgbaToHex(rgb.r, rgb.g, rgb.b, lastKnownOpacity);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Updates opacity based on slider position
|
|
608
|
+
* - Calculates normalized opacity value
|
|
609
|
+
* - Updates all color representations
|
|
610
|
+
* - Triggers change callbacks
|
|
611
|
+
* - Handles edge cases
|
|
612
|
+
* @param event - Mouse event from slider interaction
|
|
613
|
+
*/
|
|
614
|
+
function updateOpacity(event: MouseEvent) {
|
|
615
|
+
if (!opacitySliderRef) return;
|
|
616
|
+
|
|
617
|
+
const rect = opacitySliderRef.getBoundingClientRect();
|
|
618
|
+
const x = event.clientX - rect.left;
|
|
619
|
+
const width = rect.width;
|
|
620
|
+
|
|
621
|
+
let newOpacity = Math.max(0, Math.min(x, width)) / width;
|
|
622
|
+
newOpacity = Math.round(newOpacity * 100) / 100;
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
isInternalUpdate = true;
|
|
626
|
+
opacity = newOpacity; // This will trigger the reactive statement
|
|
627
|
+
rgb = { ...rgb, a: newOpacity };
|
|
628
|
+
lastKnownOpacity = newOpacity;
|
|
629
|
+
const hexValue = `#${Math.round(rgb.r * 255).toString(16).padStart(2, '0')}${Math.round(rgb.g * 255).toString(16).padStart(2, '0')}${Math.round(rgb.b * 255).toString(16).padStart(2, '0')}`;
|
|
630
|
+
value = hexValue;
|
|
631
|
+
hexInput = hexValue;
|
|
632
|
+
previewColor = getRgbaColor();
|
|
633
|
+
onChange(hexValue);
|
|
634
|
+
onOpacityChange(newOpacity); // Call the opacity change handler
|
|
635
|
+
} finally {
|
|
636
|
+
isInternalUpdate = false;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Updates opacity from numeric input
|
|
642
|
+
* - Validates input range (0-100)
|
|
643
|
+
* - Converts percentage to decimal
|
|
644
|
+
* - Updates all color states
|
|
645
|
+
* - Maintains color consistency
|
|
646
|
+
* - Prevents invalid states
|
|
647
|
+
* @param event - Input event from the numeric opacity field
|
|
648
|
+
*/
|
|
649
|
+
function updateOpacityFromInput(event: Event) {
|
|
650
|
+
const input = event.target as HTMLInputElement;
|
|
651
|
+
input.value = input.value.replace(/[^\d]/g, '').slice(0, 3);
|
|
652
|
+
|
|
653
|
+
let num = parseInt(input.value);
|
|
654
|
+
if (isNaN(num) || input.value === '') num = 0;
|
|
655
|
+
if (num > 100) num = 100;
|
|
656
|
+
|
|
657
|
+
input.value = num.toString();
|
|
658
|
+
opacityPercentage = num;
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
isInternalUpdate = true;
|
|
662
|
+
lastKnownOpacity = num / 100;
|
|
663
|
+
rgb = { ...rgb, a: num / 100 };
|
|
664
|
+
// Only use the 6-character hex without alpha
|
|
665
|
+
const hexValue = `#${Math.round(rgb.r * 255).toString(16).padStart(2, '0')}${Math.round(rgb.g * 255).toString(16).padStart(2, '0')}${Math.round(rgb.b * 255).toString(16).padStart(2, '0')}`;
|
|
666
|
+
value = hexValue;
|
|
667
|
+
hexInput = hexValue;
|
|
668
|
+
previewColor = getRgbaColor();
|
|
669
|
+
onChange(hexValue);
|
|
670
|
+
} finally {
|
|
671
|
+
isInternalUpdate = false;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Reactive statement to calculate opacity percentage
|
|
676
|
+
$: opacityPercentage = Math.round(lastKnownOpacity * 100);
|
|
677
|
+
|
|
678
|
+
let lastKnownHue = 0;
|
|
679
|
+
let colorSelectRef: ColorSelect; // Reference to ColorSelect component
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Converts HSV to RGB values
|
|
683
|
+
* @param h - Hue value
|
|
684
|
+
* @param s - Saturation value
|
|
685
|
+
* @param v - Value (brightness) value
|
|
686
|
+
* @returns RGB color object with normalized values
|
|
687
|
+
*/
|
|
688
|
+
function hsvToRgb(h: number, s: number, v: number) {
|
|
689
|
+
const f = (n: number, k = (n + h/60) % 6) => v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
|
|
690
|
+
return {
|
|
691
|
+
r: Math.round(f(5) * 255) / 255,
|
|
692
|
+
g: Math.round(f(3) * 255) / 255,
|
|
693
|
+
b: Math.round(f(1) * 255) / 255,
|
|
694
|
+
a: lastKnownOpacity
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Logs color-related events for debugging purposes
|
|
700
|
+
* @param event - Custom event from color picker interactions
|
|
701
|
+
*/
|
|
702
|
+
function onColorEvent(event: CustomEvent) {
|
|
703
|
+
// Safe logging that won't throw errors
|
|
704
|
+
console.log('Color event received:', {
|
|
705
|
+
type: event.type,
|
|
706
|
+
rgb: event.detail?.rgb || rgb,
|
|
707
|
+
mode: event.detail?.mode,
|
|
708
|
+
rawEvent: {
|
|
709
|
+
detail: event.detail,
|
|
710
|
+
target: event.target ? 'exists' : 'null',
|
|
711
|
+
currentTarget: event.currentTarget ? 'exists' : 'null'
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Handles direct hue changes from the color picker
|
|
718
|
+
* @param event - Custom event containing hue information
|
|
719
|
+
*/
|
|
720
|
+
function handleHueChange(event: CustomEvent) {
|
|
721
|
+
console.log('Hue change event:', event.detail);
|
|
722
|
+
try {
|
|
723
|
+
isInternalUpdate = true;
|
|
724
|
+
lastKnownHue = event.detail.h;
|
|
725
|
+
|
|
726
|
+
// Convert HSV to RGB using current saturation and value
|
|
727
|
+
const newRgb = hsvToRgb(
|
|
728
|
+
lastKnownHue,
|
|
729
|
+
1, // Full saturation for testing
|
|
730
|
+
1 // Full value for testing
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
rgb = newRgb;
|
|
734
|
+
handleColorChange();
|
|
735
|
+
|
|
736
|
+
console.log('After hue change:', {
|
|
737
|
+
hue: lastKnownHue,
|
|
738
|
+
rgb: newRgb,
|
|
739
|
+
hexValue: rgbaToHex(newRgb.r, newRgb.g, newRgb.b, lastKnownOpacity)
|
|
740
|
+
});
|
|
741
|
+
} finally {
|
|
742
|
+
isInternalUpdate = false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
let dragState = {
|
|
747
|
+
isHueDragging: false,
|
|
748
|
+
isOpacityDragging: false,
|
|
749
|
+
isColorDragging: false
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Manages drag state across different interactions
|
|
754
|
+
* Prevents race conditions and maintains consistent state
|
|
755
|
+
* Handles cleanup on component destruction
|
|
756
|
+
* @param event - Mouse event from global interactions
|
|
757
|
+
*/
|
|
758
|
+
function handleGlobalMouseUp(event: MouseEvent) {
|
|
759
|
+
if (!open) return;
|
|
760
|
+
|
|
761
|
+
// console.log('Global mouseup triggered', dragState);
|
|
762
|
+
if (dragState.isHueDragging || dragState.isOpacityDragging || dragState.isColorDragging) {
|
|
763
|
+
dragState = {
|
|
764
|
+
isHueDragging: false,
|
|
765
|
+
isOpacityDragging: false,
|
|
766
|
+
isColorDragging: false
|
|
767
|
+
};
|
|
768
|
+
handleColorChange();
|
|
769
|
+
// console.log('Dragging stopped');
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Handles global mouse move events during dragging
|
|
775
|
+
* @param event - Mouse move event
|
|
776
|
+
*/
|
|
777
|
+
function handleGlobalMouseMove(event: MouseEvent) {
|
|
778
|
+
if (dragState.isHueDragging || dragState.isOpacityDragging || dragState.isColorDragging) {
|
|
779
|
+
// console.log('Moving while dragging:', {
|
|
780
|
+
// type: dragState.isHueDragging ? 'hue' :
|
|
781
|
+
// dragState.isOpacityDragging ? 'opacity' : 'color',
|
|
782
|
+
// event: event
|
|
783
|
+
// });
|
|
784
|
+
handleColorChange();
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Determines the type of drag based on mouse interaction
|
|
790
|
+
* @param event - Mouse down event initiating drag
|
|
791
|
+
*/
|
|
792
|
+
function handleDragStart(event: MouseEvent) {
|
|
793
|
+
const target = event.target as HTMLElement;
|
|
794
|
+
const rect = target.getBoundingClientRect();
|
|
795
|
+
const parentRect = target.parentElement?.getBoundingClientRect();
|
|
796
|
+
|
|
797
|
+
// Log all relevant information for debugging
|
|
798
|
+
// console.log('Mousedown details:', {
|
|
799
|
+
// element: target,
|
|
800
|
+
// rect: rect,
|
|
801
|
+
// parentRect: parentRect,
|
|
802
|
+
// style: target.getAttribute('style'),
|
|
803
|
+
// parentStyle: target.parentElement?.getAttribute('style')
|
|
804
|
+
// });
|
|
805
|
+
|
|
806
|
+
// Check if it's a canvas element
|
|
807
|
+
if (target.tagName.toLowerCase() === 'canvas') {
|
|
808
|
+
// Get the computed style to check positioning
|
|
809
|
+
const computedStyle = window.getComputedStyle(target);
|
|
810
|
+
const top = parseInt(computedStyle.top);
|
|
811
|
+
|
|
812
|
+
if (rect.width === 31 && rect.height === 256) {
|
|
813
|
+
if (top < 50) { // Hue slider is typically at the top
|
|
814
|
+
dragState.isHueDragging = true;
|
|
815
|
+
console.log('Hue drag started (top slider)');
|
|
816
|
+
} else { // Opacity slider is typically at the bottom
|
|
817
|
+
dragState.isOpacityDragging = true;
|
|
818
|
+
console.log('Opacity drag started (bottom slider)');
|
|
819
|
+
}
|
|
820
|
+
} else {
|
|
821
|
+
dragState.isColorDragging = true;
|
|
822
|
+
// console.log('Color drag started (main area)');
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// console.log('Current drag state:', dragState);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Initializes component state
|
|
831
|
+
* - Normalizes initial color value
|
|
832
|
+
* - Sets up event listeners
|
|
833
|
+
* - Configures opacity handling
|
|
834
|
+
* - Initializes recent colors
|
|
835
|
+
*/
|
|
836
|
+
function initializeColor() {
|
|
837
|
+
const normalizedHex = normalizeHexInput(value);
|
|
838
|
+
if (normalizedHex.startsWith('#')) {
|
|
839
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(normalizedHex);
|
|
840
|
+
if (result) {
|
|
841
|
+
const r = parseInt(result[1], 16) / 255;
|
|
842
|
+
const g = parseInt(result[2], 16) / 255;
|
|
843
|
+
const b = parseInt(result[3], 16) / 255;
|
|
844
|
+
|
|
845
|
+
rgb = {
|
|
846
|
+
// r: r === 0 ? 0.000001 : r, // Prevent zero opacity issues
|
|
847
|
+
r: r,
|
|
848
|
+
g: g,
|
|
849
|
+
b: b,
|
|
850
|
+
a: lastKnownOpacity
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
// Set initial display values
|
|
854
|
+
hexInput = normalizedHex;
|
|
855
|
+
previewColor = getRgbaColor();
|
|
856
|
+
|
|
857
|
+
// console.log('Color initialized:', {
|
|
858
|
+
// hex: normalizedHex,
|
|
859
|
+
// rgb,
|
|
860
|
+
// opacity: lastKnownOpacity,
|
|
861
|
+
// percentage: opacityPercentage
|
|
862
|
+
// });
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Generates an RGBA color string for CSS
|
|
869
|
+
* @returns RGBA color string based on current RGB and opacity
|
|
870
|
+
*/
|
|
871
|
+
function getRgbaColor() {
|
|
872
|
+
const r = Math.round(rgb.r * 255);
|
|
873
|
+
const g = Math.round(rgb.g * 255);
|
|
874
|
+
const b = Math.round(rgb.b * 255);
|
|
875
|
+
return `rgba(${r}, ${g}, ${b}, ${lastKnownOpacity})`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Make previewColor reactive so it updates instantly whenever rgb or opacity change
|
|
879
|
+
$: previewColor = getRgbaColor();
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Handles keydown events within the popover for modifier keys
|
|
883
|
+
* @param event - Keyboard event
|
|
884
|
+
*/
|
|
885
|
+
function handlePopupKeyDown(event: KeyboardEvent) {
|
|
886
|
+
// Only handle keys if the popup is open
|
|
887
|
+
if (!open) return;
|
|
888
|
+
|
|
889
|
+
if (event.key === 'Shift' || event.key === ' ') {
|
|
890
|
+
event.preventDefault(); // Prevent space from scrolling
|
|
891
|
+
|
|
892
|
+
if (event.key === 'Shift') {
|
|
893
|
+
isShiftPressed = true;
|
|
894
|
+
} else if (event.key === ' ') {
|
|
895
|
+
isSpacePressed = true;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (recentColors.length === 0) {
|
|
899
|
+
showNoRecentMessage = true;
|
|
900
|
+
setTimeout(() => {
|
|
901
|
+
showNoRecentMessage = false;
|
|
902
|
+
}, 2000);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Handles keyup events within the popover to reset modifier keys
|
|
909
|
+
* @param event - Keyboard event
|
|
910
|
+
*/
|
|
911
|
+
function handlePopupKeyUp(event: KeyboardEvent) {
|
|
912
|
+
// Only handle keys if the popup is open
|
|
913
|
+
if (!open) return;
|
|
914
|
+
|
|
915
|
+
if (event.key === 'Shift' || event.key === ' ') {
|
|
916
|
+
if (event.key === 'Shift') {
|
|
917
|
+
isShiftPressed = false;
|
|
918
|
+
} else if (event.key === ' ') {
|
|
919
|
+
isSpacePressed = false;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Sets up event listeners when the component mounts
|
|
925
|
+
onMount(() => {
|
|
926
|
+
initializeColor();
|
|
927
|
+
window.addEventListener('mouseup', handleGlobalMouseUp);
|
|
928
|
+
window.addEventListener('mousemove', handleGlobalMouseMove);
|
|
929
|
+
window.addEventListener('keydown', handlePopupKeyDown);
|
|
930
|
+
window.addEventListener('keyup', handlePopupKeyUp);
|
|
931
|
+
window.addEventListener('scroll', handleScroll, true);
|
|
932
|
+
window.addEventListener('widgetic:close-color-pickers', onCloseOtherPickers as EventListener);
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
// Cleans up event listeners when the component is destroyed
|
|
936
|
+
onDestroy(() => {
|
|
937
|
+
window.removeEventListener('mouseup', handleGlobalMouseUp);
|
|
938
|
+
window.removeEventListener('mousemove', handleGlobalMouseMove);
|
|
939
|
+
window.removeEventListener('keydown', handlePopupKeyDown);
|
|
940
|
+
window.removeEventListener('keyup', handlePopupKeyUp);
|
|
941
|
+
window.removeEventListener('scroll', handleScroll, true);
|
|
942
|
+
window.removeEventListener('widgetic:close-color-pickers', onCloseOtherPickers as EventListener);
|
|
943
|
+
});
|
|
944
|
+
|
|
945
|
+
// Then handle opacity updates
|
|
946
|
+
$: {
|
|
947
|
+
if (value) {
|
|
948
|
+
if (value.startsWith('rgba')) {
|
|
949
|
+
const matches = value.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/);
|
|
950
|
+
if (matches) {
|
|
951
|
+
const [_, r, g, b, a] = matches;
|
|
952
|
+
rgb = {
|
|
953
|
+
r: parseInt(r) / 255,
|
|
954
|
+
g: parseInt(g) / 255,
|
|
955
|
+
b: parseInt(b) / 255,
|
|
956
|
+
a: parseFloat(a)
|
|
957
|
+
};
|
|
958
|
+
opacityValue = parseFloat(a);
|
|
959
|
+
opacityPercentage = Math.round(opacityValue * 100);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
else if (value.startsWith('#') && value.length === 9) {
|
|
963
|
+
const alpha = parseInt(value.slice(7), 16);
|
|
964
|
+
opacityValue = alpha / 255;
|
|
965
|
+
opacityPercentage = Math.round(opacityValue * 100);
|
|
966
|
+
rgb.a = opacityValue;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Saves a color to the recent colors list
|
|
973
|
+
* - Prevents duplicates by checking the most recent entry
|
|
974
|
+
* - Maintains storage limits through utility functions
|
|
975
|
+
* @param color - Hexadecimal color string
|
|
976
|
+
* @param alpha - Opacity value of the color
|
|
977
|
+
*/
|
|
978
|
+
function saveToRecentColors(color: string, alpha: number) {
|
|
979
|
+
if (!saveRecentColors) {
|
|
980
|
+
console.log('Recent colors saving is disabled');
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Check if this exact color+alpha combination is already the most recent
|
|
985
|
+
const currentRecents = recentColorsUtil.getRecentColors();
|
|
986
|
+
// console.log('Current recent colors:', currentRecents);
|
|
987
|
+
|
|
988
|
+
const mostRecent = currentRecents[0];
|
|
989
|
+
|
|
990
|
+
if (mostRecent &&
|
|
991
|
+
mostRecent.color.toLowerCase() === color.toLowerCase() &&
|
|
992
|
+
mostRecent.alpha === alpha) {
|
|
993
|
+
console.log('Color already most recent, skipping save:', { color, alpha });
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// console.log('Saving new color to recent colors:', { color, alpha });
|
|
998
|
+
recentColors = recentColorsUtil.addRecentColor(color, alpha);
|
|
999
|
+
// console.log('Updated recent colors list:', recentColors);
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Handles the selection of a color name from suggestions
|
|
1004
|
+
* @param colorName - Selected color name
|
|
1005
|
+
*/
|
|
1006
|
+
function handleColorNameSelection(colorName: string) {
|
|
1007
|
+
const colorValue = colorNames[colorName];
|
|
1008
|
+
value = colorValue;
|
|
1009
|
+
const newRgb = hexToRgba(colorValue);
|
|
1010
|
+
rgb = { ...newRgb, a: lastKnownOpacity };
|
|
1011
|
+
previewColor = getRgbaColor();
|
|
1012
|
+
onChange(colorValue);
|
|
1013
|
+
suggestionVisible = false;
|
|
1014
|
+
currentSuggestion = null;
|
|
1015
|
+
|
|
1016
|
+
// Save the selected color name
|
|
1017
|
+
saveToRecentColors(colorValue, lastKnownOpacity);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Handles click events on color suggestions
|
|
1022
|
+
* @param match - The matched color name clicked by the user
|
|
1023
|
+
*/
|
|
1024
|
+
function handleSuggestionClick(match: string) {
|
|
1025
|
+
console.log('Color name selected:', match);
|
|
1026
|
+
hexInput = match;
|
|
1027
|
+
const colorValue = colorNames[match];
|
|
1028
|
+
console.log('Converting color name to hex:', { name: match, hex: colorValue });
|
|
1029
|
+
|
|
1030
|
+
value = colorValue;
|
|
1031
|
+
const newRgb = hexToRgba(colorValue);
|
|
1032
|
+
rgb = { ...newRgb, a: lastKnownOpacity };
|
|
1033
|
+
previewColor = getRgbaColor();
|
|
1034
|
+
onChange(colorValue);
|
|
1035
|
+
suggestionVisible = false;
|
|
1036
|
+
currentSuggestion = null;
|
|
1037
|
+
|
|
1038
|
+
// Save the hex value of the selected color name
|
|
1039
|
+
saveToRecentColors(colorValue, lastKnownOpacity);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/**
|
|
1043
|
+
* Handles blur events on the hex input field to restore color if input is empty
|
|
1044
|
+
* @param event - Focus event from the input field
|
|
1045
|
+
*/
|
|
1046
|
+
function handleInputBlur(event: FocusEvent) {
|
|
1047
|
+
// Get the related target (element receiving focus)
|
|
1048
|
+
const relatedTarget = event.relatedTarget as HTMLElement;
|
|
1049
|
+
|
|
1050
|
+
// Don't close if focus is moving to a suggestion item
|
|
1051
|
+
if (relatedTarget?.closest('.color-suggestions')) {
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
const input = event.target as HTMLInputElement;
|
|
1056
|
+
const currentInputValue = input.value;
|
|
1057
|
+
|
|
1058
|
+
// Close suggestions
|
|
1059
|
+
closeAutosuggestions();
|
|
1060
|
+
|
|
1061
|
+
// Check actual input value
|
|
1062
|
+
if (!currentInputValue.trim()) {
|
|
1063
|
+
restoreCurrentColor();
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
let inputRef: HTMLDivElement;
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Restores the current color value when input is invalid or empty
|
|
1071
|
+
* - Resets the hex input to the last valid color value
|
|
1072
|
+
* - Forces the input field to update and regain focus
|
|
1073
|
+
*/
|
|
1074
|
+
function restoreCurrentColor() {
|
|
1075
|
+
console.log('Restoring current color:', {
|
|
1076
|
+
currentValue: value,
|
|
1077
|
+
hexToRestore: value.slice(0, 7),
|
|
1078
|
+
currentHexInput: hexInput
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
if (value && value.startsWith('#')) {
|
|
1082
|
+
const colorToRestore = value.slice(0, 7);
|
|
1083
|
+
|
|
1084
|
+
// Force immediate update
|
|
1085
|
+
hexInput = colorToRestore;
|
|
1086
|
+
value = colorToRestore;
|
|
1087
|
+
|
|
1088
|
+
// Force input focus and update
|
|
1089
|
+
setTimeout(() => {
|
|
1090
|
+
const inputElement = inputRef?.querySelector('input') as HTMLInputElement;
|
|
1091
|
+
if (inputElement) {
|
|
1092
|
+
inputElement.value = colorToRestore;
|
|
1093
|
+
inputElement.focus();
|
|
1094
|
+
inputElement.blur();
|
|
1095
|
+
}
|
|
1096
|
+
}, 0);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Exported onFocus handler with default no-op
|
|
1101
|
+
export let onFocus: () => void = () => {};
|
|
1102
|
+
|
|
1103
|
+
// Sets autofocus on mount if enabled
|
|
1104
|
+
onMount(() => {
|
|
1105
|
+
if (autofocus && inputRef) {
|
|
1106
|
+
inputRef.focus();
|
|
1107
|
+
console.log('ColorPickerInputController focused');
|
|
1108
|
+
}
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Focus method to programmatically focus the input field
|
|
1113
|
+
*/
|
|
1114
|
+
export function focus() {
|
|
1115
|
+
if (inputRef) {
|
|
1116
|
+
inputRef.focus();
|
|
1117
|
+
console.log('ColorPickerInputController focus method called');
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Popover visibility is now handled by {#if open} conditional rendering
|
|
1122
|
+
|
|
1123
|
+
// Exported opacity change handler with default no-op
|
|
1124
|
+
export let onOpacityChange: (opacity: number) => void = () => {};
|
|
1125
|
+
|
|
1126
|
+
// Add these variables to track input position
|
|
1127
|
+
let inputComponent: any;
|
|
1128
|
+
let inputRect: DOMRect | null = null;
|
|
1129
|
+
|
|
1130
|
+
// Track trigger position for fixed-position popover
|
|
1131
|
+
let triggerRef: HTMLDivElement;
|
|
1132
|
+
let popoverStyle = '';
|
|
1133
|
+
|
|
1134
|
+
function updatePopoverPosition() {
|
|
1135
|
+
if (!triggerRef) return;
|
|
1136
|
+
const rect = triggerRef.getBoundingClientRect();
|
|
1137
|
+
// Guard: if trigger hasn't been laid out yet, defer
|
|
1138
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
1139
|
+
requestAnimationFrame(updatePopoverPosition);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const popoverHeight = 420;
|
|
1143
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
1144
|
+
const spaceAbove = rect.top;
|
|
1145
|
+
const openAbove = spaceBelow < popoverHeight + 8 && spaceAbove > popoverHeight + 8;
|
|
1146
|
+
const top = openAbove
|
|
1147
|
+
? Math.round(rect.top - popoverHeight - 4)
|
|
1148
|
+
: Math.round(rect.bottom + 4);
|
|
1149
|
+
// Align popup's right edge with the right edge of the full input container
|
|
1150
|
+
const inputContainer = triggerRef.closest('.color-input-main-container') as HTMLElement | null;
|
|
1151
|
+
const rightRef = inputContainer ? inputContainer.getBoundingClientRect().right : rect.right;
|
|
1152
|
+
const right = Math.round(window.innerWidth - rightRef);
|
|
1153
|
+
popoverStyle = `top: ${top}px; right: ${right}px;`;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Position is calculated in handleToggle after tick()
|
|
1157
|
+
|
|
1158
|
+
// Update the position function to get the actual input element
|
|
1159
|
+
function updateInputPosition() {
|
|
1160
|
+
// Get the actual input element from the component
|
|
1161
|
+
// console.log('updateInputPosition', inputRef);
|
|
1162
|
+
const inputElement = inputRef?.querySelector('input');
|
|
1163
|
+
if (inputElement) {
|
|
1164
|
+
inputRect = inputElement.getBoundingClientRect();
|
|
1165
|
+
// console.log('inputRect', inputRect);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Update the suggestions position when they become visible
|
|
1170
|
+
$: if (suggestionVisible) {
|
|
1171
|
+
updateInputPosition();
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
// Close suggestions when picker is closed
|
|
1175
|
+
$: if (!open) {
|
|
1176
|
+
suggestionVisible = false;
|
|
1177
|
+
currentSuggestion = null;
|
|
1178
|
+
if (isColorSelectOpen) {
|
|
1179
|
+
dispatch('colorselectmodalclose');
|
|
1180
|
+
isColorSelectOpen = false;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// Add this near the top with other state declarations
|
|
1185
|
+
let isColorSelectOpen = false;
|
|
1186
|
+
|
|
1187
|
+
// Add these computed styles for layout
|
|
1188
|
+
$: opacityBarWidthPx = `${pickerSize}px`;
|
|
1189
|
+
$: recentColorsMaxWidth = `${pickerSize + sliderWidth}px`;
|
|
1190
|
+
</script>
|
|
1191
|
+
|
|
1192
|
+
<!-- Color Picker Input Controller Container -->
|
|
1193
|
+
<div class="color-picker-input-controller-ct flex flex-col gap-2 widgetic-colors">
|
|
1194
|
+
<div class="flex items-center w-full relative">
|
|
1195
|
+
|
|
1196
|
+
<!-- Input Controller Header -->
|
|
1197
|
+
<div class="input-controller-header-ct flex-1 min-w-0">
|
|
1198
|
+
<InputControllerHeader {label} helpText={helpText} />
|
|
1199
|
+
</div>
|
|
1200
|
+
|
|
1201
|
+
<!-- Input Controller Container -->
|
|
1202
|
+
<div class="input-controller-ct absolute right-0">
|
|
1203
|
+
<div class="color-input-main-container rounded-large flex items-center transition-colors duration-200"
|
|
1204
|
+
style="height: 48px; width: 185px; background: hsl(var(--grey1)); {showRing ? 'border: 1px solid hsl(var(--grey2));' : ''}">
|
|
1205
|
+
<div class="flex items-center w-full justify-between" style="padding-right: 12px;">
|
|
1206
|
+
|
|
1207
|
+
<!-- Hex Input Container -->
|
|
1208
|
+
<div class="hex-input-container flex-1" bind:this={inputRef}>
|
|
1209
|
+
<Input
|
|
1210
|
+
bind:this={inputComponent}
|
|
1211
|
+
type="text"
|
|
1212
|
+
class="h-9 border-0 bg-transparent subtext text-grey7 placeholder:text-grey3 focus-visible:ring-0 focus-visible:ring-offset-0 pl-4"
|
|
1213
|
+
value={hexInput}
|
|
1214
|
+
onfocus={(e) => {
|
|
1215
|
+
dispatch('focus', e);
|
|
1216
|
+
onFocus();
|
|
1217
|
+
}}
|
|
1218
|
+
onblur={(e) => {
|
|
1219
|
+
dispatch('blur', e);
|
|
1220
|
+
const input = e.target as HTMLInputElement;
|
|
1221
|
+
if (!input.value.trim()) {
|
|
1222
|
+
restoreCurrentColor();
|
|
1223
|
+
} else {
|
|
1224
|
+
handleInputBlur(e);
|
|
1225
|
+
}
|
|
1226
|
+
}}
|
|
1227
|
+
oninput={(e) => {
|
|
1228
|
+
const input = e.target as HTMLInputElement;
|
|
1229
|
+
if (!input.value) {
|
|
1230
|
+
hexInput = '';
|
|
1231
|
+
}
|
|
1232
|
+
handleHexInput(e);
|
|
1233
|
+
updateInputPosition();
|
|
1234
|
+
}}
|
|
1235
|
+
onkeydown={(e) => {
|
|
1236
|
+
const input = e.target as HTMLInputElement;
|
|
1237
|
+
if ((e.key === 'Enter' || e.key === 'Escape') && !input.value.trim()) {
|
|
1238
|
+
e.preventDefault();
|
|
1239
|
+
restoreCurrentColor();
|
|
1240
|
+
} else {
|
|
1241
|
+
handleKeyDown(e);
|
|
1242
|
+
}
|
|
1243
|
+
}}
|
|
1244
|
+
/>
|
|
1245
|
+
</div>
|
|
1246
|
+
|
|
1247
|
+
<!-- Color Preview Button -->
|
|
1248
|
+
<div class="color-preview-button-container relative flex items-center" style="height: 48px;">
|
|
1249
|
+
<div class="color-picker-button-content-ct relative">
|
|
1250
|
+
<!-- Color Picker Trigger (manual, no bits-ui Popover) -->
|
|
1251
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1252
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1253
|
+
<div bind:this={triggerRef}
|
|
1254
|
+
class="color-preview-button flex items-center justify-center rounded-small cursor-pointer relative overflow-hidden {showRing ? 'ring-1 ring-grey2' : ''}"
|
|
1255
|
+
style="width: 24px; height: 24px; min-width: 24px;"
|
|
1256
|
+
role="button"
|
|
1257
|
+
tabindex="0"
|
|
1258
|
+
onclick={(e) => { e.stopPropagation(); handleToggle(!open); }}
|
|
1259
|
+
>
|
|
1260
|
+
<CheckerBoardBackground class="absolute inset-0 w-full h-full pointer-events-none rounded-[inherit]" />
|
|
1261
|
+
<div class="absolute inset-0 w-full h-full rounded-[inherit] pointer-events-none" style="background-color: {previewColor};"></div>
|
|
1262
|
+
</div>
|
|
1263
|
+
|
|
1264
|
+
<!-- Color Suggestions Portal -->
|
|
1265
|
+
{#if suggestionVisible && currentSuggestion}
|
|
1266
|
+
<Portal target={portalTarget} class="color-suggestions-portal">
|
|
1267
|
+
{#if currentSuggestion.allMatches.length > 0 && inputRect}
|
|
1268
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1269
|
+
<div
|
|
1270
|
+
class="color-suggestions fixed bg-white border rounded-md shadow-lg overflow-y-auto"
|
|
1271
|
+
style="
|
|
1272
|
+
pointer-events: auto !important;
|
|
1273
|
+
z-index: 99999;
|
|
1274
|
+
max-height: 12rem;
|
|
1275
|
+
top: {inputRect.bottom + 4}px;
|
|
1276
|
+
left: {inputRect.left}px;
|
|
1277
|
+
width: {inputRect.width}px;
|
|
1278
|
+
"
|
|
1279
|
+
data-allow-scroll="true"
|
|
1280
|
+
onscroll={(e) => e.stopPropagation()}
|
|
1281
|
+
onmousedown={(event) => {
|
|
1282
|
+
event.preventDefault();
|
|
1283
|
+
event.stopPropagation();
|
|
1284
|
+
const target = event.target as HTMLElement;
|
|
1285
|
+
if (target.closest('.color-suggestion-item')) {
|
|
1286
|
+
event.preventDefault();
|
|
1287
|
+
}
|
|
1288
|
+
}}
|
|
1289
|
+
onfocusout={(event) => {
|
|
1290
|
+
const relatedTarget = event.relatedTarget as HTMLElement;
|
|
1291
|
+
// Only close if focus is moving outside the suggestions
|
|
1292
|
+
if (!relatedTarget?.closest('.color-suggestions')) {
|
|
1293
|
+
closeAutosuggestions();
|
|
1294
|
+
}
|
|
1295
|
+
}}
|
|
1296
|
+
>
|
|
1297
|
+
{#each currentSuggestion.allMatches as match, i}
|
|
1298
|
+
<div
|
|
1299
|
+
class="color-suggestion-item px-2 py-1 cursor-pointer hover:bg-gray-100"
|
|
1300
|
+
style="position: relative; z-index: 99999; pointer-events: auto !important;"
|
|
1301
|
+
class:bg-gray-100={i === currentSuggestion?.currentIndex}
|
|
1302
|
+
onmousedown={(event) => {
|
|
1303
|
+
event.preventDefault();
|
|
1304
|
+
event.stopPropagation();
|
|
1305
|
+
handleColorSelection(match, event.shiftKey);
|
|
1306
|
+
}}
|
|
1307
|
+
>
|
|
1308
|
+
{match}
|
|
1309
|
+
</div>
|
|
1310
|
+
{/each}
|
|
1311
|
+
</div>
|
|
1312
|
+
{/if}
|
|
1313
|
+
</Portal>
|
|
1314
|
+
{/if}
|
|
1315
|
+
|
|
1316
|
+
<!-- Color Picker — portaled to a unique body-level div -->
|
|
1317
|
+
{#if open}
|
|
1318
|
+
<Portal target="color-picker-portal" class="color-picker-portal-ct" style="position: fixed; inset: 0; z-index: 99999; pointer-events: none;">
|
|
1319
|
+
|
|
1320
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1321
|
+
<div class="color-picker-backdrop" style="z-index: 1;" onmousedown={() => handleToggle(false)}></div>
|
|
1322
|
+
|
|
1323
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1324
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
1325
|
+
<div class="color-picker-popover-content w-auto pt-2 pb-2 pl-2 pr-0 widgetic-colors widgetic-radius modal-shadow"
|
|
1326
|
+
style="{popoverStyle} border: 1px solid hsl(var(--grey2)); z-index: 2;"
|
|
1327
|
+
onmousedown={(e) => e.stopPropagation()}
|
|
1328
|
+
onclick={(e) => e.stopPropagation()}
|
|
1329
|
+
>
|
|
1330
|
+
<!-- Color Picker Popover Content Wrapper -->
|
|
1331
|
+
<div class="color-picker-popover-content-wrapper flex flex-col">
|
|
1332
|
+
|
|
1333
|
+
<!-- Recent Colors Container -->
|
|
1334
|
+
{#if saveRecentColors && (isShiftPressed || isSpacePressed)}
|
|
1335
|
+
<div class="recent-colors-container" style="width: {pickerSize + sliderWidth + 14}px; margin-left: 10px; padding-top: 11px; padding-bottom: 8px;">
|
|
1336
|
+
{#if recentColors.length > 0}
|
|
1337
|
+
<div class="recent-colors-grid" style="display: grid; grid-template-columns: repeat(5, 1fr); width: 100%; gap: {swatchGap}px;">
|
|
1338
|
+
{#each recentColors.slice(0, 5) as {color, alpha}, i}
|
|
1339
|
+
<button
|
|
1340
|
+
class="widgetic-colors rounded-medium relative overflow-hidden animate-recent-color"
|
|
1341
|
+
style="width: {swatchSize}px; height: {swatchSize}px; border: 1px solid hsl(var(--forest-green)); animation-delay: {i * 0.05}s"
|
|
1342
|
+
onclick={() => {
|
|
1343
|
+
try {
|
|
1344
|
+
isInternalUpdate = true;
|
|
1345
|
+
lastKnownOpacity = showOpacityControls ? alpha : 1;
|
|
1346
|
+
const newRgb = hexToRgba(color);
|
|
1347
|
+
rgb = { ...newRgb, a: showOpacityControls ? alpha : 1 };
|
|
1348
|
+
opacityPercentage = Math.round((showOpacityControls ? alpha : 1) * 100);
|
|
1349
|
+
value = color;
|
|
1350
|
+
hexInput = color;
|
|
1351
|
+
previewColor = getRgbaColor();
|
|
1352
|
+
onChange(color);
|
|
1353
|
+
} finally {
|
|
1354
|
+
isInternalUpdate = false;
|
|
1355
|
+
}
|
|
1356
|
+
}}
|
|
1357
|
+
>
|
|
1358
|
+
<CheckerBoardBackground />
|
|
1359
|
+
<!-- svelte-ignore element_invalid_self_closing_tag -->
|
|
1360
|
+
<div
|
|
1361
|
+
class="absolute inset-0"
|
|
1362
|
+
style="background-color: {color}; opacity: {showOpacityControls ? alpha : 1};"
|
|
1363
|
+
/>
|
|
1364
|
+
</button>
|
|
1365
|
+
{/each}
|
|
1366
|
+
</div>
|
|
1367
|
+
{:else}
|
|
1368
|
+
<div class="text-sm text-gray-500 text-center py-2">
|
|
1369
|
+
No recent colors
|
|
1370
|
+
</div>
|
|
1371
|
+
{/if}
|
|
1372
|
+
</div>
|
|
1373
|
+
{/if}
|
|
1374
|
+
|
|
1375
|
+
<!-- Color Picker Container (block layout, padded) -->
|
|
1376
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
1377
|
+
<div
|
|
1378
|
+
class="color-picker-wrapper relative flex flex-col"
|
|
1379
|
+
role="application"
|
|
1380
|
+
aria-label="Color picker"
|
|
1381
|
+
onmousedown={handleDragStart}
|
|
1382
|
+
style="padding: 0;"
|
|
1383
|
+
>
|
|
1384
|
+
<!-- Picker row: 8px left/top padding, 8px right padding for hue bar -->
|
|
1385
|
+
<div style="display: flex; flex-direction: row; align-items: flex-start;">
|
|
1386
|
+
<ColorSelect
|
|
1387
|
+
bind:this={colorSelectRef}
|
|
1388
|
+
bind:rgb
|
|
1389
|
+
bind:a={lastKnownOpacity}
|
|
1390
|
+
onchange={handleColorChange}
|
|
1391
|
+
alpha={true}
|
|
1392
|
+
{pickerSize}
|
|
1393
|
+
{sliderWidth}
|
|
1394
|
+
/>
|
|
1395
|
+
<!-- 8px right padding for hue bar is handled by ColorSelect's sliderWidth prop -->
|
|
1396
|
+
</div>
|
|
1397
|
+
|
|
1398
|
+
<!-- Opacity bar and number input: horizontal flex row under picker/hue bar -->
|
|
1399
|
+
<div style="display: flex; flex-direction: row; align-items: center; margin-top: 4px;">
|
|
1400
|
+
<!-- Opacity bar under color picker -->
|
|
1401
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
1402
|
+
<div
|
|
1403
|
+
bind:this={opacitySliderRef}
|
|
1404
|
+
class="relative cursor-pointer opacity-slider"
|
|
1405
|
+
style="width: {pickerSize}px; height: {sliderWidth}px; margin-left: 10px;"
|
|
1406
|
+
onmousedown={handleOpacityMouseDown}
|
|
1407
|
+
>
|
|
1408
|
+
<div class="absolute inset-0 overflow-hidden">
|
|
1409
|
+
<CheckerBoardBackground />
|
|
1410
|
+
<div
|
|
1411
|
+
class="absolute inset-0"
|
|
1412
|
+
style="background: linear-gradient(to right,
|
|
1413
|
+
rgba({Math.round(rgb.r * 255)}, {Math.round(rgb.g * 255)}, {Math.round(rgb.b * 255)}, 0) 0%,
|
|
1414
|
+
rgba({Math.round(rgb.r * 255)}, {Math.round(rgb.g * 255)}, {Math.round(rgb.b * 255)}, 1) 100%
|
|
1415
|
+
);"
|
|
1416
|
+
></div>
|
|
1417
|
+
</div>
|
|
1418
|
+
<div
|
|
1419
|
+
class="opacity-scrub absolute top-0 bottom-0 w-0.5 bg-white cursor-pointer z-20"
|
|
1420
|
+
style={verticalLineStyle}
|
|
1421
|
+
>
|
|
1422
|
+
<OpacitySliderArrows class="opacity-slider-arrows" />
|
|
1423
|
+
</div>
|
|
1424
|
+
</div>
|
|
1425
|
+
<!-- Opacity number input — square, matching swatch size -->
|
|
1426
|
+
<div class="opacity-number-ct flex items-center justify-center" style="width: {swatchSize + 6}px; min-width: {swatchSize + 6}px; height: {swatchSize}px; margin-left: 8px;">
|
|
1427
|
+
<Input
|
|
1428
|
+
type="number"
|
|
1429
|
+
min="0"
|
|
1430
|
+
max="100"
|
|
1431
|
+
class="w-full h-full text-center [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none ring-0 focus:ring-0 shadow-none widgetic-colors subtext"
|
|
1432
|
+
style="border: 1px solid hsl(var(--forest-green)); color: hsl(var(--grey7)); padding-left: 8px; padding-right: 0;"
|
|
1433
|
+
value={opacityPercentage}
|
|
1434
|
+
oninput={(e) => {
|
|
1435
|
+
e.stopPropagation();
|
|
1436
|
+
updateOpacityFromInput(e);
|
|
1437
|
+
}}
|
|
1438
|
+
onkeydown={(e) => {
|
|
1439
|
+
if (e.key === 'Shift') isShiftPressed = true;
|
|
1440
|
+
e.stopPropagation();
|
|
1441
|
+
}}
|
|
1442
|
+
onkeyup={(e) => {
|
|
1443
|
+
if (e.key === 'Shift') isShiftPressed = false;
|
|
1444
|
+
e.stopPropagation();
|
|
1445
|
+
}}
|
|
1446
|
+
onclick={(e) => {
|
|
1447
|
+
e.stopPropagation();
|
|
1448
|
+
}}
|
|
1449
|
+
/>
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
</div> <!-- end of color-picker -->
|
|
1453
|
+
|
|
1454
|
+
</div> <!-- end of color-picker-popover-content-wrapper -->
|
|
1455
|
+
</div>
|
|
1456
|
+
</Portal>
|
|
1457
|
+
{/if}
|
|
1458
|
+
</div>
|
|
1459
|
+
</div> <!-- end of color-preview-button-container -->
|
|
1460
|
+
|
|
1461
|
+
</div>
|
|
1462
|
+
</div>
|
|
1463
|
+
</div>
|
|
1464
|
+
</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
|
|
1467
|
+
<style>
|
|
1468
|
+
/**
|
|
1469
|
+
* Component Styling
|
|
1470
|
+
* - Manages z-index stacking
|
|
1471
|
+
* - Handles drag interactions
|
|
1472
|
+
* - Controls visibility states
|
|
1473
|
+
* - Ensures proper event handling
|
|
1474
|
+
*/
|
|
1475
|
+
|
|
1476
|
+
.color-input-main-container:hover {
|
|
1477
|
+
border-color: hsl(var(--forest-green)) !important;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
/* Add cursor styles for dragging */
|
|
1481
|
+
:global(.color-picker-wrapper canvas),
|
|
1482
|
+
.opacity-slider {
|
|
1483
|
+
cursor: grab !important;
|
|
1484
|
+
user-select: none !important;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
:global(.color-picker-wrapper canvas:active),
|
|
1488
|
+
.opacity-slider:active {
|
|
1489
|
+
cursor: grabbing !important;
|
|
1490
|
+
user-select: none !important;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
/* Prevent cursor from changing during drag */
|
|
1494
|
+
:global(.color-picker-wrapper *),
|
|
1495
|
+
.opacity-slider * {
|
|
1496
|
+
cursor: inherit !important;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
/* Add a class when dragging starts */
|
|
1500
|
+
.opacity-slider:active * {
|
|
1501
|
+
cursor: grabbing !important;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
/* Backdrop covers the entire viewport to catch outside clicks */
|
|
1505
|
+
.color-picker-backdrop {
|
|
1506
|
+
position: fixed;
|
|
1507
|
+
inset: 0;
|
|
1508
|
+
z-index: 99998;
|
|
1509
|
+
pointer-events: auto;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
/* Popover content — fixed position relative to viewport */
|
|
1513
|
+
.color-picker-popover-content {
|
|
1514
|
+
position: fixed;
|
|
1515
|
+
z-index: 99999;
|
|
1516
|
+
margin: 0;
|
|
1517
|
+
padding: 8px;
|
|
1518
|
+
background: hsl(var(--grey1));
|
|
1519
|
+
border: 1px solid hsl(var(--forest-green));
|
|
1520
|
+
border-radius: var(--radius-small);
|
|
1521
|
+
box-shadow: 0px 0px 15px 3px rgba(0, 0, 0, 0.1);
|
|
1522
|
+
pointer-events: auto;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
/* Update input styles */
|
|
1526
|
+
:global(input.bg-transparent) {
|
|
1527
|
+
background: transparent !important;
|
|
1528
|
+
border: none !important;
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
:global(input.bg-transparent:focus),
|
|
1532
|
+
:global(input.bg-transparent:focus-visible) {
|
|
1533
|
+
outline: none !important;
|
|
1534
|
+
box-shadow: none !important;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
/* Update button styles */
|
|
1538
|
+
:global(button.rounded-md) {
|
|
1539
|
+
border-radius: var(--radius) !important;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
/* Ensure opacity slider border is visible */
|
|
1543
|
+
:global(.opacity-slider) {
|
|
1544
|
+
border: 1px solid hsl(var(--forest-green)) !important;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/* Updated animation for individual recent colors */
|
|
1548
|
+
@keyframes zoomIn {
|
|
1549
|
+
from {
|
|
1550
|
+
transform: scale(0.6);
|
|
1551
|
+
opacity: 0;
|
|
1552
|
+
}
|
|
1553
|
+
to {
|
|
1554
|
+
transform: scale(1);
|
|
1555
|
+
opacity: 1;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
:global(.animate-recent-color) {
|
|
1560
|
+
animation: zoomIn 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
|
|
1561
|
+
transform-origin: center center;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/* Add these styles */
|
|
1565
|
+
.color-preview-button {
|
|
1566
|
+
outline: none !important;
|
|
1567
|
+
box-shadow: none !important;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
.color-preview-button:focus {
|
|
1571
|
+
outline: none !important;
|
|
1572
|
+
box-shadow: none !important;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
.color-preview-button::after {
|
|
1576
|
+
content: '';
|
|
1577
|
+
position: absolute;
|
|
1578
|
+
inset: 0;
|
|
1579
|
+
border-radius: inherit;
|
|
1580
|
+
border: 1px solid hsl(var(--grey3));
|
|
1581
|
+
pointer-events: none;
|
|
1582
|
+
}
|
|
1583
|
+
</style>
|
|
1584
|
+
|