@widgetic/editor 3.11.3 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,3952 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* PropsEditor Component
|
|
4
|
+
* A customizable property editor that handles both design and content properties
|
|
5
|
+
* Supports various input types and manages state for both design and content tabs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* SVELTE IMPORTS
|
|
10
|
+
*/
|
|
11
|
+
const browser = typeof window !== 'undefined';
|
|
12
|
+
|
|
13
|
+
// Svelte imports
|
|
14
|
+
import { onMount, onDestroy, createEventDispatcher, afterUpdate } from 'svelte';
|
|
15
|
+
const dispatch = createEventDispatcher();
|
|
16
|
+
import { writable, get } from 'svelte/store';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* COMPONENTS IMPORTS
|
|
20
|
+
*/
|
|
21
|
+
// Shadcn Regular Components
|
|
22
|
+
import { Button, Separator } from '@widgetic/design-system/components';
|
|
23
|
+
// Shadcn namespaced(compound) components imports
|
|
24
|
+
import { Tabs, Accordion, Card, Collapsible } from '@widgetic/design-system/components';
|
|
25
|
+
// Import ScrollArea components
|
|
26
|
+
import { ScrollArea } from '@widgetic/design-system/components';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* ICONS IMPORTS
|
|
30
|
+
*/
|
|
31
|
+
import { ChevronDown, IconGripVertical } from '@widgetic/design-system/icons';
|
|
32
|
+
import AddNew from '../icons/addnew.svelte';
|
|
33
|
+
import DragHandle from '../icons/drag.svelte';
|
|
34
|
+
|
|
35
|
+
// Input Controllers imports
|
|
36
|
+
import {
|
|
37
|
+
ColorPickerInputController,
|
|
38
|
+
PositionInputController,
|
|
39
|
+
DropdownInputController,
|
|
40
|
+
SliderInputController,
|
|
41
|
+
SliderRangeInputController,
|
|
42
|
+
FontInputController,
|
|
43
|
+
SwitchInputController,
|
|
44
|
+
TextInputController,
|
|
45
|
+
TextAreaInputController,
|
|
46
|
+
TextAreaPreviewInputController,
|
|
47
|
+
DatePickerInputController,
|
|
48
|
+
BrowserInputController,
|
|
49
|
+
NumberInputController,
|
|
50
|
+
RangeCalendarInputController,
|
|
51
|
+
FixedPositionInputController,
|
|
52
|
+
VectorInputController,
|
|
53
|
+
AngleInputController,
|
|
54
|
+
InputControllerHeader,
|
|
55
|
+
RadioGroupInputController,
|
|
56
|
+
CheckboxInputController,
|
|
57
|
+
TimeInputController,
|
|
58
|
+
RoundedCornersInputController,
|
|
59
|
+
TagsInputController,
|
|
60
|
+
DateFieldInputController
|
|
61
|
+
} from '../ic';
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* UTILS IMPORTS
|
|
65
|
+
*/
|
|
66
|
+
import MediaManager from '../../utils/MediaManager';
|
|
67
|
+
import FontManager from '../../utils/FontManager';
|
|
68
|
+
import { config as appConfig } from '../../config';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* STYLES from Widgetic Design System
|
|
72
|
+
* Note: props-editor.css global rules (body, *) moved to app.css (standalone only).
|
|
73
|
+
* Component-scoped rules are in the style block at the end of this file.
|
|
74
|
+
*/
|
|
75
|
+
import "@widgetic/design-system/styles";
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Type Definitions
|
|
80
|
+
* Defines the structure for input values and content schema
|
|
81
|
+
*/
|
|
82
|
+
type InputValue = string | number | boolean | any | null;
|
|
83
|
+
|
|
84
|
+
interface PropertyGroup {
|
|
85
|
+
name: string;
|
|
86
|
+
properties: Array<Property>;
|
|
87
|
+
visibilityCondition?: {
|
|
88
|
+
dependsOn: string | string[];
|
|
89
|
+
condition: string;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Add this interface near the other interfaces at the top of the file
|
|
94
|
+
interface ContentProperty {
|
|
95
|
+
id: string;
|
|
96
|
+
defaultValue?: any;
|
|
97
|
+
value?: any;
|
|
98
|
+
inputController: {
|
|
99
|
+
type: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
helpText?: string;
|
|
102
|
+
config?: Record<string, any>;
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* EXTERNAL COMPONENT PROPS
|
|
108
|
+
*/
|
|
109
|
+
// Class
|
|
110
|
+
let className = '';
|
|
111
|
+
export { className as class };
|
|
112
|
+
// Style
|
|
113
|
+
export let style = '';
|
|
114
|
+
// Dimensions
|
|
115
|
+
export let width: string = "450px";
|
|
116
|
+
export let height: string = "720px";
|
|
117
|
+
// Schemas
|
|
118
|
+
export let designSchema: any = null;
|
|
119
|
+
export let contentSchema: any | undefined = undefined;
|
|
120
|
+
export let itemTemplate: any = null; // Store template for new items
|
|
121
|
+
// Tab titles
|
|
122
|
+
export let designTabTitle: string = 'Design';
|
|
123
|
+
export let contentTabTitle: string = 'Content';
|
|
124
|
+
// Default tab selection
|
|
125
|
+
export let defaultTab: 'design' | 'content' = 'content';
|
|
126
|
+
|
|
127
|
+
/** Portal slot for FileBrowser overlay — scoped to editor panel, not document body. */
|
|
128
|
+
let fileBrowserPortalHost: HTMLDivElement | null = null;
|
|
129
|
+
|
|
130
|
+
// Determine if the first accordion item should be open by default
|
|
131
|
+
export let defaultOpen: boolean = true;
|
|
132
|
+
|
|
133
|
+
// Add showDragHandle prop
|
|
134
|
+
export let showDragHandle = true;
|
|
135
|
+
// Enable built-in drag (component manages its own absolute positioning)
|
|
136
|
+
export let enableDrag = true;
|
|
137
|
+
|
|
138
|
+
// Initial values from saved composition (merged over schema defaults)
|
|
139
|
+
export let initialDesignValues: Record<string, any> = {};
|
|
140
|
+
export let initialContentValues: Record<string, any> = {};
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* CONSTANTS
|
|
144
|
+
*/
|
|
145
|
+
const CONTENT_PADDING = 16; // Outer padding for both tabs
|
|
146
|
+
const CONTENT_INNER_PADDING = 16; // Inner padding for cards in Content tab
|
|
147
|
+
const HEADER_VERTICAL_PADDING = 24; // Padding for top and bottom of header
|
|
148
|
+
const HEADER_HEIGHT = 85; // 96px for header + 1px for separator
|
|
149
|
+
const EDITOR_STROKE_COLOR = '#e5e7eb'; // Color for editor stroke
|
|
150
|
+
const EDITOR_STROKE_WIDTH = '1px'; // Thickness of editor stroke
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* State Management
|
|
154
|
+
* Track various component states including open items, drag state, and focus
|
|
155
|
+
*/
|
|
156
|
+
let hasLoggedDesign = false; // Prevents duplicate logging of design tab info
|
|
157
|
+
let hasLoggedContent = false; // Prevents duplicate logging of content tab info
|
|
158
|
+
let designState: Record<string, InputValue> = { ...initialDesignValues }; // Stores design property values
|
|
159
|
+
let contentState: Record<string, InputValue> = { ...initialContentValues }; // Stores content property values
|
|
160
|
+
let isGenericContentOpen = true; // Controls generic content section visibility
|
|
161
|
+
let lastFocusedDesignController: string | null = null; // Tracks last focused design input
|
|
162
|
+
let lastFocusedContentController: string | null = null; // Tracks last focused content input
|
|
163
|
+
let currentTab = 'design'; // Current active tab
|
|
164
|
+
let activeControllerId: string | null = null; // Currently active input controller
|
|
165
|
+
let designOpenItems = new Set<string>();
|
|
166
|
+
let designOpenAccordion: string[] = [];
|
|
167
|
+
let contentOpenAccordion: string[] = [];
|
|
168
|
+
let isDragging = false;
|
|
169
|
+
|
|
170
|
+
// Add isHeaderHovered state variable after the other state declarations
|
|
171
|
+
let isHeaderHovered = false;
|
|
172
|
+
|
|
173
|
+
// Add this near the top with other state variables
|
|
174
|
+
let isInitialTabLoad = true;
|
|
175
|
+
|
|
176
|
+
// Add these for mirror positioning
|
|
177
|
+
let grabOffsetY = 0;
|
|
178
|
+
let grabOffsetX = 0;
|
|
179
|
+
const DRAG_SHIFT_X = 24; // shift mirror left so cursor is over drag handle
|
|
180
|
+
|
|
181
|
+
// Helper function to get visible groups
|
|
182
|
+
function getVisibleGroups(components: Array<{ propertyGroups: PropertyGroup[] }>): PropertyGroup[] {
|
|
183
|
+
return components?.reduce((acc: PropertyGroup[], component) => {
|
|
184
|
+
const visibleGroups = component.propertyGroups.filter((group: PropertyGroup) =>
|
|
185
|
+
shouldShowPropertyGroup(group, component.propertyGroups[0].properties, 'design')
|
|
186
|
+
);
|
|
187
|
+
return [...acc, ...visibleGroups];
|
|
188
|
+
}, []) || [];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Use the helper function in the reactive statement
|
|
192
|
+
$: allVisibleGroups = designSchema?.[0]?.components ? getVisibleGroups(designSchema[0].components) : [];
|
|
193
|
+
|
|
194
|
+
// Update the function with proper type annotations
|
|
195
|
+
function getAccordionItemPosition(
|
|
196
|
+
component: { propertyGroups: PropertyGroup[] },
|
|
197
|
+
propertyGroup: PropertyGroup,
|
|
198
|
+
groupIndex: number
|
|
199
|
+
): 'first' | 'middle' | 'last' | 'only' {
|
|
200
|
+
// Get all currently visible groups (considering visibility conditions)
|
|
201
|
+
const currentlyVisibleGroups = allVisibleGroups.filter(group =>
|
|
202
|
+
shouldShowPropertyGroup(group, component.propertyGroups[0].properties, 'design')
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const globalIndex = currentlyVisibleGroups.findIndex(g => g.name === propertyGroup.name);
|
|
206
|
+
|
|
207
|
+
// Check if this group is actually visible
|
|
208
|
+
if (!shouldShowPropertyGroup(propertyGroup, component.propertyGroups[0].properties, 'design')) {
|
|
209
|
+
return 'middle'; // or handle hidden items differently if needed
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (currentlyVisibleGroups.length === 1) return 'only';
|
|
213
|
+
if (globalIndex === 0) return 'first';
|
|
214
|
+
if (globalIndex === currentlyVisibleGroups.length - 1) return 'last';
|
|
215
|
+
return 'middle';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Clear font weight keys immediately when script loads
|
|
219
|
+
if (browser) {
|
|
220
|
+
const keysToRemove: string[] = [];
|
|
221
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
222
|
+
const key = localStorage.key(i);
|
|
223
|
+
if (key && key.includes('font-weight-state-')) {
|
|
224
|
+
keysToRemove.push(key);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (keysToRemove.length > 0) {
|
|
229
|
+
console.log('🧹 [PropsEditor] Clearing font weight states on init:', keysToRemove);
|
|
230
|
+
keysToRemove.forEach(key => localStorage.removeItem(key));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* DOM References and Scroll State
|
|
236
|
+
*/
|
|
237
|
+
// DOM References for scroll areas and content wrappers
|
|
238
|
+
let designScrollViewport: HTMLDivElement | undefined;
|
|
239
|
+
let designContentWrapper: HTMLDivElement | undefined;
|
|
240
|
+
let contentScrollViewport: HTMLDivElement | undefined;
|
|
241
|
+
let contentWrapper: HTMLDivElement | undefined;
|
|
242
|
+
let contentItemsContainer: HTMLDivElement | undefined;
|
|
243
|
+
|
|
244
|
+
// Initialize scroll state
|
|
245
|
+
let scrollPosition: number = 0;
|
|
246
|
+
let contentHeight: number = 0;
|
|
247
|
+
let scrollAreaHeight: number = 0;
|
|
248
|
+
let shouldShowScroll: boolean = false;
|
|
249
|
+
|
|
250
|
+
// Create stores for scroll states
|
|
251
|
+
const designScrollState = writable({
|
|
252
|
+
scrollPosition: 0,
|
|
253
|
+
contentHeight: 0,
|
|
254
|
+
scrollAreaHeight: 0,
|
|
255
|
+
shouldShowScroll: false
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const contentScrollState = writable({
|
|
259
|
+
scrollPosition: 0,
|
|
260
|
+
contentHeight: 0,
|
|
261
|
+
scrollAreaHeight: 0,
|
|
262
|
+
shouldShowScroll: false
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Type assertion function for DOM elements
|
|
266
|
+
function assertElement<T extends HTMLElement>(el: T | null | undefined): asserts el is T {
|
|
267
|
+
if (!el) throw new Error('Element not found');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* METHODS
|
|
272
|
+
*/
|
|
273
|
+
let cleanup: (() => void) | undefined;
|
|
274
|
+
|
|
275
|
+
// Initialize FontManager
|
|
276
|
+
const initializeEditor = async () => {
|
|
277
|
+
try {
|
|
278
|
+
// console.log('🚀 [PropsEditor] Starting initialization...', {
|
|
279
|
+
// hasDesignSchema: !!designSchema,
|
|
280
|
+
// hasContentSchema: !!contentSchema
|
|
281
|
+
// });
|
|
282
|
+
|
|
283
|
+
// Initialize FontManager when PropsEditor mounts
|
|
284
|
+
// console.log('📚 [PropsEditor] Initializing FontManager...');
|
|
285
|
+
const fontManagerInstance = await FontManager.getInstance(appConfig.GOOGLE_FONTS_API_KEY);
|
|
286
|
+
// console.log('✅ [PropsEditor] FontManager initialized');
|
|
287
|
+
|
|
288
|
+
// Preload the "Mine" category fonts to prevent loading delay in FontInputController instances
|
|
289
|
+
try {
|
|
290
|
+
const customFonts = await fontManagerInstance.loadCustomFonts();
|
|
291
|
+
console.log('✅ [PropsEditor] Preloaded "Mine" category fonts:', {
|
|
292
|
+
count: customFonts.length,
|
|
293
|
+
fonts: customFonts.slice(0, 3).map(f => f.family)
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('❌ [PropsEditor] Failed to preload "Mine" category fonts:', error);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Log design tab info if schema exists
|
|
300
|
+
if (designSchema && designSchema[0]?.components && !hasLoggedDesign) {
|
|
301
|
+
const firstComponent = designSchema[0].components[0];
|
|
302
|
+
const firstGroup = firstComponent.propertyGroups[0];
|
|
303
|
+
const firstProperty = firstGroup.properties[0];
|
|
304
|
+
|
|
305
|
+
if (firstProperty) {
|
|
306
|
+
// console.log('🎨 [PropsEditor] Design Tab initialized:', {
|
|
307
|
+
// type: firstProperty.inputController.type,
|
|
308
|
+
// name: firstProperty.id,
|
|
309
|
+
// description: firstProperty.inputController.description
|
|
310
|
+
// });
|
|
311
|
+
}
|
|
312
|
+
hasLoggedDesign = true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Skip content initialization if contentSchema is missing
|
|
316
|
+
if (!contentSchema) {
|
|
317
|
+
// console.log('ℹ️ [PropsEditor] No content schema provided, skipping content initialization');
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Only initialize content if it exists and hasn't been logged
|
|
322
|
+
if (!hasLoggedContent) {
|
|
323
|
+
const firstProperty = contentSchema.genericContent?.properties?.[0];
|
|
324
|
+
|
|
325
|
+
if (firstProperty) {
|
|
326
|
+
// console.log('📝 [PropsEditor] Content Tab initialized:', {
|
|
327
|
+
// type: firstProperty.inputController.type,
|
|
328
|
+
// name: firstProperty.id,
|
|
329
|
+
// description: firstProperty.inputController.description
|
|
330
|
+
// });
|
|
331
|
+
}
|
|
332
|
+
hasLoggedContent = true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
} catch (error) {
|
|
336
|
+
console.error('❌ [PropsEditor] Error during initialization:', error);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
let isDraggableInitialized = false;
|
|
341
|
+
let currentDraggable: any = null;
|
|
342
|
+
let isInitializing = false;
|
|
343
|
+
|
|
344
|
+
const initializeDraggable = async () => {
|
|
345
|
+
// Prevent multiple simultaneous initializations
|
|
346
|
+
if (isInitializing) {
|
|
347
|
+
// console.log('🔄 Already initializing draggable, skipping...');
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
isInitializing = true;
|
|
352
|
+
// console.log('🔄 [PropsEditor] Starting draggable initialization');
|
|
353
|
+
|
|
354
|
+
try {
|
|
355
|
+
if (contentSchema && typeof window !== 'undefined') {
|
|
356
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
357
|
+
|
|
358
|
+
// Target only the content-items container
|
|
359
|
+
const containerElement = document.querySelector('.content-items');
|
|
360
|
+
// console.log('🔄 [PropsEditor] Found content container:', containerElement);
|
|
361
|
+
|
|
362
|
+
if (containerElement instanceof HTMLElement) {
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
if (currentDraggable) {
|
|
366
|
+
// console.log('🧹 Cleaning up existing draggable instance');
|
|
367
|
+
currentDraggable.destroy();
|
|
368
|
+
currentDraggable = null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const { Sortable, Plugins } = await import('@shopify/draggable');
|
|
372
|
+
const { SortAnimation } = Plugins;
|
|
373
|
+
|
|
374
|
+
let dragStartTime = 0;
|
|
375
|
+
let isDragStarted = false;
|
|
376
|
+
let dragThreshold = 0; // pixels to move before considering it a drag
|
|
377
|
+
let initialMousePosition = { x: 0, y: 0 };
|
|
378
|
+
let grabOffsetY = 0;
|
|
379
|
+
let grabOffsetX = 0;
|
|
380
|
+
|
|
381
|
+
// Add forceCleanupState function
|
|
382
|
+
function forceCleanupState() {
|
|
383
|
+
// console.log('🧹 Performing emergency state cleanup');
|
|
384
|
+
|
|
385
|
+
// Clean up any existing mirror elements first
|
|
386
|
+
const mirrors = document.querySelectorAll('.draggable-mirror');
|
|
387
|
+
mirrors.forEach(mirror => {
|
|
388
|
+
if (mirror.parentNode) {
|
|
389
|
+
mirror.parentNode.removeChild(mirror);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Reset all global state
|
|
394
|
+
isDragging = false;
|
|
395
|
+
isDragStarted = false;
|
|
396
|
+
document.documentElement.classList.remove('draggable--is-dragging');
|
|
397
|
+
|
|
398
|
+
// Remove dragging classes from body and document
|
|
399
|
+
document.body.classList.remove('dragging-active');
|
|
400
|
+
document.documentElement.classList.remove('dragging-active');
|
|
401
|
+
|
|
402
|
+
// Force cleanup all items
|
|
403
|
+
document.querySelectorAll('.content-item').forEach((item: Element) => {
|
|
404
|
+
if (item instanceof HTMLElement) {
|
|
405
|
+
// Remove all possible dragging classes
|
|
406
|
+
item.classList.remove('draggable-source--is-dragging');
|
|
407
|
+
item.classList.remove('draggable--over');
|
|
408
|
+
item.classList.remove('draggable--original');
|
|
409
|
+
item.classList.remove('draggable-source--placed');
|
|
410
|
+
item.classList.remove('draggable-mirror');
|
|
411
|
+
|
|
412
|
+
// Reset all possible styles
|
|
413
|
+
item.style.transform = '';
|
|
414
|
+
item.style.transition = 'none';
|
|
415
|
+
// Ensure inline order values are cleared so visual order matches DOM order
|
|
416
|
+
item.style.order = '';
|
|
417
|
+
void item.offsetHeight; // Force reflow
|
|
418
|
+
item.style.transition = '';
|
|
419
|
+
item.style.position = 'relative';
|
|
420
|
+
item.style.zIndex = '1';
|
|
421
|
+
item.style.opacity = '1';
|
|
422
|
+
item.style.visibility = 'visible';
|
|
423
|
+
item.style.pointerEvents = '';
|
|
424
|
+
item.style.cursor = 'pointer';
|
|
425
|
+
|
|
426
|
+
// Update collapsible state based on our openItems
|
|
427
|
+
const itemId = item.getAttribute('data-id');
|
|
428
|
+
const isOpen = itemId && openItems.has(itemId);
|
|
429
|
+
|
|
430
|
+
// Find the collapsible elements
|
|
431
|
+
const collapsibleRoot = item.querySelector('[data-radix-collapsible-root]');
|
|
432
|
+
const trigger = item.querySelector('[data-radix-collapsible-trigger]');
|
|
433
|
+
const content = item.querySelector('[data-radix-collapsible-content]');
|
|
434
|
+
const chevron = item.querySelector('.chevron');
|
|
435
|
+
|
|
436
|
+
// Update states
|
|
437
|
+
if (collapsibleRoot instanceof HTMLElement) {
|
|
438
|
+
collapsibleRoot.setAttribute('data-state', isOpen ? 'open' : 'closed');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (trigger instanceof HTMLElement) {
|
|
442
|
+
trigger.setAttribute('data-state', isOpen ? 'open' : 'closed');
|
|
443
|
+
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (content instanceof HTMLElement) {
|
|
447
|
+
if (!isOpen) {
|
|
448
|
+
content.style.height = '0';
|
|
449
|
+
content.style.overflow = 'hidden';
|
|
450
|
+
} else {
|
|
451
|
+
content.style.height = 'auto';
|
|
452
|
+
content.style.overflow = 'visible';
|
|
453
|
+
}
|
|
454
|
+
content.setAttribute('data-state', isOpen ? 'open' : 'closed');
|
|
455
|
+
content.style.display = ''; // Always reset display property
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
if (chevron instanceof HTMLElement) {
|
|
459
|
+
chevron.style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0deg)';
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Clean container
|
|
465
|
+
const container = document.querySelector('.content-items');
|
|
466
|
+
if (container instanceof HTMLElement) {
|
|
467
|
+
container.classList.remove('content-items--is-dragging');
|
|
468
|
+
container.classList.remove('dragging-active');
|
|
469
|
+
container.classList.remove('draggable-container--over');
|
|
470
|
+
container.style.pointerEvents = '';
|
|
471
|
+
container.style.cursor = '';
|
|
472
|
+
|
|
473
|
+
// Force reflow
|
|
474
|
+
void container.offsetHeight;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
currentDraggable = new Sortable(containerElement, {
|
|
479
|
+
draggable: '.content-item',
|
|
480
|
+
handle: '.drag-handle',
|
|
481
|
+
delay: 0,
|
|
482
|
+
mirror: {
|
|
483
|
+
constrainDimensions: true,
|
|
484
|
+
appendTo: 'body',
|
|
485
|
+
xAxis: true,
|
|
486
|
+
yAxis: true
|
|
487
|
+
},
|
|
488
|
+
plugins: [SortAnimation],
|
|
489
|
+
sortAnimation: {
|
|
490
|
+
duration: 200,
|
|
491
|
+
easingFunction: 'ease-in-out'
|
|
492
|
+
},
|
|
493
|
+
classes: {
|
|
494
|
+
'body:dragging': 'draggable--is-dragging',
|
|
495
|
+
'container:dragging': 'dragging-active',
|
|
496
|
+
'source:dragging': 'draggable-source--is-dragging',
|
|
497
|
+
'source:placed': 'draggable-source--placed',
|
|
498
|
+
'container:placed': 'draggable-container--placed',
|
|
499
|
+
'draggable:over': 'draggable--over',
|
|
500
|
+
'container:over': 'draggable-container--over',
|
|
501
|
+
'source:original': 'draggable--original',
|
|
502
|
+
'mirror': 'draggable-mirror'
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
// Update event handlers
|
|
509
|
+
currentDraggable.on('drag:start', (event: any) => {
|
|
510
|
+
console.log('[Draggable] drag:start event');
|
|
511
|
+
isDragging = true; // Set flag immediately to prevent afterUpdate interference
|
|
512
|
+
|
|
513
|
+
const dragHandle = event.originalSource?.querySelector('.drag-handle');
|
|
514
|
+
const target = event.originalEvent?.target;
|
|
515
|
+
|
|
516
|
+
if (!dragHandle?.contains(target)) {
|
|
517
|
+
event.cancel();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Store initial mouse position for threshold calculation
|
|
522
|
+
if (event.originalEvent instanceof MouseEvent) {
|
|
523
|
+
initialMousePosition = {
|
|
524
|
+
x: event.originalEvent.clientX,
|
|
525
|
+
y: event.originalEvent.clientY
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Store previously open items
|
|
530
|
+
previouslyOpen = new Set(openItems);
|
|
531
|
+
|
|
532
|
+
// Reset open items
|
|
533
|
+
openItems = new Set();
|
|
534
|
+
|
|
535
|
+
// Add a class to body to trigger our CSS rules
|
|
536
|
+
document.body.classList.add('dragging-active');
|
|
537
|
+
|
|
538
|
+
// Force reflow to apply height changes immediately
|
|
539
|
+
document.body.offsetHeight;
|
|
540
|
+
|
|
541
|
+
// Add design system classes to all content items for consistent styling
|
|
542
|
+
document.querySelectorAll('.content-item').forEach(item => {
|
|
543
|
+
item.classList.add('widgetic-colors', 'widgetic-radius');
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
// Force visual update of all dropdown chevrons
|
|
547
|
+
document.querySelectorAll('.content-item .chevron').forEach(chevron => {
|
|
548
|
+
if (chevron instanceof HTMLElement) {
|
|
549
|
+
chevron.style.transform = 'rotate(0deg)';
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Add drag:move handler - update mirror position to follow mouse
|
|
555
|
+
currentDraggable.on('drag:move', (event: any) => {
|
|
556
|
+
if (isDragStarted) {
|
|
557
|
+
// Update mirror position to follow mouse cursor, accounting for scroll
|
|
558
|
+
if (event.originalEvent instanceof MouseEvent) {
|
|
559
|
+
requestAnimationFrame(() => {
|
|
560
|
+
const mirror = document.querySelector('.draggable-mirror');
|
|
561
|
+
if (mirror instanceof HTMLElement) {
|
|
562
|
+
// Position mirror accounting for grab offset
|
|
563
|
+
mirror.style.top = `${event.originalEvent.clientY - grabOffsetY}px`;
|
|
564
|
+
mirror.style.left = `${event.originalEvent.clientX - grabOffsetX}px`;
|
|
565
|
+
mirror.style.transform = 'translate3d(0,0,0)';
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (event.originalEvent instanceof MouseEvent) {
|
|
573
|
+
const deltaX = Math.abs(event.originalEvent.clientX - initialMousePosition.x);
|
|
574
|
+
const deltaY = Math.abs(event.originalEvent.clientY - initialMousePosition.y);
|
|
575
|
+
|
|
576
|
+
// Only start drag if moved more than threshold
|
|
577
|
+
if (deltaX > dragThreshold || deltaY > dragThreshold) {
|
|
578
|
+
isDragStarted = true;
|
|
579
|
+
// isDragging = true; - This is now set in drag:start
|
|
580
|
+
console.log('[Draggable] isDragStarted set to true');
|
|
581
|
+
|
|
582
|
+
requestAnimationFrame(() => {
|
|
583
|
+
const mirror = document.querySelector('.draggable-mirror');
|
|
584
|
+
if (mirror instanceof HTMLElement) {
|
|
585
|
+
// First, snap to the correct position to prevent flickering
|
|
586
|
+
mirror.style.top = `${event.originalEvent.clientY - grabOffsetY}px`;
|
|
587
|
+
mirror.style.left = `${event.originalEvent.clientX - grabOffsetX}px`;
|
|
588
|
+
mirror.style.transform = 'translate3d(0,0,0)';
|
|
589
|
+
|
|
590
|
+
// Then apply styling
|
|
591
|
+
mirror.style.height = '56px';
|
|
592
|
+
mirror.style.overflow = 'hidden';
|
|
593
|
+
mirror.style.border = '2px solid hsl(var(--grey2))';
|
|
594
|
+
mirror.style.borderRadius = 'var(--radius-small)';
|
|
595
|
+
mirror.style.backgroundColor = 'white';
|
|
596
|
+
mirror.style.boxShadow = 'none';
|
|
597
|
+
|
|
598
|
+
// Add our styling classes without changing any positioning
|
|
599
|
+
mirror.classList.add('widgetic-colors', 'widgetic-radius');
|
|
600
|
+
|
|
601
|
+
// Remove borders from all children
|
|
602
|
+
Array.from(mirror.querySelectorAll('*')).forEach(el => {
|
|
603
|
+
if (el instanceof HTMLElement) {
|
|
604
|
+
el.style.border = 'none';
|
|
605
|
+
el.style.borderTop = 'none';
|
|
606
|
+
el.style.borderBottom = 'none';
|
|
607
|
+
el.style.borderLeft = 'none';
|
|
608
|
+
el.style.borderRight = 'none';
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
// Ensure the mirror content is in collapsed state
|
|
613
|
+
const collapsibleContent = mirror.querySelector('[data-radix-collapsible-content]');
|
|
614
|
+
if (collapsibleContent instanceof HTMLElement) {
|
|
615
|
+
collapsibleContent.style.height = '0';
|
|
616
|
+
collapsibleContent.style.display = 'none';
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
} else {
|
|
621
|
+
// Cancel the drag if we haven't moved enough
|
|
622
|
+
event.cancel();
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
currentDraggable.on('drag:stop', (event: any) => {
|
|
628
|
+
// Clean up any mirror elements immediately
|
|
629
|
+
requestAnimationFrame(() => {
|
|
630
|
+
const mirrors = document.querySelectorAll('.draggable-mirror');
|
|
631
|
+
mirrors.forEach(mirror => {
|
|
632
|
+
if (mirror.parentNode) {
|
|
633
|
+
mirror.parentNode.removeChild(mirror);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// If we never started dragging, this was just a click
|
|
639
|
+
if (!isDragStarted) {
|
|
640
|
+
event.cancel();
|
|
641
|
+
forceCleanupState();
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Reset drag state
|
|
646
|
+
isDragStarted = false;
|
|
647
|
+
isDragging = false;
|
|
648
|
+
dragStartTime = 0;
|
|
649
|
+
initialMousePosition = { x: 0, y: 0 };
|
|
650
|
+
grabOffsetY = 0;
|
|
651
|
+
grabOffsetX = 0;
|
|
652
|
+
|
|
653
|
+
// Remove the class that forces fixed heights
|
|
654
|
+
document.body.classList.remove('dragging-active');
|
|
655
|
+
|
|
656
|
+
// Reset fixed heights from all items
|
|
657
|
+
document.querySelectorAll('.content-item[data-forced-collapsed="true"]').forEach(item => {
|
|
658
|
+
if (item instanceof HTMLElement) {
|
|
659
|
+
item.style.height = '';
|
|
660
|
+
item.style.minHeight = '';
|
|
661
|
+
item.style.maxHeight = '';
|
|
662
|
+
item.style.overflow = '';
|
|
663
|
+
item.removeAttribute('data-forced-collapsed');
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// Reset all collapsible content elements
|
|
668
|
+
document.querySelectorAll('[data-radix-collapsible-content]').forEach(content => {
|
|
669
|
+
if (content instanceof HTMLElement) {
|
|
670
|
+
content.style.display = '';
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
// Restore previously open items
|
|
675
|
+
setTimeout(() => {
|
|
676
|
+
openItems = new Set(previouslyOpen);
|
|
677
|
+
|
|
678
|
+
// Force update collapsible elements to match our state
|
|
679
|
+
document.querySelectorAll('[data-radix-collapsible-root]').forEach(root => {
|
|
680
|
+
if (root instanceof HTMLElement) {
|
|
681
|
+
const itemId = root.closest('.content-item')?.getAttribute('data-id');
|
|
682
|
+
if (itemId && openItems.has(itemId)) {
|
|
683
|
+
root.setAttribute('data-state', 'open');
|
|
684
|
+
const trigger = root.querySelector('[data-radix-collapsible-trigger]');
|
|
685
|
+
if (trigger instanceof HTMLElement) {
|
|
686
|
+
trigger.setAttribute('data-state', 'open');
|
|
687
|
+
trigger.setAttribute('aria-expanded', 'true');
|
|
688
|
+
}
|
|
689
|
+
const chevron = root.querySelector('.chevron');
|
|
690
|
+
if (chevron instanceof HTMLElement) {
|
|
691
|
+
chevron.style.transform = 'rotate(180deg)';
|
|
692
|
+
}
|
|
693
|
+
const content = root.querySelector('[data-radix-collapsible-content]');
|
|
694
|
+
if (content instanceof HTMLElement) {
|
|
695
|
+
content.style.height = 'auto';
|
|
696
|
+
content.style.overflow = 'visible';
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
}, 50);
|
|
702
|
+
|
|
703
|
+
// Perform normal cleanup
|
|
704
|
+
forceCleanupState();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
currentDraggable.on('sortable:sorted', (event: { newIndex: number; oldIndex: number }) => {
|
|
708
|
+
console.log('[Draggable] sortable:sorted event fired', { oldIndex: event.oldIndex, newIndex: event.newIndex });
|
|
709
|
+
try {
|
|
710
|
+
const { oldIndex, newIndex } = event;
|
|
711
|
+
|
|
712
|
+
// Add robust checks for valid indices
|
|
713
|
+
if (typeof oldIndex !== 'number' || typeof newIndex !== 'number' || oldIndex < 0 || newIndex < 0) {
|
|
714
|
+
console.error('[Drag] Invalid or missing indices from event. Aborting reorder.');
|
|
715
|
+
isDragStarted = false;
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// No need to do anything if the item was dropped in the same place
|
|
720
|
+
if (oldIndex === newIndex) {
|
|
721
|
+
console.log('[Drag] Item dropped in the same position. No change needed.');
|
|
722
|
+
isDragStarted = false;
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (!Array.isArray(contentSchema?.contentItems)) {
|
|
727
|
+
console.warn('[Drag] contentItems is not an array, skipping reorder');
|
|
728
|
+
isDragStarted = false;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
console.log('[Drag] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
732
|
+
|
|
733
|
+
const items = [...contentSchema.contentItems];
|
|
734
|
+
const [movedItem] = items.splice(oldIndex, 1);
|
|
735
|
+
items.splice(newIndex, 0, movedItem);
|
|
736
|
+
|
|
737
|
+
// Safety checks before updating state
|
|
738
|
+
if (hasDuplicateIds(items)) {
|
|
739
|
+
console.error('[Drag] Duplicate IDs detected after reordering with indices. Aborting update.', items.map(i => i.id));
|
|
740
|
+
isDragStarted = false;
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (items.length !== contentSchema.contentItems.length) {
|
|
745
|
+
console.error('[Drag] Array length mismatch after reordering with indices. Aborting update.');
|
|
746
|
+
isDragStarted = false;
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
const renumbered = renumberAutoItemTitles(items);
|
|
751
|
+
contentSchema = { ...contentSchema, contentItems: renumbered };
|
|
752
|
+
console.log('[Drag] After (reordered with indices):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
753
|
+
dispatch('updated', { contentSchema });
|
|
754
|
+
dispatchContentItemsChanged(contentSchema.contentItems);
|
|
755
|
+
|
|
756
|
+
} catch (err) {
|
|
757
|
+
console.error('[Drag] Error updating order in sortable:sorted:', err);
|
|
758
|
+
}
|
|
759
|
+
isDragStarted = false;
|
|
760
|
+
isDragging = false; // Allow afterUpdate to run again
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Update mirror handling - position mirror to follow mouse cursor directly
|
|
764
|
+
currentDraggable.on('mirror:created', (event: any) => {
|
|
765
|
+
const mirror = event.mirror;
|
|
766
|
+
if (mirror instanceof HTMLElement) {
|
|
767
|
+
// Add widgetic-colors class to access CSS variables
|
|
768
|
+
mirror.classList.add('widgetic-colors', 'widgetic-radius');
|
|
769
|
+
|
|
770
|
+
// Set border color to sage-green using CSS variable
|
|
771
|
+
mirror.style.border = '2px solid hsl(var(--sage-green))';
|
|
772
|
+
mirror.style.borderRadius = 'var(--radius-small)';
|
|
773
|
+
mirror.style.boxShadow = 'none';
|
|
774
|
+
|
|
775
|
+
// Style the mirror element, but do NOT set top, left, or transform.
|
|
776
|
+
mirror.style.height = '56px';
|
|
777
|
+
mirror.style.overflow = 'hidden';
|
|
778
|
+
mirror.style.position = 'fixed';
|
|
779
|
+
mirror.style.width = `${event.originalSource.offsetWidth}px`;
|
|
780
|
+
mirror.style.backgroundColor = 'white';
|
|
781
|
+
mirror.style.pointerEvents = 'none'; // Ensure it doesn't interfere with mouse events
|
|
782
|
+
mirror.style.zIndex = '1000'; // Ensure it's above other elements
|
|
783
|
+
|
|
784
|
+
// Position mirror at exact mouse cursor location
|
|
785
|
+
const mouseEvent = event.originalEvent;
|
|
786
|
+
if (mouseEvent instanceof MouseEvent) {
|
|
787
|
+
// Position mirror centered on mouse cursor
|
|
788
|
+
const mirrorHeight = 56; // Fixed height we set
|
|
789
|
+
const mirrorWidth = event.originalSource.offsetWidth;
|
|
790
|
+
|
|
791
|
+
// Center the mirror on the mouse cursor
|
|
792
|
+
grabOffsetY = mirrorHeight / 2;
|
|
793
|
+
grabOffsetX = mirrorWidth / 2 + DRAG_SHIFT_X;
|
|
794
|
+
|
|
795
|
+
// Position mirror so mouse is at center
|
|
796
|
+
mirror.style.top = `${mouseEvent.clientY - grabOffsetY}px`;
|
|
797
|
+
mirror.style.left = `${mouseEvent.clientX - grabOffsetX}px`;
|
|
798
|
+
mirror.style.transform = 'translate3d(0,0,0)';
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Style text with sage-green CSS variable
|
|
802
|
+
const textElements = mirror.querySelectorAll('h4, .content-title, span');
|
|
803
|
+
textElements.forEach(el => {
|
|
804
|
+
if (el instanceof HTMLElement) {
|
|
805
|
+
el.style.color = 'hsl(var(--sage-green))';
|
|
806
|
+
}
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
// Style only chevrons with sage-green CSS variable
|
|
810
|
+
const chevrons = mirror.querySelectorAll('.chevron, svg');
|
|
811
|
+
chevrons.forEach(el => {
|
|
812
|
+
if (el instanceof SVGElement) {
|
|
813
|
+
el.style.stroke = 'hsl(var(--sage-green))';
|
|
814
|
+
el.style.fill = 'transparent';
|
|
815
|
+
|
|
816
|
+
// Also handle any paths inside SVGs
|
|
817
|
+
const paths = el.querySelectorAll('path');
|
|
818
|
+
paths.forEach(path => {
|
|
819
|
+
path.setAttribute('stroke', 'hsl(var(--sage-green))');
|
|
820
|
+
path.setAttribute('fill', 'transparent');
|
|
821
|
+
});
|
|
822
|
+
|
|
823
|
+
// Check if this item was previously open and rotate the chevron accordingly
|
|
824
|
+
const sourceId = event.originalSource.getAttribute('data-id');
|
|
825
|
+
if (sourceId && previouslyOpen && previouslyOpen.has(sourceId) && el.classList.contains('chevron')) {
|
|
826
|
+
// Apply the same transition effect as the normal open/close operation
|
|
827
|
+
el.style.transition = 'transform 200ms ease-in-out';
|
|
828
|
+
el.style.transform = 'rotate(360deg)';
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// After initialisation, force-clear any inline transforms left by previous Sortable instances or the plugin
|
|
836
|
+
Array.from(containerElement.children).forEach(child => {
|
|
837
|
+
const el = child as HTMLElement;
|
|
838
|
+
el.style.transform = '';
|
|
839
|
+
el.style.transition = '';
|
|
840
|
+
el.style.order = '';
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
logOrder('after init');
|
|
844
|
+
|
|
845
|
+
isDraggableInitialized = true;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
} catch (error) {
|
|
849
|
+
console.error('❌ Error initializing draggable:', error);
|
|
850
|
+
isDraggableInitialized = false;
|
|
851
|
+
currentDraggable = null;
|
|
852
|
+
} finally {
|
|
853
|
+
isInitializing = false;
|
|
854
|
+
}
|
|
855
|
+
};
|
|
856
|
+
|
|
857
|
+
// Add this near other reactive declarations at the top level
|
|
858
|
+
$: if (Array.isArray(contentSchema?.contentItems) && contentSchema.contentItems.length > 0 && !itemTemplate) {
|
|
859
|
+
itemTemplate = JSON.parse(JSON.stringify(contentSchema.contentItems[0]));
|
|
860
|
+
// console.log('Template initialized:', itemTemplate);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Populate contentState with per-item values keyed as "itemId__propName".
|
|
864
|
+
// Canonical source order: existing state > parent items map > legacy prop.value.
|
|
865
|
+
$: if (Array.isArray(contentSchema?.contentItems)) {
|
|
866
|
+
let needsUpdate = false;
|
|
867
|
+
const patch: Record<string, InputValue> = {};
|
|
868
|
+
const parentItemValues = initialContentValues?.items;
|
|
869
|
+
for (const item of contentSchema.contentItems) {
|
|
870
|
+
for (const prop of item.properties || []) {
|
|
871
|
+
const key = `${item.id}__${prop.id || prop.name}`;
|
|
872
|
+
if (contentState[key] === undefined) {
|
|
873
|
+
// Prefer the parent's canonical saved-value map over the
|
|
874
|
+
// schema mirror, which older compositions may not fill.
|
|
875
|
+
const parentValue = parentItemValues?.[item.id]?.[prop.id || prop.name];
|
|
876
|
+
const seed = parentValue !== undefined ? parentValue : prop.value;
|
|
877
|
+
if (seed !== undefined) {
|
|
878
|
+
patch[key] = seed;
|
|
879
|
+
needsUpdate = true;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (needsUpdate) {
|
|
885
|
+
contentState = { ...contentState, ...patch };
|
|
886
|
+
console.log('[PropsEditor] Initialized content item state keys:', Object.keys(patch));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
onMount(() => {
|
|
891
|
+
// console.log('PropsEditor Mounted!');
|
|
892
|
+
|
|
893
|
+
// Initialize event listeners and state
|
|
894
|
+
if (designScrollViewport) {
|
|
895
|
+
designScrollViewport.addEventListener('scroll', (event) => handleScroll(event, 'design'));
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
if (contentScrollViewport) {
|
|
899
|
+
contentScrollViewport.addEventListener('scroll', (event) => handleScroll(event, 'content'));
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Start initialization
|
|
903
|
+
initializeEditor();
|
|
904
|
+
|
|
905
|
+
// Drag handles are now declarative (on:mousedown in template) — no imperative setup needed
|
|
906
|
+
|
|
907
|
+
if (defaultOpen && designSchema?.[0]?.components?.[0]?.propertyGroups) {
|
|
908
|
+
// Open all property groups by default
|
|
909
|
+
designOpenAccordion = designSchema[0].components
|
|
910
|
+
.flatMap((c: any) => (c.propertyGroups || []).map((pg: PropertyGroup) => pg.name))
|
|
911
|
+
.filter(Boolean);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Return cleanup function
|
|
915
|
+
return () => {
|
|
916
|
+
if (cleanup) cleanup();
|
|
917
|
+
if (designScrollViewport) {
|
|
918
|
+
designScrollViewport.removeEventListener('scroll', (event) => handleScroll(event, 'design'));
|
|
919
|
+
}
|
|
920
|
+
if (contentScrollViewport) {
|
|
921
|
+
contentScrollViewport.removeEventListener('scroll', (event) => handleScroll(event, 'content'));
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// Drag handle listeners are declarative (on:mousedown) — no manual cleanup needed
|
|
925
|
+
};
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* ON DESTROY
|
|
930
|
+
*/
|
|
931
|
+
onDestroy(() => {
|
|
932
|
+
// Clean up drag listeners
|
|
933
|
+
document.removeEventListener('mousemove', handleDragMove);
|
|
934
|
+
document.removeEventListener('mouseup', handleDragEnd);
|
|
935
|
+
|
|
936
|
+
// Clean up scroll listeners
|
|
937
|
+
if (designScrollViewport) {
|
|
938
|
+
designScrollViewport.removeEventListener('scroll', (event) => handleScroll(event, 'design'));
|
|
939
|
+
}
|
|
940
|
+
if (contentScrollViewport) {
|
|
941
|
+
contentScrollViewport.removeEventListener('scroll', (event) => handleScroll(event, 'content'));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Clean up any remaining tooltips
|
|
945
|
+
if (browser) {
|
|
946
|
+
const tooltips = document.querySelectorAll('[role="tooltip"]');
|
|
947
|
+
tooltips.forEach(tooltip => {
|
|
948
|
+
if (tooltip.parentNode) {
|
|
949
|
+
tooltip.parentNode.removeChild(tooltip);
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* METHODS
|
|
957
|
+
*/
|
|
958
|
+
/**
|
|
959
|
+
* Visibility Control Methods
|
|
960
|
+
* Handle conditional rendering of properties and property groups
|
|
961
|
+
*/
|
|
962
|
+
function shouldShowProperty(property: any, allProperties: any[], schema: 'design' | 'content'): boolean {
|
|
963
|
+
if (!property.visibilityCondition) return true;
|
|
964
|
+
return evaluateVisibilityCondition(property.visibilityCondition, allProperties, schema);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
function shouldShowPropertyGroup(propertyGroup: any, allProperties: any[], schema: 'design' | 'content'): boolean {
|
|
968
|
+
if (!propertyGroup.visibilityCondition) return true;
|
|
969
|
+
return evaluateVisibilityCondition(propertyGroup.visibilityCondition, allProperties, schema);
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Evaluates visibility conditions for properties and property groups
|
|
974
|
+
* @param visibilityCondition - Object containing dependency and condition info
|
|
975
|
+
* @param allProperties - Array of all properties in the current context
|
|
976
|
+
* @param schema - Whether this is for 'design' or 'content' schema
|
|
977
|
+
* @returns boolean indicating if the item should be visible
|
|
978
|
+
*/
|
|
979
|
+
function evaluateVisibilityCondition(visibilityCondition: any, allProperties: any[], schema: 'design' | 'content'): boolean {
|
|
980
|
+
const { dependsOn, condition } = visibilityCondition;
|
|
981
|
+
if (!dependsOn || !condition) return true;
|
|
982
|
+
|
|
983
|
+
// Create context object with values
|
|
984
|
+
const context: Record<string, any> = {};
|
|
985
|
+
|
|
986
|
+
try {
|
|
987
|
+
// Handle array of dependencies
|
|
988
|
+
const dependencies = Array.isArray(dependsOn) ? dependsOn : [dependsOn];
|
|
989
|
+
|
|
990
|
+
dependencies.forEach(dep => {
|
|
991
|
+
// Replace hyphens with underscores for valid JavaScript variable names
|
|
992
|
+
const safeKey = dep.replace(/-/g, '_');
|
|
993
|
+
const stateValue = schema === 'design' ? designState[dep] : contentState[dep];
|
|
994
|
+
const dependentProperty = allProperties.find(p => (p.id || p.name) === dep);
|
|
995
|
+
context[safeKey] = stateValue ?? dependentProperty?.defaultValue;
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
// Replace ALL hyphens with underscores in the condition string
|
|
999
|
+
const safeCondition = condition.replace(/-/g, '_');
|
|
1000
|
+
|
|
1001
|
+
const evalFunction = new Function(
|
|
1002
|
+
...Object.keys(context),
|
|
1003
|
+
`return ${safeCondition}`
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
return evalFunction(...Object.values(context));
|
|
1007
|
+
} catch (error) {
|
|
1008
|
+
// console.error('Error evaluating visibility condition:', error);
|
|
1009
|
+
// console.log('Condition:', condition);
|
|
1010
|
+
// console.log('Dependencies:', dependsOn);
|
|
1011
|
+
// console.log('Context:', context);
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* State Update Handler
|
|
1018
|
+
* Updates component state and dispatches changes to parent
|
|
1019
|
+
* @param schema - 'design' or 'content'
|
|
1020
|
+
* @param propertyId - Property identifier
|
|
1021
|
+
* @param value - New property value
|
|
1022
|
+
* @param opacity - Optional opacity value for color properties
|
|
1023
|
+
*/
|
|
1024
|
+
function updateState(schema: string, propertyId: string, value: InputValue, opacity?: number) {
|
|
1025
|
+
requestAnimationFrame(() => {
|
|
1026
|
+
if (schema === 'design') {
|
|
1027
|
+
const newState = { ...designState };
|
|
1028
|
+
newState[propertyId] = value;
|
|
1029
|
+
if (opacity !== undefined) {
|
|
1030
|
+
newState[`${propertyId}_opacity`] = opacity;
|
|
1031
|
+
}
|
|
1032
|
+
designState = newState;
|
|
1033
|
+
} else {
|
|
1034
|
+
const newState = { ...contentState };
|
|
1035
|
+
newState[propertyId] = value;
|
|
1036
|
+
if (opacity !== undefined) {
|
|
1037
|
+
newState[`${propertyId}_opacity`] = opacity;
|
|
1038
|
+
}
|
|
1039
|
+
contentState = newState;
|
|
1040
|
+
|
|
1041
|
+
// For content item properties (keyed as "itemId__propName"), also update item.properties[].value
|
|
1042
|
+
if (propertyId.includes('__') && Array.isArray(contentSchema?.contentItems)) {
|
|
1043
|
+
const [itemId, basePropName] = propertyId.split('__');
|
|
1044
|
+
const item = contentSchema.contentItems.find((i: ContentItem) => i.id === itemId);
|
|
1045
|
+
if (item) {
|
|
1046
|
+
const prop = item.properties.find((p: any) => (p.id || p.name) === basePropName);
|
|
1047
|
+
if (prop) prop.value = value;
|
|
1048
|
+
|
|
1049
|
+
// Update the collapsible header title when a "title"-like property changes
|
|
1050
|
+
const propName = (basePropName || '').toLowerCase();
|
|
1051
|
+
if (propName.includes('title') || propName.includes('name') || propName.includes('label')) {
|
|
1052
|
+
if (typeof value === 'string' && value.trim()) {
|
|
1053
|
+
item.title = value;
|
|
1054
|
+
contentSchema = contentSchema; // trigger Svelte reactivity
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// Dispatch with both composite key and item context for parent to handle
|
|
1062
|
+
let itemId: string | undefined;
|
|
1063
|
+
let originalPropertyId = propertyId;
|
|
1064
|
+
if (schema === 'content' && propertyId.includes('__')) {
|
|
1065
|
+
[itemId, originalPropertyId] = propertyId.split('__');
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
dispatch('propertyChange', { schema, propertyId: originalPropertyId, value, opacity, itemId });
|
|
1069
|
+
|
|
1070
|
+
if (schema === 'content' && itemId && Array.isArray(contentSchema?.contentItems)) {
|
|
1071
|
+
dispatchContentItemsChanged(contentSchema.contentItems);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// When editing a content item property, navigate widget to that item
|
|
1075
|
+
if (itemId) {
|
|
1076
|
+
const itemIndex = getContentItemIndex(itemId);
|
|
1077
|
+
if (itemIndex >= 0) {
|
|
1078
|
+
dispatch('goToItem', { index: itemIndex });
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
function getContentItemsMax(): number | null {
|
|
1085
|
+
const max = contentSchema?.options?.max;
|
|
1086
|
+
if (max === undefined || max === null || max === '' || max === 'none') return null;
|
|
1087
|
+
const parsed = Number(max);
|
|
1088
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function canAddContentItem(): boolean {
|
|
1092
|
+
const max = getContentItemsMax();
|
|
1093
|
+
if (max === null) return true;
|
|
1094
|
+
return Array.isArray(contentSchema?.contentItems) && contentSchema.contentItems.length < max;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
// Make states reactive
|
|
1098
|
+
$: designState = designState;
|
|
1099
|
+
$: contentState = contentState;
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Handles initial focus behavior for input controllers
|
|
1103
|
+
* Prevents auto-focus for TextArea components
|
|
1104
|
+
* @param InputComponent - The component type to check
|
|
1105
|
+
* @returns boolean indicating if component should receive initial focus
|
|
1106
|
+
*/
|
|
1107
|
+
function handleInitialFocus(InputComponent: any) {
|
|
1108
|
+
// Don't auto-focus TextAreaInputController
|
|
1109
|
+
return InputComponent !== TextAreaInputController;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
/**
|
|
1113
|
+
* Tracks focus events for input controllers and logs controller information
|
|
1114
|
+
* Updates last focused controller for both design and content tabs
|
|
1115
|
+
* @param controllerInfo - Object containing controller metadata
|
|
1116
|
+
*/
|
|
1117
|
+
function handleControllerFocus(controllerInfo: {
|
|
1118
|
+
type: string;
|
|
1119
|
+
name: string;
|
|
1120
|
+
description: string;
|
|
1121
|
+
index: number;
|
|
1122
|
+
groupName: string;
|
|
1123
|
+
}) {
|
|
1124
|
+
const controllerId = `${controllerInfo.groupName}-${controllerInfo.name}`;
|
|
1125
|
+
|
|
1126
|
+
// Only log if focus is actually changing
|
|
1127
|
+
if ((currentTab === 'design' && lastFocusedDesignController !== controllerId) ||
|
|
1128
|
+
(currentTab === 'content' && lastFocusedContentController !== controllerId)) {
|
|
1129
|
+
|
|
1130
|
+
if (currentTab === 'design') {
|
|
1131
|
+
lastFocusedDesignController = controllerId;
|
|
1132
|
+
} else {
|
|
1133
|
+
lastFocusedContentController = controllerId;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
console.log(`Controller focused in ${currentTab} tab:`, {
|
|
1137
|
+
controllerId,
|
|
1138
|
+
...controllerInfo
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Manages tab changes between design and content views
|
|
1145
|
+
* Ensures proper cleanup of modals before tab switch
|
|
1146
|
+
* @param tab - The tab to switch to (design or content)
|
|
1147
|
+
*/
|
|
1148
|
+
function handleTabChange(tab: string | undefined) {
|
|
1149
|
+
const currentValue = tab ?? getActiveTab();
|
|
1150
|
+
const previousTab = currentTab;
|
|
1151
|
+
|
|
1152
|
+
console.log('🔄 [PropsEditor] Tab Change:', {
|
|
1153
|
+
from: previousTab,
|
|
1154
|
+
to: currentValue,
|
|
1155
|
+
hasContentSchema: !!contentSchema,
|
|
1156
|
+
isInitialLoad
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
// Update tab state
|
|
1160
|
+
currentTab = currentValue;
|
|
1161
|
+
|
|
1162
|
+
// Set isInitialTabLoad to false after first tab change
|
|
1163
|
+
isInitialTabLoad = false;
|
|
1164
|
+
|
|
1165
|
+
// Only proceed with content-related operations if contentSchema exists
|
|
1166
|
+
if (currentValue === 'content' && !contentSchema) {
|
|
1167
|
+
console.log('ℹ️ [PropsEditor] Content tab selected but no content schema available');
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// Only log state if content exists
|
|
1172
|
+
if (contentSchema) {
|
|
1173
|
+
console.log('💾 Storing', currentValue, 'tab state:', {
|
|
1174
|
+
openItems: Array.from(openItems),
|
|
1175
|
+
openAccordion: currentValue === 'design' ? designOpenAccordion : contentOpenAccordion
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// First find and remove all modal-related elements, but preserve draggable
|
|
1180
|
+
const cleanup = () => {
|
|
1181
|
+
// Get all possible modal elements with more specific selectors
|
|
1182
|
+
const modalElements = document.querySelectorAll([
|
|
1183
|
+
'[data-radix-popper-content-wrapper]',
|
|
1184
|
+
'[role="dialog"]',
|
|
1185
|
+
'[cmdk-root]',
|
|
1186
|
+
'.font-popover-content',
|
|
1187
|
+
'[data-weight-popover]',
|
|
1188
|
+
'.color-picker-popover',
|
|
1189
|
+
'.command-menu',
|
|
1190
|
+
'[data-side="bottom"]',
|
|
1191
|
+
'[data-side="top"]',
|
|
1192
|
+
'.select-content',
|
|
1193
|
+
'.popover-content',
|
|
1194
|
+
'.command-dialog'
|
|
1195
|
+
].join(','));
|
|
1196
|
+
|
|
1197
|
+
console.log('🧹 Cleaning up modals:', {
|
|
1198
|
+
count: modalElements.length,
|
|
1199
|
+
elements: Array.from(modalElements).map(el => ({
|
|
1200
|
+
className: el.className,
|
|
1201
|
+
role: el.getAttribute('role'),
|
|
1202
|
+
state: el.getAttribute('data-state'),
|
|
1203
|
+
side: el.getAttribute('data-side')
|
|
1204
|
+
}))
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// Remove each modal immediately, but protect scroll-related and draggable elements
|
|
1208
|
+
modalElements.forEach(element => {
|
|
1209
|
+
// Don't remove if it's part of the core UI structure or draggable
|
|
1210
|
+
if (
|
|
1211
|
+
element.closest('[role="tabpanel"]') ||
|
|
1212
|
+
element.closest('[data-accordion-content]') ||
|
|
1213
|
+
element.closest('.accordion-content') ||
|
|
1214
|
+
element.getAttribute('role') === 'tabpanel' ||
|
|
1215
|
+
element.getAttribute('data-accordion-content') !== null ||
|
|
1216
|
+
element.closest('.scroll-area') ||
|
|
1217
|
+
element.closest('[data-radix-scroll-area-viewport]') ||
|
|
1218
|
+
element.classList.contains('scrollbar') ||
|
|
1219
|
+
element.classList.contains('scroll-area') ||
|
|
1220
|
+
element.hasAttribute('data-scrollable') ||
|
|
1221
|
+
element.classList.contains('draggable-mirror') ||
|
|
1222
|
+
element.classList.contains('draggable-source--is-dragging')
|
|
1223
|
+
) {
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (element.parentNode) {
|
|
1228
|
+
element.parentNode.removeChild(element);
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
// Clear the dropdowns store
|
|
1233
|
+
const currentDropdowns = get(openDropdowns);
|
|
1234
|
+
if (currentDropdowns.size > 0) {
|
|
1235
|
+
console.log('🗑️ Clearing dropdowns:', Array.from(currentDropdowns));
|
|
1236
|
+
}
|
|
1237
|
+
openDropdowns.set(new Set());
|
|
1238
|
+
|
|
1239
|
+
// Stop any playing media
|
|
1240
|
+
MediaManager.stopAll();
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
// Execute cleanup
|
|
1244
|
+
cleanup();
|
|
1245
|
+
|
|
1246
|
+
// Reset active controller
|
|
1247
|
+
activeControllerId = null;
|
|
1248
|
+
|
|
1249
|
+
// If switching to content tab and content exists, ensure draggable is initialized
|
|
1250
|
+
if (currentValue === 'content' && contentSchema) {
|
|
1251
|
+
// Wait for tab content to be rendered
|
|
1252
|
+
setTimeout(async () => {
|
|
1253
|
+
console.log('🎯 Checking draggable state in content tab');
|
|
1254
|
+
|
|
1255
|
+
// Check if draggable needs initialization
|
|
1256
|
+
const containers = document.querySelectorAll('.content-items');
|
|
1257
|
+
const needsInit = !isDraggableInitialized ||
|
|
1258
|
+
!currentDraggable ||
|
|
1259
|
+
containers.length === 0 ||
|
|
1260
|
+
!document.querySelector('.draggable-container');
|
|
1261
|
+
|
|
1262
|
+
if (needsInit) {
|
|
1263
|
+
console.log('🔄 Reinitializing draggable');
|
|
1264
|
+
await initializeDraggable();
|
|
1265
|
+
} else {
|
|
1266
|
+
console.log('✅ Draggable already initialized and working');
|
|
1267
|
+
}
|
|
1268
|
+
}, 50);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// Restore open items state for the selected tab
|
|
1272
|
+
openItems = currentValue === 'design' ? designOpenItems : contentOpenItems;
|
|
1273
|
+
const accordionToOpen = currentValue === 'design' ? designOpenAccordion : contentOpenAccordion;
|
|
1274
|
+
|
|
1275
|
+
// Only log if content exists
|
|
1276
|
+
if (contentSchema) {
|
|
1277
|
+
console.log('🔄 Restoring state for', currentValue, 'tab:', {
|
|
1278
|
+
openItems: Array.from(openItems),
|
|
1279
|
+
accordionToOpen
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Restore accordion states after a short delay to ensure DOM is ready
|
|
1284
|
+
setTimeout(() => {
|
|
1285
|
+
const itemsToOpen = Array.isArray(accordionToOpen) ? accordionToOpen : (accordionToOpen ? [accordionToOpen] : []);
|
|
1286
|
+
for (const name of itemsToOpen) {
|
|
1287
|
+
const accordion = document.querySelector(`[data-accordion-id="${name}"]`);
|
|
1288
|
+
if (accordion) {
|
|
1289
|
+
accordion.setAttribute('data-state', 'open');
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
if (itemsToOpen.length > 0) {
|
|
1293
|
+
console.log('📂 Opened accordions:', itemsToOpen);
|
|
1294
|
+
}
|
|
1295
|
+
}, 50);
|
|
1296
|
+
|
|
1297
|
+
// Handle focus after tab switch
|
|
1298
|
+
setTimeout(() => {
|
|
1299
|
+
const controllerToFocus = currentValue === 'design'
|
|
1300
|
+
? lastFocusedDesignController
|
|
1301
|
+
: lastFocusedContentController;
|
|
1302
|
+
|
|
1303
|
+
if (controllerToFocus) {
|
|
1304
|
+
console.log('🎯 Focusing last focused controller:', controllerToFocus);
|
|
1305
|
+
activeControllerId = controllerToFocus;
|
|
1306
|
+
focusController(controllerToFocus);
|
|
1307
|
+
} else {
|
|
1308
|
+
// Focus first controller
|
|
1309
|
+
const firstController = document.querySelector(
|
|
1310
|
+
`[data-tab="${currentValue}"] .input-controller:first-child`
|
|
1311
|
+
);
|
|
1312
|
+
if (firstController) {
|
|
1313
|
+
const id = firstController.getAttribute('id');
|
|
1314
|
+
if (id) {
|
|
1315
|
+
console.log('🎯 Focusing first controller:', id);
|
|
1316
|
+
activeControllerId = id;
|
|
1317
|
+
focusController(id);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}, 150);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Input Controller Management
|
|
1326
|
+
* Handles rendering and configuration of different input types
|
|
1327
|
+
*/
|
|
1328
|
+
function renderInputController(schema: string, property: any, propertyIndex: number, isFirstGroup: boolean, contentItemId?: string) {
|
|
1329
|
+
const basePropertyId = property.id || property.name;
|
|
1330
|
+
const propertyId = contentItemId ? `${contentItemId}__${basePropertyId}` : basePropertyId;
|
|
1331
|
+
const { type, inputController } = property;
|
|
1332
|
+
const InputControllerComponent = getInputControllerComponent(inputController.type.toLowerCase());
|
|
1333
|
+
const controllerId = `${schema}-${propertyId}`;
|
|
1334
|
+
|
|
1335
|
+
const stateValue = schema === 'design' ? designState[propertyId] : contentState[propertyId];
|
|
1336
|
+
let initialValue = stateValue !== undefined ? stateValue : (property.value !== undefined ? property.value : property.defaultValue);
|
|
1337
|
+
|
|
1338
|
+
// portalTarget for overlays (browser = full editor panel; textarea preview = tab panel)
|
|
1339
|
+
let portalTarget: string | HTMLElement = schema === 'design' ? 'tab-design-container' : 'tab-content-container';
|
|
1340
|
+
if (inputController.type.toLowerCase() === 'browser') {
|
|
1341
|
+
portalTarget = fileBrowserPortalHost ?? 'props-editor-file-browser-portal';
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Get opacity state for color pickers
|
|
1345
|
+
let initialOpacity;
|
|
1346
|
+
if (inputController.type.toLowerCase() === 'colorpicker') {
|
|
1347
|
+
const opacityKey = `${propertyId}_opacity`;
|
|
1348
|
+
const opacityState = schema === 'design' ? designState[opacityKey] : contentState[opacityKey];
|
|
1349
|
+
initialOpacity = opacityState !== undefined ? opacityState :
|
|
1350
|
+
(inputController.config?.defaultTransparency !== undefined ? inputController.config.defaultTransparency / 100 : 1);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
const { description, label, ...restInputController } = inputController;
|
|
1354
|
+
|
|
1355
|
+
// Only set shouldFocus on initial mount or explicit focus request
|
|
1356
|
+
const shouldFocus = activeControllerId === controllerId;
|
|
1357
|
+
|
|
1358
|
+
// Add special handling for position controller
|
|
1359
|
+
const additionalProps = inputController.type.toLowerCase() === 'position'
|
|
1360
|
+
? { onDragStateChange: (dragging: boolean) => isDragging = dragging }
|
|
1361
|
+
: {};
|
|
1362
|
+
|
|
1363
|
+
// Add visibility dependency tracking
|
|
1364
|
+
const hasVisibilityCondition = property.visibilityCondition !== undefined;
|
|
1365
|
+
|
|
1366
|
+
return {
|
|
1367
|
+
component: InputControllerComponent,
|
|
1368
|
+
props: {
|
|
1369
|
+
id: controllerId,
|
|
1370
|
+
value: initialValue,
|
|
1371
|
+
opacity: initialOpacity,
|
|
1372
|
+
onChange: (value: InputValue) => updateState(schema, propertyId, value),
|
|
1373
|
+
onOpacityChange: (opacity: number) => updateState(schema, propertyId, initialValue, opacity),
|
|
1374
|
+
portalTarget,
|
|
1375
|
+
...restInputController,
|
|
1376
|
+
...property.constraints,
|
|
1377
|
+
...additionalProps,
|
|
1378
|
+
config: inputController.config,
|
|
1379
|
+
controllerId,
|
|
1380
|
+
openDropdowns,
|
|
1381
|
+
toggleDropdown: (id: string) => {
|
|
1382
|
+
const currentSet = get(openDropdowns);
|
|
1383
|
+
if (currentSet.has(id)) {
|
|
1384
|
+
console.log(`[toggleDropdown] CLOSING ${id}`);
|
|
1385
|
+
openDropdowns.set(new Set());
|
|
1386
|
+
} else {
|
|
1387
|
+
console.log(`[toggleDropdown] OPENING ${id} (closing all others)`);
|
|
1388
|
+
openDropdowns.set(new Set([id]));
|
|
1389
|
+
}
|
|
1390
|
+
},
|
|
1391
|
+
shouldFocus,
|
|
1392
|
+
onFocus: () => {
|
|
1393
|
+
const newControllerId = `${schema}-${propertyId}`;
|
|
1394
|
+
if (schema === 'design' && lastFocusedDesignController !== newControllerId) {
|
|
1395
|
+
lastFocusedDesignController = newControllerId;
|
|
1396
|
+
handleControllerFocus({
|
|
1397
|
+
type: inputController.type,
|
|
1398
|
+
name: propertyId,
|
|
1399
|
+
description: description || propertyId,
|
|
1400
|
+
index: propertyIndex,
|
|
1401
|
+
groupName: schema
|
|
1402
|
+
});
|
|
1403
|
+
} else if (schema === 'content' && lastFocusedContentController !== newControllerId) {
|
|
1404
|
+
lastFocusedContentController = newControllerId;
|
|
1405
|
+
handleControllerFocus({
|
|
1406
|
+
type: inputController.type,
|
|
1407
|
+
name: propertyId,
|
|
1408
|
+
description: description || propertyId,
|
|
1409
|
+
index: propertyIndex,
|
|
1410
|
+
groupName: schema
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
},
|
|
1414
|
+
'data-visibility-dependent': hasVisibilityCondition
|
|
1415
|
+
},
|
|
1416
|
+
header: {
|
|
1417
|
+
label: description || propertyId,
|
|
1418
|
+
helpText: inputController.helpText
|
|
1419
|
+
}
|
|
1420
|
+
};
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
/**
|
|
1424
|
+
* Returns the appropriate input controller component based on type
|
|
1425
|
+
* Maps input types to their corresponding controller components
|
|
1426
|
+
* @param type - String identifier for the input type
|
|
1427
|
+
* @returns Component class for the specified input type
|
|
1428
|
+
*/
|
|
1429
|
+
function getInputControllerComponent(type: string) {
|
|
1430
|
+
switch (type.toLowerCase()) {
|
|
1431
|
+
case 'textinput': return TextInputController;
|
|
1432
|
+
case 'textarea':
|
|
1433
|
+
case 'textArea': return TextAreaPreviewInputController;
|
|
1434
|
+
case 'number': return NumberInputController;
|
|
1435
|
+
case 'slider': return SliderInputController;
|
|
1436
|
+
case 'sliderrange': return SliderRangeInputController;
|
|
1437
|
+
case 'dropdown': return DropdownInputController;
|
|
1438
|
+
case 'datepicker': return DatePickerInputController;
|
|
1439
|
+
case 'datefield': return DateFieldInputController;
|
|
1440
|
+
case 'rangecalendar': return RangeCalendarInputController;
|
|
1441
|
+
case 'switch': return SwitchInputController;
|
|
1442
|
+
case 'colorpicker': return ColorPickerInputController;
|
|
1443
|
+
case 'checkbox': return CheckboxInputController;
|
|
1444
|
+
case 'radiogroup':
|
|
1445
|
+
case 'radioGroup': return RadioGroupInputController;
|
|
1446
|
+
case 'font': return FontInputController;
|
|
1447
|
+
case 'position': return PositionInputController;
|
|
1448
|
+
case 'browser': return BrowserInputController;
|
|
1449
|
+
case 'fixedposition': return FixedPositionInputController;
|
|
1450
|
+
case 'vector': return VectorInputController;
|
|
1451
|
+
case 'angle': return AngleInputController;
|
|
1452
|
+
case 'time': return TimeInputController;
|
|
1453
|
+
case 'roundedcorners': return RoundedCornersInputController;
|
|
1454
|
+
case 'tags': return TagsInputController;
|
|
1455
|
+
default: return TextInputController;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
// Create a store for managing open dropdowns (only one should be open at a time)
|
|
1460
|
+
const openDropdowns = writable<Set<string>>(new Set());
|
|
1461
|
+
|
|
1462
|
+
/**
|
|
1463
|
+
* Closes all open dropdowns and color pickers
|
|
1464
|
+
* Used when changing tabs or when scroll events occur
|
|
1465
|
+
*/
|
|
1466
|
+
function closeAllDropdowns() {
|
|
1467
|
+
console.log('Closing all dropdowns');
|
|
1468
|
+
openDropdowns.set(new Set());
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// Debounce scroll handler
|
|
1472
|
+
const debounce = (fn: Function, ms = 16) => {
|
|
1473
|
+
let timeoutId: ReturnType<typeof setTimeout>;
|
|
1474
|
+
return function (this: any, ...args: any[]) {
|
|
1475
|
+
clearTimeout(timeoutId);
|
|
1476
|
+
timeoutId = setTimeout(() => fn.apply(this, args), ms);
|
|
1477
|
+
};
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// Optimize scroll handler
|
|
1481
|
+
const handleScroll = debounce((event: Event, tabId: 'design' | 'content') => {
|
|
1482
|
+
// Skip if scrolling in scrollable element
|
|
1483
|
+
if ((event.target as HTMLElement).closest('[data-allow-scroll="true"]')) return;
|
|
1484
|
+
|
|
1485
|
+
const wrapper = tabId === 'design' ? designContentWrapper : contentWrapper;
|
|
1486
|
+
if (!wrapper) return;
|
|
1487
|
+
|
|
1488
|
+
// Batch DOM reads
|
|
1489
|
+
const measurements = {
|
|
1490
|
+
scrollTop: wrapper.scrollTop,
|
|
1491
|
+
scrollHeight: wrapper.scrollHeight,
|
|
1492
|
+
clientHeight: wrapper.clientHeight
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
// Batch DOM writes
|
|
1496
|
+
requestAnimationFrame(() => {
|
|
1497
|
+
const state = {
|
|
1498
|
+
scrollPosition: measurements.scrollTop,
|
|
1499
|
+
contentHeight: measurements.scrollHeight,
|
|
1500
|
+
scrollAreaHeight: measurements.clientHeight,
|
|
1501
|
+
shouldShowScroll: measurements.scrollHeight > measurements.clientHeight
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
if (tabId === 'design') {
|
|
1505
|
+
designScrollState.set(state);
|
|
1506
|
+
} else {
|
|
1507
|
+
contentScrollState.set(state);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
// Only close dropdowns if actually scrolling
|
|
1511
|
+
if (measurements.scrollTop !== scrollPosition) {
|
|
1512
|
+
closeAllDropdowns();
|
|
1513
|
+
scrollPosition = measurements.scrollTop;
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
}, 16);
|
|
1517
|
+
|
|
1518
|
+
let contentItems: any[] = [];
|
|
1519
|
+
let openItems = new Set<string>();
|
|
1520
|
+
let draggedItem: string | null = null;
|
|
1521
|
+
let previouslyOpen = new Set<string>();
|
|
1522
|
+
|
|
1523
|
+
// Add after the state declarations
|
|
1524
|
+
$: isItemOpen = (itemId: string) => openItems.has(itemId);
|
|
1525
|
+
|
|
1526
|
+
/**
|
|
1527
|
+
* Manages the open/closed state of content items
|
|
1528
|
+
* @param itemId - Identifier for the content item to toggle
|
|
1529
|
+
*/
|
|
1530
|
+
function toggleItem(itemId: string) {
|
|
1531
|
+
console.log('🔄 Toggling item:', itemId, 'Current state:', openItems.has(itemId));
|
|
1532
|
+
|
|
1533
|
+
if (openItems.has(itemId)) {
|
|
1534
|
+
openItems.delete(itemId);
|
|
1535
|
+
previouslyOpen.delete(itemId);
|
|
1536
|
+
} else {
|
|
1537
|
+
openItems.add(itemId);
|
|
1538
|
+
previouslyOpen.add(itemId);
|
|
1539
|
+
|
|
1540
|
+
// When opening an item, tell the widget to navigate to it
|
|
1541
|
+
const itemIndex = getContentItemIndex(itemId);
|
|
1542
|
+
if (itemIndex >= 0) {
|
|
1543
|
+
dispatch('goToItem', { index: itemIndex });
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
// Create new Sets to trigger reactivity
|
|
1548
|
+
openItems = new Set(openItems);
|
|
1549
|
+
previouslyOpen = new Set(previouslyOpen);
|
|
1550
|
+
|
|
1551
|
+
console.log('✅ New state:', {
|
|
1552
|
+
isOpen: openItems.has(itemId),
|
|
1553
|
+
openItems: Array.from(openItems),
|
|
1554
|
+
previouslyOpen: Array.from(previouslyOpen)
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
/** Returns the index of a content item by its id, or -1 if not found */
|
|
1559
|
+
function getContentItemIndex(itemId: string): number {
|
|
1560
|
+
if (!contentSchema || !Array.isArray(contentSchema.contentItems)) return -1;
|
|
1561
|
+
return contentSchema.contentItems.findIndex((i: ContentItem) => i.id === itemId);
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Interface definitions for content structure
|
|
1566
|
+
*/
|
|
1567
|
+
interface Property {
|
|
1568
|
+
id: string;
|
|
1569
|
+
name?: string;
|
|
1570
|
+
type: string;
|
|
1571
|
+
defaultValue?: any;
|
|
1572
|
+
value?: any; // Add value property to interface
|
|
1573
|
+
constraints?: Record<string, any>;
|
|
1574
|
+
inputController: {
|
|
1575
|
+
type: string;
|
|
1576
|
+
description?: string;
|
|
1577
|
+
helpText?: string;
|
|
1578
|
+
config?: Record<string, any>;
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1582
|
+
interface ContentItem {
|
|
1583
|
+
id: string;
|
|
1584
|
+
properties: Array<Property>;
|
|
1585
|
+
orderNumber?: number;
|
|
1586
|
+
title?: string;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
interface ContentSchema {
|
|
1590
|
+
genericContent: {
|
|
1591
|
+
title: string;
|
|
1592
|
+
properties: Array<Property>;
|
|
1593
|
+
};
|
|
1594
|
+
contentItems: ContentItem[];
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
onMount(() => {
|
|
1598
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
1599
|
+
if (contentWrapper) {
|
|
1600
|
+
contentHeight = contentWrapper.scrollHeight;
|
|
1601
|
+
scrollAreaHeight = contentWrapper.clientHeight;
|
|
1602
|
+
// console.log('Content size changed - Height:', contentHeight, 'View:', scrollAreaHeight);
|
|
1603
|
+
}
|
|
1604
|
+
});
|
|
1605
|
+
|
|
1606
|
+
if (contentWrapper) {
|
|
1607
|
+
resizeObserver.observe(contentWrapper);
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
return () => {
|
|
1611
|
+
resizeObserver.disconnect();
|
|
1612
|
+
};
|
|
1613
|
+
});
|
|
1614
|
+
|
|
1615
|
+
/**
|
|
1616
|
+
* Focus Management
|
|
1617
|
+
* Controls input focus behavior and tab switching
|
|
1618
|
+
*/
|
|
1619
|
+
function focusController(controllerId: string) {
|
|
1620
|
+
console.log('Attempting to focus controller:', controllerId);
|
|
1621
|
+
activeControllerId = controllerId;
|
|
1622
|
+
const element = document.getElementById(controllerId);
|
|
1623
|
+
if (element) {
|
|
1624
|
+
const componentInstance = element.querySelector(':scope > *');
|
|
1625
|
+
if (componentInstance && 'focus' in componentInstance) {
|
|
1626
|
+
console.log('Calling focus method on component');
|
|
1627
|
+
(componentInstance as any).focus();
|
|
1628
|
+
} else {
|
|
1629
|
+
const input = element.querySelector('input, textarea, [role="slider"], button');
|
|
1630
|
+
if (input instanceof HTMLElement) {
|
|
1631
|
+
console.log('Found focusable element:', input);
|
|
1632
|
+
input.focus();
|
|
1633
|
+
} else {
|
|
1634
|
+
console.log('No focusable element found in controller');
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
} else {
|
|
1638
|
+
console.log('Controller element not found:', controllerId);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// Update the Accordion components to include value prop
|
|
1643
|
+
function handleAccordionChange(value: string | string[]) {
|
|
1644
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
1645
|
+
if (currentTab === 'design') {
|
|
1646
|
+
designOpenAccordion = arr;
|
|
1647
|
+
} else {
|
|
1648
|
+
contentOpenAccordion = arr;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
// Stores content property values for open groups
|
|
1653
|
+
let contentOpenItems = new Set<string>();
|
|
1654
|
+
|
|
1655
|
+
// Functions to expand or collapse all content items
|
|
1656
|
+
function expandAllContentItems() {
|
|
1657
|
+
try {
|
|
1658
|
+
if (contentSchema && Array.isArray(contentSchema.contentItems)) {
|
|
1659
|
+
// console.log("Expanding content items from contentSchema.contentItems");
|
|
1660
|
+
contentSchema.contentItems.forEach((item: ContentItem) => {
|
|
1661
|
+
if (item.id) {
|
|
1662
|
+
openItems.add(item.id);
|
|
1663
|
+
console.log("Added content item:", item.id);
|
|
1664
|
+
}
|
|
1665
|
+
});
|
|
1666
|
+
} else {
|
|
1667
|
+
console.warn("contentSchema is not in recognized format", contentSchema);
|
|
1668
|
+
}
|
|
1669
|
+
// Trigger reactivity for openItems
|
|
1670
|
+
openItems = new Set(openItems);
|
|
1671
|
+
} catch (err) {
|
|
1672
|
+
console.error("Error expanding all content items", err);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
function collapseAllContentItems() {
|
|
1677
|
+
// console.log('🔄 Collapsing all items - Initial state:', Array.from(openItems));
|
|
1678
|
+
// Only clear the current openItems, don't touch previouslyOpen
|
|
1679
|
+
openItems = new Set();
|
|
1680
|
+
// console.log("✅ Cleared openItems - New state:", Array.from(openItems));
|
|
1681
|
+
// Trigger reactivity for openItems
|
|
1682
|
+
openItems = new Set(openItems);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
// Keydown handler: SHIFT + '+' expands all, SHIFT + '-' collapses all
|
|
1686
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
1687
|
+
// console.log("handleKeyDown triggered: key=", event.key, "shiftKey=", event.shiftKey);
|
|
1688
|
+
if (event.shiftKey && event.key === 'ArrowDown') {
|
|
1689
|
+
// console.log("Expanding all content items");
|
|
1690
|
+
expandAllContentItems();
|
|
1691
|
+
// console.log("After expanding, openItems:", Array.from(openItems));
|
|
1692
|
+
}
|
|
1693
|
+
if (event.shiftKey && event.key === 'ArrowUp') {
|
|
1694
|
+
// console.log("Collapsing all content items");
|
|
1695
|
+
collapseAllContentItems();
|
|
1696
|
+
// console.log("After collapsing, openItems:", Array.from(openItems));
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// Add keydown event listener on mount and remove it on destroy
|
|
1701
|
+
onMount(() => {
|
|
1702
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
onDestroy(() => {
|
|
1706
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
1707
|
+
});
|
|
1708
|
+
|
|
1709
|
+
/**
|
|
1710
|
+
* Handles adding a new content item
|
|
1711
|
+
* Creates a new item based on the first item's template
|
|
1712
|
+
* and appends it to the content items list
|
|
1713
|
+
*/
|
|
1714
|
+
function formatDefaultItemTitle(itemNumber: number): string {
|
|
1715
|
+
if (itemNumber < 10) return `Item 0${itemNumber}`;
|
|
1716
|
+
return `Item ${itemNumber}`;
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
function formatSlideTitle(slideNumber: number): string {
|
|
1720
|
+
return `Slide ${slideNumber}`;
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
function formatPhotoTitle(photoNumber: number): string {
|
|
1724
|
+
return `Photo ${photoNumber}`;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
function isAutoGeneratedItemTitle(title: string | undefined): boolean {
|
|
1728
|
+
if (!title?.trim()) return true;
|
|
1729
|
+
const trimmed = title.trim();
|
|
1730
|
+
return (
|
|
1731
|
+
/^Item 0?\d+$/i.test(trimmed) ||
|
|
1732
|
+
/^Slide \d+$/i.test(trimmed) ||
|
|
1733
|
+
/^Photo \d+$/i.test(trimmed)
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function isSlideAutoTitle(title: string | undefined): boolean {
|
|
1738
|
+
return !!title?.trim() && /^Slide \d+$/i.test(title.trim());
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function isPhotoAutoTitle(title: string | undefined): boolean {
|
|
1742
|
+
return !!title?.trim() && /^Photo \d+$/i.test(title.trim());
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function formatAutoItemTitle(title: string | undefined, position: number): string {
|
|
1746
|
+
if (isSlideAutoTitle(title)) return formatSlideTitle(position);
|
|
1747
|
+
if (isPhotoAutoTitle(title)) return formatPhotoTitle(position);
|
|
1748
|
+
return formatDefaultItemTitle(position);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
type AutoTitlePattern = 'photo' | 'slide' | 'item';
|
|
1752
|
+
|
|
1753
|
+
/** Pick Photo / Slide / Item naming from the dominant pattern in existing items. */
|
|
1754
|
+
function detectDominantAutoTitlePattern(items: ContentItem[]): AutoTitlePattern {
|
|
1755
|
+
let photoCount = 0;
|
|
1756
|
+
let slideCount = 0;
|
|
1757
|
+
let itemCount = 0;
|
|
1758
|
+
|
|
1759
|
+
for (const item of items) {
|
|
1760
|
+
const title = item.title?.trim() || '';
|
|
1761
|
+
if (/^Photo \d+$/i.test(title)) photoCount++;
|
|
1762
|
+
else if (/^Slide \d+$/i.test(title)) slideCount++;
|
|
1763
|
+
else if (/^Item 0?\d+$/i.test(title) || !title) itemCount++;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
if (photoCount > 0 && photoCount >= slideCount && photoCount >= itemCount) return 'photo';
|
|
1767
|
+
if (slideCount > 0 && slideCount >= itemCount) return 'slide';
|
|
1768
|
+
return 'item';
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
function formatNewItemTitle(items: ContentItem[]): string {
|
|
1772
|
+
const nextNumber = items.length + 1;
|
|
1773
|
+
const pattern = detectDominantAutoTitlePattern(items);
|
|
1774
|
+
if (pattern === 'photo') return formatPhotoTitle(nextNumber);
|
|
1775
|
+
if (pattern === 'slide') return formatSlideTitle(nextNumber);
|
|
1776
|
+
return formatDefaultItemTitle(nextNumber);
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
function renumberAutoItemTitles(items: ContentItem[]): ContentItem[] {
|
|
1780
|
+
return items.map((item, index) => {
|
|
1781
|
+
const position = index + 1;
|
|
1782
|
+
if (!isAutoGeneratedItemTitle(item.title)) {
|
|
1783
|
+
return { ...item, orderNumber: position };
|
|
1784
|
+
}
|
|
1785
|
+
const nextTitle = formatAutoItemTitle(item.title, position);
|
|
1786
|
+
return { ...item, title: nextTitle, orderNumber: position };
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
function getDisplayItemTitle(item: ContentItem, index: number): string {
|
|
1791
|
+
if (Array.isArray(item.properties)) {
|
|
1792
|
+
const titleProp = item.properties.find((p) => {
|
|
1793
|
+
const n = (p.id || p.name || '').toLowerCase();
|
|
1794
|
+
return n.includes('title') || n.includes('name') || n.includes('label');
|
|
1795
|
+
});
|
|
1796
|
+
if (titleProp) {
|
|
1797
|
+
const stateKey = `${item.id}__${titleProp.id || titleProp.name}`;
|
|
1798
|
+
const editedVal = contentState[stateKey];
|
|
1799
|
+
if (editedVal && typeof editedVal === 'string' && editedVal.trim()) {
|
|
1800
|
+
return editedVal;
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
if (item.title && item.title.trim() && !isAutoGeneratedItemTitle(item.title)) {
|
|
1805
|
+
return item.title;
|
|
1806
|
+
}
|
|
1807
|
+
if (isAutoGeneratedItemTitle(item.title)) {
|
|
1808
|
+
return formatAutoItemTitle(item.title, index + 1);
|
|
1809
|
+
}
|
|
1810
|
+
return formatDefaultItemTitle(index + 1);
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/** Hide genericContent when it duplicates the contentItems template (codegen anti-pattern). */
|
|
1814
|
+
function shouldShowGenericContentSection(schema: typeof contentSchema): boolean {
|
|
1815
|
+
const genericProps = schema?.genericContent?.properties;
|
|
1816
|
+
if (!Array.isArray(genericProps) || genericProps.length === 0) return false;
|
|
1817
|
+
const items = schema?.contentItems;
|
|
1818
|
+
if (!Array.isArray(items) || items.length === 0) return true;
|
|
1819
|
+
const firstItemProps = items[0]?.properties;
|
|
1820
|
+
if (!Array.isArray(firstItemProps) || firstItemProps.length === 0) return true;
|
|
1821
|
+
const genericIds = genericProps.map((p) => p.id || p.name).sort().join(',');
|
|
1822
|
+
const itemIds = firstItemProps.map((p) => p.id || p.name).sort().join(',');
|
|
1823
|
+
return genericIds !== itemIds;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
function handleAddNewItem() {
|
|
1827
|
+
try {
|
|
1828
|
+
if (!contentSchema) {
|
|
1829
|
+
console.warn('Cannot add item: contentSchema is undefined');
|
|
1830
|
+
return;
|
|
1831
|
+
}
|
|
1832
|
+
if (!Array.isArray(contentSchema.contentItems)) {
|
|
1833
|
+
contentSchema = { ...contentSchema, contentItems: [] };
|
|
1834
|
+
}
|
|
1835
|
+
if (contentSchema.contentItems.length === 0 && itemTemplate) {
|
|
1836
|
+
contentSchema = {
|
|
1837
|
+
...contentSchema,
|
|
1838
|
+
contentItems: [JSON.parse(JSON.stringify(itemTemplate))],
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
if (contentSchema.contentItems.length === 0 && contentSchema.genericContent?.properties?.length) {
|
|
1842
|
+
const uniqueId = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1843
|
+
contentSchema = {
|
|
1844
|
+
...contentSchema,
|
|
1845
|
+
contentItems: [{
|
|
1846
|
+
id: uniqueId,
|
|
1847
|
+
title: contentSchema.genericContent.title || 'Item 1',
|
|
1848
|
+
orderNumber: 1,
|
|
1849
|
+
properties: contentSchema.genericContent.properties.map((p: any) => ({ ...p })),
|
|
1850
|
+
}],
|
|
1851
|
+
};
|
|
1852
|
+
}
|
|
1853
|
+
if (contentSchema.contentItems.length === 0) {
|
|
1854
|
+
console.warn('Cannot add item: no content item template in schema');
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
if (!canAddContentItem()) {
|
|
1858
|
+
const max = getContentItemsMax();
|
|
1859
|
+
console.warn(`[Add] Blocked: content item limit reached (max ${max})`);
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
// Log before add
|
|
1863
|
+
console.log('[Add] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
1864
|
+
// Generate a unique ID for the new item
|
|
1865
|
+
let uniqueId: string;
|
|
1866
|
+
do {
|
|
1867
|
+
uniqueId = `${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1868
|
+
} while (contentSchema.contentItems.some((item: ContentItem) => item.id === uniqueId));
|
|
1869
|
+
const newTitle = formatNewItemTitle(contentSchema.contentItems);
|
|
1870
|
+
// Create the new item (copy template or last item structure)
|
|
1871
|
+
const template = contentSchema.contentItems[0];
|
|
1872
|
+
const newItem: ContentItem = {
|
|
1873
|
+
...template,
|
|
1874
|
+
id: uniqueId,
|
|
1875
|
+
title: newTitle,
|
|
1876
|
+
orderNumber: contentSchema.contentItems.length + 1,
|
|
1877
|
+
properties: template.properties.map((p: any) => ({ ...p })),
|
|
1878
|
+
};
|
|
1879
|
+
// Remove the new item if it exists elsewhere (shouldn't, but for safety)
|
|
1880
|
+
const nextItems = [...contentSchema.contentItems.filter((i: ContentItem) => i.id !== uniqueId), newItem];
|
|
1881
|
+
if (hasDuplicateIds(nextItems)) {
|
|
1882
|
+
console.error('[Add] Duplicate IDs detected after add:', nextItems.map(i => i.id));
|
|
1883
|
+
return;
|
|
1884
|
+
}
|
|
1885
|
+
contentSchema = { ...contentSchema, contentItems: nextItems };
|
|
1886
|
+
// Defer opening the item until after it's positioned
|
|
1887
|
+
// openItems = new Set([...openItems, uniqueId]);
|
|
1888
|
+
|
|
1889
|
+
// Dispatch the updated event
|
|
1890
|
+
dispatch('updated', { contentSchema });
|
|
1891
|
+
// Notify parent about the full items list so WidgetPreview can send it to iframe
|
|
1892
|
+
dispatchContentItemsChanged(contentSchema.contentItems);
|
|
1893
|
+
// Navigate widget to the newly added item
|
|
1894
|
+
const newItemIndex = contentSchema.contentItems.findIndex((i: ContentItem) => i.id === uniqueId);
|
|
1895
|
+
if (newItemIndex >= 0) {
|
|
1896
|
+
dispatch('goToItem', { index: newItemIndex });
|
|
1897
|
+
}
|
|
1898
|
+
// Log after add
|
|
1899
|
+
console.log('[Add] After (should be at end):', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
1900
|
+
console.log('[Add] New item added:', newItem);
|
|
1901
|
+
|
|
1902
|
+
// Ensure DOM order matches array order after adding new item
|
|
1903
|
+
setTimeout(() => {
|
|
1904
|
+
const container = document.querySelector('.content-items');
|
|
1905
|
+
if (container && Array.isArray(contentSchema?.contentItems)) {
|
|
1906
|
+
const elementMap = new Map<string, HTMLElement>();
|
|
1907
|
+
Array.from(container.querySelectorAll('.content-item')).forEach((el: Element) => {
|
|
1908
|
+
const id = (el as HTMLElement).dataset.id;
|
|
1909
|
+
if (id) elementMap.set(id, el as HTMLElement);
|
|
1910
|
+
});
|
|
1911
|
+
|
|
1912
|
+
const desiredOrderIds = contentSchema.contentItems.map((item: ContentItem) => item.id);
|
|
1913
|
+
|
|
1914
|
+
// Surgical re-ordering to minimize DOM moves and flicker
|
|
1915
|
+
for (let i = 0; i < desiredOrderIds.length; i++) {
|
|
1916
|
+
const desiredId = desiredOrderIds[i];
|
|
1917
|
+
const currentDomNode = container.children[i] as HTMLElement | undefined;
|
|
1918
|
+
|
|
1919
|
+
// Only move the element if it's not already in the correct position
|
|
1920
|
+
if (currentDomNode?.dataset.id !== desiredId) {
|
|
1921
|
+
const elementToMove = elementMap.get(desiredId);
|
|
1922
|
+
if (elementToMove) {
|
|
1923
|
+
container.insertBefore(elementToMove, currentDomNode || null);
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// Now that the item is positioned, open it to reveal content
|
|
1929
|
+
openItems = new Set([...openItems, uniqueId]);
|
|
1930
|
+
}
|
|
1931
|
+
}, 0);
|
|
1932
|
+
} catch (err) {
|
|
1933
|
+
console.error('[Add] Error:', err);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// Helper function to clone properties
|
|
1938
|
+
function cloneProperties(properties: Array<any>): Property[] {
|
|
1939
|
+
if (!properties || !Array.isArray(properties)) {
|
|
1940
|
+
return [];
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
return properties.map(prop => {
|
|
1944
|
+
// Create a deep clone of each property
|
|
1945
|
+
const clonedProp: Property = {
|
|
1946
|
+
id: prop.id || prop.name,
|
|
1947
|
+
name: prop.name || prop.id,
|
|
1948
|
+
type: prop.type || 'string', // Ensure type is set
|
|
1949
|
+
defaultValue: prop.defaultValue,
|
|
1950
|
+
value: prop.defaultValue, // Reset to default value
|
|
1951
|
+
inputController: {
|
|
1952
|
+
type: prop.inputController?.type || 'text', // Ensure inputController.type is set
|
|
1953
|
+
description: prop.inputController?.description,
|
|
1954
|
+
helpText: prop.inputController?.helpText,
|
|
1955
|
+
config: prop.inputController?.config ? { ...prop.inputController.config } : undefined
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
return clonedProp;
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/**
|
|
1963
|
+
* Dispatches the full content items list to the parent so WidgetPreview
|
|
1964
|
+
* can forward it to the widget iframe via postMessage.
|
|
1965
|
+
* Each item is serialized with its id, title, and current property values.
|
|
1966
|
+
*/
|
|
1967
|
+
function normalizeContentPropertyValue(raw: unknown): unknown {
|
|
1968
|
+
if (raw && typeof raw === 'object' && 'url' in (raw as Record<string, unknown>)) {
|
|
1969
|
+
return (raw as { url?: string }).url ?? '';
|
|
1970
|
+
}
|
|
1971
|
+
return raw ?? '';
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
function dispatchContentItemsChanged(items: ContentItem[]) {
|
|
1975
|
+
const serialized = items.map((item: ContentItem) => {
|
|
1976
|
+
const props: Record<string, any> = {};
|
|
1977
|
+
if (Array.isArray(item.properties)) {
|
|
1978
|
+
item.properties.forEach((p: Property) => {
|
|
1979
|
+
const key = p.id || p.name;
|
|
1980
|
+
// Canonical state first; schema mirror/defaultValue are legacy
|
|
1981
|
+
// seeds for keys the editor never touched.
|
|
1982
|
+
props[key] = normalizeContentPropertyValue(
|
|
1983
|
+
contentState[`${item.id}__${key}`] ?? p.value ?? p.defaultValue
|
|
1984
|
+
);
|
|
1985
|
+
});
|
|
1986
|
+
// Carry opacity state so parent saves don't lose per-property
|
|
1987
|
+
// transparency edits made through color pickers.
|
|
1988
|
+
for (const stateKey of Object.keys(contentState)) {
|
|
1989
|
+
if (!stateKey.startsWith(`${item.id}__`) || !stateKey.endsWith('_opacity')) continue;
|
|
1990
|
+
const baseKey = stateKey.slice(0, -'_opacity'.length);
|
|
1991
|
+
if (props[baseKey] !== undefined) {
|
|
1992
|
+
props[`${baseKey}_opacity`] = contentState[stateKey];
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
return { id: item.id, title: item.title, ...props };
|
|
1997
|
+
});
|
|
1998
|
+
console.log('[PropsEditor] Dispatching contentItemsChanged:', serialized.length, 'items');
|
|
1999
|
+
dispatch('contentItemsChanged', { items: serialized });
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
/**
|
|
2003
|
+
* Handles deleting a content item
|
|
2004
|
+
* Removes the item with the specified ID from the content items list
|
|
2005
|
+
* @param itemId - The ID of the item to delete
|
|
2006
|
+
*/
|
|
2007
|
+
function handleDeleteItem(itemId: string) {
|
|
2008
|
+
try {
|
|
2009
|
+
if (!contentSchema || !Array.isArray(contentSchema?.contentItems)) {
|
|
2010
|
+
console.warn('Cannot delete item: contentSchema or contentItems is undefined/not an array');
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// Log before deletion
|
|
2015
|
+
console.log('[Delete] Before:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
2016
|
+
const deletedIndex = contentSchema.contentItems.findIndex((i: ContentItem) => i.id === itemId);
|
|
2017
|
+
const initialLength = contentSchema.contentItems.length;
|
|
2018
|
+
contentSchema.contentItems = contentSchema.contentItems.filter((i: ContentItem) => i.id !== itemId);
|
|
2019
|
+
openItems.delete(itemId);
|
|
2020
|
+
previouslyOpen.delete(itemId);
|
|
2021
|
+
openItems = new Set(openItems);
|
|
2022
|
+
previouslyOpen = new Set(previouslyOpen);
|
|
2023
|
+
if (contentSchema.contentItems.length === initialLength) {
|
|
2024
|
+
console.warn('🤔 No item found with ID:', itemId);
|
|
2025
|
+
} else {
|
|
2026
|
+
// Log after deletion
|
|
2027
|
+
console.log('[Delete] After:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
2028
|
+
// Notify parent about the updated items list
|
|
2029
|
+
dispatch('updated', { contentSchema });
|
|
2030
|
+
dispatchContentItemsChanged(contentSchema.contentItems);
|
|
2031
|
+
// Navigate widget to the previous item (or first if deleted was first)
|
|
2032
|
+
if (contentSchema.contentItems.length > 0) {
|
|
2033
|
+
const targetIndex = Math.max(0, deletedIndex - 1);
|
|
2034
|
+
dispatch('goToItem', { index: targetIndex });
|
|
2035
|
+
}
|
|
2036
|
+
setTimeout(() => {
|
|
2037
|
+
console.log('🔄 Reinitializing draggable after deletion');
|
|
2038
|
+
initializeDraggable();
|
|
2039
|
+
}, 0);
|
|
2040
|
+
}
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
console.error('❌ Error deleting content item:', error);
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// Add intersection observer for content items
|
|
2047
|
+
onMount(() => {
|
|
2048
|
+
const observer = new IntersectionObserver(
|
|
2049
|
+
(entries) => {
|
|
2050
|
+
entries.forEach(entry => {
|
|
2051
|
+
const element = entry.target as HTMLElement;
|
|
2052
|
+
if (!entry.isIntersecting) {
|
|
2053
|
+
element.style.visibility = 'hidden';
|
|
2054
|
+
} else {
|
|
2055
|
+
element.style.visibility = 'visible';
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
},
|
|
2059
|
+
{
|
|
2060
|
+
root: contentWrapper,
|
|
2061
|
+
rootMargin: '100px 0px'
|
|
2062
|
+
}
|
|
2063
|
+
);
|
|
2064
|
+
|
|
2065
|
+
// Observe content items
|
|
2066
|
+
document.querySelectorAll('.content-item').forEach(item => {
|
|
2067
|
+
observer.observe(item);
|
|
2068
|
+
});
|
|
2069
|
+
|
|
2070
|
+
return () => observer.disconnect();
|
|
2071
|
+
});
|
|
2072
|
+
|
|
2073
|
+
// Keep CSS --index in sync with DOM position **after** Svelte has rendered updates
|
|
2074
|
+
afterUpdate(() => {
|
|
2075
|
+
if (isDragging) return;
|
|
2076
|
+
const items = getContentItems();
|
|
2077
|
+
if (items.length === 0) return;
|
|
2078
|
+
|
|
2079
|
+
items.forEach((item: ContentItem, index: number) => {
|
|
2080
|
+
const el = document.querySelector(`[data-id="${item.id}"]`);
|
|
2081
|
+
if (el instanceof HTMLElement) {
|
|
2082
|
+
el.style.setProperty('--index', index.toString());
|
|
2083
|
+
}
|
|
2084
|
+
});
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
// Add this near the top of the script section where other variables are declared
|
|
2088
|
+
let isMounted = false;
|
|
2089
|
+
let isInitialLoad = true;
|
|
2090
|
+
let isFirstLoad = true;
|
|
2091
|
+
|
|
2092
|
+
|
|
2093
|
+
onMount(() => {
|
|
2094
|
+
isMounted = true;
|
|
2095
|
+
|
|
2096
|
+
// Initialize editor
|
|
2097
|
+
initializeEditor();
|
|
2098
|
+
|
|
2099
|
+
// Initialize the correct tab based on schema availability
|
|
2100
|
+
const initialTab = getActiveTab();
|
|
2101
|
+
if (initialTab === 'content' && contentSchema) {
|
|
2102
|
+
console.log('🎯 Initializing content tab as default');
|
|
2103
|
+
currentTab = 'content';
|
|
2104
|
+
|
|
2105
|
+
// Wait for DOM to be ready
|
|
2106
|
+
setTimeout(async () => {
|
|
2107
|
+
try {
|
|
2108
|
+
console.log('🔄 Initializing draggable for content tab');
|
|
2109
|
+
await initializeDraggable();
|
|
2110
|
+
|
|
2111
|
+
// Set initial states
|
|
2112
|
+
isInitialLoad = false;
|
|
2113
|
+
isFirstLoad = false;
|
|
2114
|
+
|
|
2115
|
+
// Force a UI update
|
|
2116
|
+
contentSchema = { ...contentSchema };
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
console.error('❌ Error initializing content tab:', error);
|
|
2119
|
+
}
|
|
2120
|
+
}, 100);
|
|
2121
|
+
} else {
|
|
2122
|
+
// Set isInitialLoad to false after the first animation completes
|
|
2123
|
+
setTimeout(() => {
|
|
2124
|
+
isInitialLoad = false;
|
|
2125
|
+
isFirstLoad = false;
|
|
2126
|
+
}, 300); // Match this with your animation duration
|
|
2127
|
+
}
|
|
2128
|
+
});
|
|
2129
|
+
|
|
2130
|
+
onMount(() => {
|
|
2131
|
+
// Add scroll observer
|
|
2132
|
+
if (contentScrollViewport && contentScrollViewport instanceof HTMLElement) {
|
|
2133
|
+
const scrollObserver = new MutationObserver((mutations) => {
|
|
2134
|
+
mutations.forEach((mutation) => {
|
|
2135
|
+
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
|
2136
|
+
// console.log('🔍 Content changed, checking scroll position');
|
|
2137
|
+
const scrollHeight = contentScrollViewport?.scrollHeight || 0;
|
|
2138
|
+
const scrollTop = contentScrollViewport?.scrollTop || 0;
|
|
2139
|
+
const clientHeight = contentScrollViewport?.clientHeight || 0;
|
|
2140
|
+
|
|
2141
|
+
// console.log('📊 Scroll metrics:', {
|
|
2142
|
+
// scrollHeight,
|
|
2143
|
+
// scrollTop,
|
|
2144
|
+
// clientHeight,
|
|
2145
|
+
// distanceToBottom: scrollHeight - (scrollTop + clientHeight)
|
|
2146
|
+
// });
|
|
2147
|
+
}
|
|
2148
|
+
});
|
|
2149
|
+
});
|
|
2150
|
+
|
|
2151
|
+
scrollObserver.observe(contentScrollViewport, {
|
|
2152
|
+
childList: true,
|
|
2153
|
+
subtree: true
|
|
2154
|
+
});
|
|
2155
|
+
|
|
2156
|
+
return () => {
|
|
2157
|
+
scrollObserver.disconnect();
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
});
|
|
2161
|
+
|
|
2162
|
+
// Add after other function declarations but before the script closing tag
|
|
2163
|
+
/**
|
|
2164
|
+
* Determines the active tab based on available schemas and default tab
|
|
2165
|
+
* @returns {'design' | 'content'} The active tab value
|
|
2166
|
+
*/
|
|
2167
|
+
function getActiveTab(): 'design' | 'content' {
|
|
2168
|
+
// If no schemas are available, return design as fallback
|
|
2169
|
+
if (!designSchema && !contentSchema) return 'design';
|
|
2170
|
+
// If design schema is missing but content schema exists, force content tab regardless of defaultTab
|
|
2171
|
+
if (!designSchema && contentSchema) return 'content';
|
|
2172
|
+
// If content schema is missing but design schema exists, force design tab
|
|
2173
|
+
if (designSchema && !contentSchema) return 'design';
|
|
2174
|
+
// If both schemas exist, use the defaultTab
|
|
2175
|
+
return defaultTab;
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
// Add reactive statement to update defaultTab when schemas change
|
|
2179
|
+
$: {
|
|
2180
|
+
const newTab = getActiveTab();
|
|
2181
|
+
if (defaultTab !== newTab) {
|
|
2182
|
+
defaultTab = newTab;
|
|
2183
|
+
console.log('📑 [PropsEditor] Updated defaultTab:', { from: defaultTab, to: newTab, hasDesignSchema: !!designSchema, hasContentSchema: !!contentSchema });
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
/**
|
|
2188
|
+
* COMPONENT PROPERTIES
|
|
2189
|
+
*/
|
|
2190
|
+
// ─── BUILT-IN DRAG ───────────────────────────────────────────
|
|
2191
|
+
let rootElement: HTMLElement;
|
|
2192
|
+
let dragState = { active: false, startX: 0, startY: 0, initialLeft: 0, initialTop: 0 };
|
|
2193
|
+
|
|
2194
|
+
function handleDragStart(e: MouseEvent) {
|
|
2195
|
+
if (!enableDrag || !rootElement) return;
|
|
2196
|
+
e.preventDefault();
|
|
2197
|
+
|
|
2198
|
+
dragState = {
|
|
2199
|
+
active: true,
|
|
2200
|
+
startX: e.clientX,
|
|
2201
|
+
startY: e.clientY,
|
|
2202
|
+
initialLeft: parseFloat(rootElement.style.left) || 0,
|
|
2203
|
+
initialTop: parseFloat(rootElement.style.top) || 0
|
|
2204
|
+
};
|
|
2205
|
+
rootElement.style.zIndex = '50';
|
|
2206
|
+
document.addEventListener('mousemove', handleDragMove);
|
|
2207
|
+
document.addEventListener('mouseup', handleDragEnd);
|
|
2208
|
+
document.body.style.cursor = 'move';
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
function handleDragMove(e: MouseEvent) {
|
|
2212
|
+
if (!dragState.active || !rootElement) return;
|
|
2213
|
+
const left = dragState.initialLeft + (e.clientX - dragState.startX);
|
|
2214
|
+
const top = dragState.initialTop + (e.clientY - dragState.startY);
|
|
2215
|
+
rootElement.style.left = `${left}px`;
|
|
2216
|
+
rootElement.style.top = `${top}px`;
|
|
2217
|
+
dispatch('move', { left, top });
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
function handleDragEnd() {
|
|
2221
|
+
if (!dragState.active) return;
|
|
2222
|
+
dragState.active = false;
|
|
2223
|
+
document.removeEventListener('mousemove', handleDragMove);
|
|
2224
|
+
document.removeEventListener('mouseup', handleDragEnd);
|
|
2225
|
+
document.body.style.cursor = '';
|
|
2226
|
+
dispatch('moveEnd', {
|
|
2227
|
+
left: parseFloat(rootElement.style.left) || 0,
|
|
2228
|
+
top: parseFloat(rootElement.style.top) || 0
|
|
2229
|
+
});
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
/** Public: update a design state value externally (used by EmbedEditor for dimension sync) */
|
|
2233
|
+
export function updateDesignValue(propertyId: string, value: any) {
|
|
2234
|
+
designState = { ...designState, [propertyId]: value };
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
/** Public: set absolute position programmatically (used by parent for layout) */
|
|
2238
|
+
export function setPosition(left: number, top: number) {
|
|
2239
|
+
if (!rootElement) return;
|
|
2240
|
+
rootElement.style.left = `${left}px`;
|
|
2241
|
+
rootElement.style.top = `${top}px`;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
/** Public: get current position */
|
|
2245
|
+
export function getPosition() {
|
|
2246
|
+
if (!rootElement) return { left: 0, top: 0 };
|
|
2247
|
+
return {
|
|
2248
|
+
left: parseFloat(rootElement.style.left) || 0,
|
|
2249
|
+
top: parseFloat(rootElement.style.top) || 0
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
// Add a reactive log after render to show the current array order and titles
|
|
2254
|
+
$: if (contentSchema && Array.isArray(contentSchema?.contentItems)) {
|
|
2255
|
+
console.log('[Render] Current contentItems order:', contentSchema.contentItems.map((i: ContentItem) => ({ id: i.id, title: i.title })));
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
/** Safe accessor — always returns an array even when contentSchema.contentItems is missing or not an array */
|
|
2259
|
+
function getContentItems(): ContentItem[] {
|
|
2260
|
+
return Array.isArray(contentSchema?.contentItems) ? contentSchema.contentItems : [];
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
function hasDuplicateIds(items: ContentItem[]): boolean {
|
|
2264
|
+
const ids = items.map(item => item.id);
|
|
2265
|
+
return ids.length !== new Set(ids).size;
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
// Helper: log DOM visual order for debugging
|
|
2269
|
+
function logOrder(label: string) {
|
|
2270
|
+
const titles = Array.from(document.querySelectorAll('.content-item .content-title')).map(el => el.textContent?.trim());
|
|
2271
|
+
console.log(`[Order-check] ${label}:`, titles);
|
|
2272
|
+
const transforms = Array.from(document.querySelectorAll('.content-item')).map(el => ({ id: (el as HTMLElement).dataset.id, transform: (el as HTMLElement).style.transform }));
|
|
2273
|
+
console.table(transforms);
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
|
|
2277
|
+
|
|
2278
|
+
|
|
2279
|
+
</script>
|
|
2280
|
+
|
|
2281
|
+
<div
|
|
2282
|
+
bind:this={rootElement}
|
|
2283
|
+
class="{className ? className + ' ' : ''}props-editor-container relative widgetic-colors widgetic-radius overflow-hidden"
|
|
2284
|
+
style="
|
|
2285
|
+
width: {width};
|
|
2286
|
+
height: {height};
|
|
2287
|
+
border: 1px solid hsl(var(--grey2));
|
|
2288
|
+
{style ? ' ' + style : ''}
|
|
2289
|
+
">
|
|
2290
|
+
<Tabs.Root
|
|
2291
|
+
value={getActiveTab()}
|
|
2292
|
+
class="tabs-container relative w-full h-full flex flex-col z-10 bg-white rounded-large"
|
|
2293
|
+
onValueChange={handleTabChange}
|
|
2294
|
+
>
|
|
2295
|
+
|
|
2296
|
+
{#if !designSchema && !contentSchema}
|
|
2297
|
+
<div class="flex flex-col items-center justify-center h-full">
|
|
2298
|
+
<div class="text-error text-lg font-medium">No schema configuration available</div>
|
|
2299
|
+
<div class="text-grey5 subtext mt-1">Error code: CONFIG_NOT_FOUND</div>
|
|
2300
|
+
</div>
|
|
2301
|
+
{:else}
|
|
2302
|
+
<!-- Drag Handle - Show outside header when no header exists -->
|
|
2303
|
+
{#if showDragHandle && (!designSchema || !contentSchema)}
|
|
2304
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
2305
|
+
<div class="absolute top-0 left-0 w-full flex justify-center z-[999]">
|
|
2306
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
2307
|
+
<div
|
|
2308
|
+
class="flex items-center justify-center h-6 cursor-move transition-all duration-200 widgetic-colors opacity-0 hover:opacity-100 drag-handle-container"
|
|
2309
|
+
onmousedown={handleDragStart}
|
|
2310
|
+
>
|
|
2311
|
+
<DragHandle class="w-[66px] h-[24px] text-grey2 hover:text-grey3 transition-colors" />
|
|
2312
|
+
</div>
|
|
2313
|
+
</div>
|
|
2314
|
+
{/if}
|
|
2315
|
+
|
|
2316
|
+
<!-- Tabs header with buttons and separator - only show if both schemas exist -->
|
|
2317
|
+
{#if designSchema && contentSchema}
|
|
2318
|
+
<div
|
|
2319
|
+
class="tabs-header h-[96px] flex items-start pt-6 px-6 pb-4 rounded-t-[var(--radius-large)] header-hover-area relative"
|
|
2320
|
+
>
|
|
2321
|
+
<!-- Drag Handle - Show inside header when header exists -->
|
|
2322
|
+
{#if showDragHandle}
|
|
2323
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
2324
|
+
<div class="absolute top-0 left-0 w-full flex justify-center z-[999]">
|
|
2325
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
2326
|
+
<div
|
|
2327
|
+
class="flex items-center justify-center h-6 cursor-move transition-all duration-200 widgetic-colors opacity-0 hover:opacity-100 drag-handle-container"
|
|
2328
|
+
onmousedown={handleDragStart}
|
|
2329
|
+
>
|
|
2330
|
+
<DragHandle class="w-[66px] h-[24px] text-grey2 hover:text-grey3 transition-colors" />
|
|
2331
|
+
</div>
|
|
2332
|
+
</div>
|
|
2333
|
+
{/if}
|
|
2334
|
+
|
|
2335
|
+
<Tabs.List
|
|
2336
|
+
class="tabs-list w-full h-[56px] rounded-large bg-grey2 p-1"
|
|
2337
|
+
>
|
|
2338
|
+
<Tabs.Trigger
|
|
2339
|
+
value="design"
|
|
2340
|
+
class="body flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
|
|
2341
|
+
>
|
|
2342
|
+
{designTabTitle}
|
|
2343
|
+
</Tabs.Trigger>
|
|
2344
|
+
{#if contentSchema}
|
|
2345
|
+
<Tabs.Trigger
|
|
2346
|
+
onclick={() => {
|
|
2347
|
+
initializeDraggable();
|
|
2348
|
+
}}
|
|
2349
|
+
value="content"
|
|
2350
|
+
class="body flex-1 h-full rounded-[11px] transition-colors duration-250 text-grey5 hover:text-grey7 hover:underline data-[state=active]:bg-white data-[state=active]:text-grey7 data-[state=active]:hover:text-grey7"
|
|
2351
|
+
>
|
|
2352
|
+
{contentTabTitle}
|
|
2353
|
+
</Tabs.Trigger>
|
|
2354
|
+
{/if}
|
|
2355
|
+
</Tabs.List>
|
|
2356
|
+
</div>
|
|
2357
|
+
|
|
2358
|
+
<Separator
|
|
2359
|
+
class="bg-grey2"
|
|
2360
|
+
/>
|
|
2361
|
+
{/if}
|
|
2362
|
+
|
|
2363
|
+
<!-- Design Tab Content -->
|
|
2364
|
+
<div class="tab-design-container flex-grow overflow-y-auto scroll-area widgetic-colors hidden"
|
|
2365
|
+
class:hidden={currentTab !== 'design'}
|
|
2366
|
+
style="max-height: {!designSchema || !contentSchema ? '100%' : `calc(100% - ${HEADER_HEIGHT}px)`}"
|
|
2367
|
+
data-scrollable={$designScrollState.shouldShowScroll}
|
|
2368
|
+
onscroll={(event: Event) => { event.preventDefault(); handleScroll(event, 'design'); }}>
|
|
2369
|
+
<ScrollArea
|
|
2370
|
+
class="h-full"
|
|
2371
|
+
type="hover"
|
|
2372
|
+
scrollHideDelay={0}
|
|
2373
|
+
scrollbarYClasses="flex touch-none select-none transition-colors w-[24px] px-2 bg-white {!designSchema || !contentSchema ? 'pt-6' : 'pt-4'} pb-6 [&>div]:w-[8px] [&>div]:bg-grey1 [&>div]:rounded-pill [&>div]:cursor-pointer [&>div]:transition-colors [&>div]:duration-250 [&>div]:hover:bg-grey2"
|
|
2374
|
+
>
|
|
2375
|
+
<div class="scroll-viewport" data-radix-scroll-area-viewport bind:this={designScrollViewport}>
|
|
2376
|
+
<div class="px-6 {!designSchema || !contentSchema ? 'py-6' : 'pt-4 pb-6'}" bind:this={designContentWrapper}>
|
|
2377
|
+
<Tabs.Content
|
|
2378
|
+
value="design"
|
|
2379
|
+
class="design-tab-content !mt-0 tabs-content-container w-full outline-none {isFirstLoad ? 'initial-load' : !isInitialTabLoad ? 'animate-slide-left' : ''}"
|
|
2380
|
+
data-tab="design"
|
|
2381
|
+
style="animation-duration: 300ms; transform: none;"
|
|
2382
|
+
>
|
|
2383
|
+
{#if designSchema && designSchema[0]?.components}
|
|
2384
|
+
<Accordion.Root
|
|
2385
|
+
value={currentTab === 'design' ? designOpenAccordion : contentOpenAccordion}
|
|
2386
|
+
onValueChange={handleAccordionChange}
|
|
2387
|
+
type="multiple"
|
|
2388
|
+
class="design-tab-accordion tab-accordion-root w-full"
|
|
2389
|
+
style="position: relative; z-index: 1;"
|
|
2390
|
+
>
|
|
2391
|
+
{#each designSchema[0].components as component}
|
|
2392
|
+
{@const visibleGroups = component.propertyGroups.filter((group: PropertyGroup) =>
|
|
2393
|
+
shouldShowPropertyGroup(group, component.propertyGroups[0].properties, 'design')
|
|
2394
|
+
)}
|
|
2395
|
+
{#each visibleGroups as propertyGroup, groupIndex}
|
|
2396
|
+
<Accordion.Item
|
|
2397
|
+
value={propertyGroup.name}
|
|
2398
|
+
class="accordion-item w-full"
|
|
2399
|
+
data-accordion-id={propertyGroup.name}
|
|
2400
|
+
data-position={getAccordionItemPosition(component, propertyGroup, groupIndex)}
|
|
2401
|
+
>
|
|
2402
|
+
<Accordion.Trigger class="accordion-trigger flex items-center justify-between w-full px-4 py-4">
|
|
2403
|
+
<span class="body-medium" style="color: hsl(var(--grey7));">{propertyGroup.name}</span>
|
|
2404
|
+
</Accordion.Trigger>
|
|
2405
|
+
|
|
2406
|
+
<Accordion.Content class="accordion-content w-full p-0 px-4">
|
|
2407
|
+
<div class="properties-list flex flex-col" style="gap: 8px;">
|
|
2408
|
+
{#each propertyGroup.properties as property, propertyIndex (propertyGroup.name + '_' + (property.id ?? property.name ?? propertyIndex))}
|
|
2409
|
+
{@const result = renderInputController('design', property, propertyIndex, groupIndex === 0)}
|
|
2410
|
+
{@const InputComponent = result.component}
|
|
2411
|
+
{@const props = result.props}
|
|
2412
|
+
{@const header = result.header}
|
|
2413
|
+
{@const isVisible = shouldShowProperty(property, propertyGroup.properties, 'design')}
|
|
2414
|
+
{@const stateKey = property.id || property.name}
|
|
2415
|
+
{@const reactiveValue = designState[stateKey]}
|
|
2416
|
+
|
|
2417
|
+
{#if isVisible}
|
|
2418
|
+
<div
|
|
2419
|
+
class="ictrl input-controller flex items-center justify-between w-full p-0"
|
|
2420
|
+
data-state={$openDropdowns?.has(props.controllerId) ? 'open' : 'closed'}
|
|
2421
|
+
class:double-height={property.inputController.type.toLowerCase() === 'font'}
|
|
2422
|
+
class:position-height={property.inputController.type.toLowerCase() === 'position'}
|
|
2423
|
+
class:fixed-position-height={property.inputController.type.toLowerCase() === 'fixedposition'}
|
|
2424
|
+
class:text-area-preview-height={property.inputController.type.toLowerCase() === 'textarea' || property.inputController.type.toLowerCase() === 'textArea'}
|
|
2425
|
+
class:position-controller={property.inputController.type.toLowerCase() === 'position'}
|
|
2426
|
+
class:position-dragging={property.inputController.type.toLowerCase() === 'position' && isDragging}
|
|
2427
|
+
>
|
|
2428
|
+
<div class="ic-header-ct flex-1 min-w-0">
|
|
2429
|
+
<InputControllerHeader
|
|
2430
|
+
label={header.label}
|
|
2431
|
+
helpText={header.helpText}
|
|
2432
|
+
/>
|
|
2433
|
+
</div>
|
|
2434
|
+
|
|
2435
|
+
<div class="ic-input-ct flex-shrink-0 ml-auto" style="width: 185px;">
|
|
2436
|
+
<svelte:component
|
|
2437
|
+
this={InputComponent}
|
|
2438
|
+
{...props}
|
|
2439
|
+
value={reactiveValue !== undefined ? reactiveValue : props.value}
|
|
2440
|
+
shouldFocus={activeControllerId === props.id}
|
|
2441
|
+
/>
|
|
2442
|
+
</div>
|
|
2443
|
+
</div>
|
|
2444
|
+
{/if}
|
|
2445
|
+
{/each}
|
|
2446
|
+
</div>
|
|
2447
|
+
</Accordion.Content>
|
|
2448
|
+
</Accordion.Item>
|
|
2449
|
+
{/each}
|
|
2450
|
+
{/each}
|
|
2451
|
+
</Accordion.Root>
|
|
2452
|
+
{:else}
|
|
2453
|
+
<div class="p-4 text-gray-500">
|
|
2454
|
+
{#if !designSchema}
|
|
2455
|
+
No design schema available
|
|
2456
|
+
{:else if !designSchema[0]}
|
|
2457
|
+
No schema data available
|
|
2458
|
+
{:else if !designSchema[0].components}
|
|
2459
|
+
No components defined in schema
|
|
2460
|
+
{:else}
|
|
2461
|
+
No properties available
|
|
2462
|
+
{/if}
|
|
2463
|
+
</div>
|
|
2464
|
+
{/if}
|
|
2465
|
+
</Tabs.Content>
|
|
2466
|
+
</div>
|
|
2467
|
+
</div>
|
|
2468
|
+
</ScrollArea>
|
|
2469
|
+
</div>
|
|
2470
|
+
|
|
2471
|
+
<!-- Content Tab Content -->
|
|
2472
|
+
{#if contentSchema}
|
|
2473
|
+
<div class="tab-content-container flex-grow overflow-y-auto scroll-area widgetic-colors hidden"
|
|
2474
|
+
class:hidden={currentTab !== 'content'}
|
|
2475
|
+
style="max-height: {!contentSchema || !designSchema ? '100%' : `calc(100% - ${HEADER_HEIGHT}px)`}"
|
|
2476
|
+
data-scrollable={$contentScrollState.shouldShowScroll}
|
|
2477
|
+
onscroll={(event: Event) => { event.preventDefault(); handleScroll(event, 'content'); }}>
|
|
2478
|
+
<ScrollArea
|
|
2479
|
+
class="h-full"
|
|
2480
|
+
type="hover"
|
|
2481
|
+
scrollHideDelay={0}
|
|
2482
|
+
scrollbarYClasses="flex touch-none select-none transition-colors w-[24px] px-2 bg-white {!designSchema || !contentSchema ? 'pt-6' : 'pt-4'} pb-6 [&>div]:w-[8px] [&>div]:bg-grey1 [&>div]:rounded-pill [&>div]:cursor-pointer [&>div]:transition-colors [&>div]:duration-250 [&>div]:hover:bg-grey2"
|
|
2483
|
+
>
|
|
2484
|
+
<div class="scroll-viewport" data-radix-scroll-area-viewport bind:this={contentScrollViewport}>
|
|
2485
|
+
<div class="px-6 {!designSchema || !contentSchema ? 'pt-6 pb-6' : 'pt-4 pb-6'}" bind:this={contentWrapper}>
|
|
2486
|
+
<Tabs.Content
|
|
2487
|
+
value="content"
|
|
2488
|
+
class="content-tab-content !mt-0 tabs-content-container w-full {isFirstLoad ? 'initial-load' : !isInitialTabLoad ? 'animate-slide-right' : ''}"
|
|
2489
|
+
data-tab="content"
|
|
2490
|
+
style="transform: none; position: relative; z-index: 1;"
|
|
2491
|
+
>
|
|
2492
|
+
<div class="tab-content-root w-full" style="position: relative; z-index: 1;">
|
|
2493
|
+
<!-- Generic Content Collapsible Card -->
|
|
2494
|
+
{#if shouldShowGenericContentSection(contentSchema)}
|
|
2495
|
+
<Card.Root class="generic-content-card relative mt-0" style="position: relative; z-index: 1;">
|
|
2496
|
+
<Collapsible.Root
|
|
2497
|
+
open={isGenericContentOpen}
|
|
2498
|
+
onOpenChange={() => isGenericContentOpen = !isGenericContentOpen}
|
|
2499
|
+
>
|
|
2500
|
+
<Collapsible.Trigger class="generic-content-trigger w-full">
|
|
2501
|
+
<div class="flex items-center h-[52px]" style="padding-left: {CONTENT_INNER_PADDING}px; padding-right: {CONTENT_INNER_PADDING}px;">
|
|
2502
|
+
<h3 class="body-medium text-grey7 flex-1 content-title">{contentSchema?.genericContent?.title || 'Content'}</h3>
|
|
2503
|
+
<ChevronDown class="h-6 w-6 text-grey5 transition-transform chevron" />
|
|
2504
|
+
</div>
|
|
2505
|
+
</Collapsible.Trigger>
|
|
2506
|
+
|
|
2507
|
+
{#if isGenericContentOpen}
|
|
2508
|
+
<Separator class="separator border-grey2" />
|
|
2509
|
+
{/if}
|
|
2510
|
+
|
|
2511
|
+
<Collapsible.Content>
|
|
2512
|
+
<Card.Content class="p-0 px-4">
|
|
2513
|
+
<div class="pt-[14px] pb-[16px]">
|
|
2514
|
+
<div class="properties-list flex flex-col" style="gap: 8px;">
|
|
2515
|
+
{#each contentSchema?.genericContent?.properties || [] as property, propertyIndex}
|
|
2516
|
+
{@const result = renderInputController(
|
|
2517
|
+
'content',
|
|
2518
|
+
property,
|
|
2519
|
+
propertyIndex,
|
|
2520
|
+
true
|
|
2521
|
+
)}
|
|
2522
|
+
{@const InputComponent = result.component}
|
|
2523
|
+
{@const props = result.props}
|
|
2524
|
+
{@const header = result.header}
|
|
2525
|
+
{@const isVisible = shouldShowProperty(property, contentSchema?.genericContent?.properties || [], 'content')}
|
|
2526
|
+
|
|
2527
|
+
{#if isVisible}
|
|
2528
|
+
<div class="ictrl input-controller flex items-center justify-between w-full p-0 browser-input-wrapper"
|
|
2529
|
+
class:double-height={property.inputController.type.toLowerCase() === 'font'}
|
|
2530
|
+
>
|
|
2531
|
+
<!-- Left side - Label -->
|
|
2532
|
+
{#if InputComponent !== BrowserInputController}
|
|
2533
|
+
<InputControllerHeader
|
|
2534
|
+
label={header.label}
|
|
2535
|
+
helpText={header.helpText}
|
|
2536
|
+
/>
|
|
2537
|
+
{:else}
|
|
2538
|
+
<!-- Special case for BrowserInputController -->
|
|
2539
|
+
<div class="flex-1 min-w-0 pr-4">
|
|
2540
|
+
<InputControllerHeader
|
|
2541
|
+
label={header.label}
|
|
2542
|
+
helpText={header.helpText}
|
|
2543
|
+
/>
|
|
2544
|
+
</div>
|
|
2545
|
+
{/if}
|
|
2546
|
+
|
|
2547
|
+
<!-- Right side - Input Component -->
|
|
2548
|
+
<div class="ic-input-ct flex-shrink-0" style="width: 185px;">
|
|
2549
|
+
<svelte:component
|
|
2550
|
+
this={InputComponent}
|
|
2551
|
+
{...props}
|
|
2552
|
+
{header}
|
|
2553
|
+
shouldFocus={activeControllerId === props.id}
|
|
2554
|
+
/>
|
|
2555
|
+
</div>
|
|
2556
|
+
</div>
|
|
2557
|
+
{/if}
|
|
2558
|
+
{/each}
|
|
2559
|
+
</div>
|
|
2560
|
+
</div>
|
|
2561
|
+
</Card.Content>
|
|
2562
|
+
</Collapsible.Content>
|
|
2563
|
+
</Collapsible.Root>
|
|
2564
|
+
</Card.Root>
|
|
2565
|
+
{/if}
|
|
2566
|
+
|
|
2567
|
+
<!-- Content Items Container -->
|
|
2568
|
+
<div class="content-items-wrapper mt-0" style="position: relative; z-index: 1;">
|
|
2569
|
+
<!-- Sortable Container -->
|
|
2570
|
+
<div class="content-items flex flex-col gap-4" bind:this={contentItemsContainer} style="position: relative; z-index: 1;">
|
|
2571
|
+
{@html (() => {
|
|
2572
|
+
const items = contentSchema?.contentItems;
|
|
2573
|
+
if (!Array.isArray(items)) return '';
|
|
2574
|
+
const ids = items.map((i: ContentItem) => i.id);
|
|
2575
|
+
const hasDupes = ids.length !== new Set(ids).size;
|
|
2576
|
+
if (hasDupes) {
|
|
2577
|
+
console.error('[UI Render] Duplicate IDs in contentItems:', ids);
|
|
2578
|
+
}
|
|
2579
|
+
return ''
|
|
2580
|
+
})()}
|
|
2581
|
+
{#each (Array.isArray(contentSchema?.contentItems) ? contentSchema.contentItems : []) as item, index (item.id)}
|
|
2582
|
+
<!-- Content Item Collapsible Card -->
|
|
2583
|
+
<div class="content-item" data-id={item.id}>
|
|
2584
|
+
<Card.Root class="content-item-card relative">
|
|
2585
|
+
<Collapsible.Root
|
|
2586
|
+
open={isItemOpen(item.id)}
|
|
2587
|
+
onOpenChange={() => toggleItem(item.id)}
|
|
2588
|
+
>
|
|
2589
|
+
<Collapsible.Trigger class="content-item-trigger w-full">
|
|
2590
|
+
<div class="flex items-center h-[52px] cursor-pointer widgetic-colors">
|
|
2591
|
+
<div class="drag-handle flex items-center justify-center w-[56px] cursor-move hover:text-grey5">
|
|
2592
|
+
<IconGripVertical class="w-6 h-6 text-grey2 transition-colors duration-200" />
|
|
2593
|
+
</div>
|
|
2594
|
+
|
|
2595
|
+
<!-- Item Title — user caption/title wins; Slide N / Item 0N follow list position -->
|
|
2596
|
+
<h4 class="body-medium text-grey7 flex-1 content-title">
|
|
2597
|
+
{getDisplayItemTitle(item, index)}
|
|
2598
|
+
</h4>
|
|
2599
|
+
|
|
2600
|
+
<!-- Arrow Icon -->
|
|
2601
|
+
<div class="pr-4">
|
|
2602
|
+
<ChevronDown class="h-6 w-6 text-grey7 transition-transform chevron" />
|
|
2603
|
+
</div>
|
|
2604
|
+
</div>
|
|
2605
|
+
</Collapsible.Trigger>
|
|
2606
|
+
|
|
2607
|
+
{#if isItemOpen(item.id)}
|
|
2608
|
+
<Separator class="separator border-grey2" />
|
|
2609
|
+
{/if}
|
|
2610
|
+
|
|
2611
|
+
<Collapsible.Content>
|
|
2612
|
+
<Card.Content class="p-0 px-4">
|
|
2613
|
+
<div class="pt-[14px] pb-[16px]">
|
|
2614
|
+
<div class="properties-list flex flex-col" style="gap: 8px;">
|
|
2615
|
+
{#each item.properties as property, propertyIndex}
|
|
2616
|
+
{@const result = renderInputController(
|
|
2617
|
+
'content',
|
|
2618
|
+
property,
|
|
2619
|
+
propertyIndex,
|
|
2620
|
+
false,
|
|
2621
|
+
item.id
|
|
2622
|
+
)}
|
|
2623
|
+
{@const InputComponent = result.component}
|
|
2624
|
+
{@const props = result.props}
|
|
2625
|
+
{@const header = result.header}
|
|
2626
|
+
{@const isVisible = shouldShowProperty(property, item.properties, 'content')}
|
|
2627
|
+
|
|
2628
|
+
{#if isVisible}
|
|
2629
|
+
<div class="ictrl input-controller flex items-center justify-between w-full p-0 browser-input-wrapper"
|
|
2630
|
+
class:double-height={property.inputController.type.toLowerCase() === 'font'}
|
|
2631
|
+
>
|
|
2632
|
+
<!-- Left side - Label -->
|
|
2633
|
+
{#if InputComponent !== BrowserInputController}
|
|
2634
|
+
<InputControllerHeader
|
|
2635
|
+
label={header.label}
|
|
2636
|
+
helpText={header.helpText}
|
|
2637
|
+
/>
|
|
2638
|
+
{:else}
|
|
2639
|
+
<!-- Special case for BrowserInputController -->
|
|
2640
|
+
<div class="flex-1 min-w-0 pr-4">
|
|
2641
|
+
<InputControllerHeader
|
|
2642
|
+
label={header.label}
|
|
2643
|
+
helpText={header.helpText}
|
|
2644
|
+
/>
|
|
2645
|
+
</div>
|
|
2646
|
+
{/if}
|
|
2647
|
+
|
|
2648
|
+
<!-- Right side - Input Component -->
|
|
2649
|
+
<div class="ic-input-ct flex-shrink-0" style="width: 185px;">
|
|
2650
|
+
<svelte:component
|
|
2651
|
+
this={InputComponent}
|
|
2652
|
+
{...props}
|
|
2653
|
+
{header}
|
|
2654
|
+
shouldFocus={activeControllerId === props.id}
|
|
2655
|
+
/>
|
|
2656
|
+
</div>
|
|
2657
|
+
</div>
|
|
2658
|
+
{/if}
|
|
2659
|
+
{/each}
|
|
2660
|
+
</div>
|
|
2661
|
+
|
|
2662
|
+
<!-- Delete Item Button -->
|
|
2663
|
+
<div class="mt-[16px] flex flex-col gap-2 relative z-10">
|
|
2664
|
+
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
|
|
2665
|
+
<button
|
|
2666
|
+
type="button"
|
|
2667
|
+
class="widgetic-colors h-[34px] w-full flex items-center justify-center relative z-10 group"
|
|
2668
|
+
style="
|
|
2669
|
+
background-color: hsl(var(--error)) !important;
|
|
2670
|
+
border-radius: var(--radius-small) !important;
|
|
2671
|
+
color: white !important;
|
|
2672
|
+
transition: background-color 0.2s ease !important;
|
|
2673
|
+
line-height: 1 !important;
|
|
2674
|
+
"
|
|
2675
|
+
onclick={(e: MouseEvent) => {
|
|
2676
|
+
e.stopPropagation();
|
|
2677
|
+
handleDeleteItem(item.id);
|
|
2678
|
+
}}
|
|
2679
|
+
onmouseover={(e) => {
|
|
2680
|
+
e.currentTarget.style.backgroundColor = 'color-mix(in srgb, hsl(var(--error)) 90%, black) !important';
|
|
2681
|
+
}}
|
|
2682
|
+
onmouseout={(e) => {
|
|
2683
|
+
e.currentTarget.style.backgroundColor = 'hsl(var(--error)) !important';
|
|
2684
|
+
}}
|
|
2685
|
+
>
|
|
2686
|
+
<span class="subtext text-white transition-colors duration-200">
|
|
2687
|
+
Delete item
|
|
2688
|
+
</span>
|
|
2689
|
+
</button>
|
|
2690
|
+
</div>
|
|
2691
|
+
</div>
|
|
2692
|
+
</Card.Content>
|
|
2693
|
+
</Collapsible.Content>
|
|
2694
|
+
</Collapsible.Root>
|
|
2695
|
+
</Card.Root>
|
|
2696
|
+
</div>
|
|
2697
|
+
{/each}
|
|
2698
|
+
</div>
|
|
2699
|
+
|
|
2700
|
+
{#if canAddContentItem()}
|
|
2701
|
+
<div class="content-add-item-ct mt-4">
|
|
2702
|
+
<Button
|
|
2703
|
+
variant="ghost"
|
|
2704
|
+
class="group w-full h-[52px] bg-grey1 hover:bg-grey2 active:scale-[0.98] border-2 border-grey2 rounded-medium"
|
|
2705
|
+
style="transition: background-color 0.2s ease, transform 0.15s ease !important;"
|
|
2706
|
+
onclick={handleAddNewItem}
|
|
2707
|
+
>
|
|
2708
|
+
<div class="flex items-center justify-center gap-2">
|
|
2709
|
+
<AddNew class="w-5 h-5 text-grey5" />
|
|
2710
|
+
<span class="body-medium text-grey7 group-hover:underline">Add new</span>
|
|
2711
|
+
</div>
|
|
2712
|
+
</Button>
|
|
2713
|
+
</div>
|
|
2714
|
+
{/if}
|
|
2715
|
+
</div>
|
|
2716
|
+
</div>
|
|
2717
|
+
</Tabs.Content>
|
|
2718
|
+
</div>
|
|
2719
|
+
</div>
|
|
2720
|
+
</ScrollArea>
|
|
2721
|
+
</div>
|
|
2722
|
+
{/if}
|
|
2723
|
+
{/if}
|
|
2724
|
+
|
|
2725
|
+
<div
|
|
2726
|
+
bind:this={fileBrowserPortalHost}
|
|
2727
|
+
id="props-editor-file-browser-portal"
|
|
2728
|
+
class="props-editor-file-browser-portal pointer-events-none absolute inset-0 z-[999998]"
|
|
2729
|
+
></div>
|
|
2730
|
+
</Tabs.Root>
|
|
2731
|
+
</div>
|
|
2732
|
+
|
|
2733
|
+
<style>
|
|
2734
|
+
.props-editor-container {
|
|
2735
|
+
--has-both-schemas: 0;
|
|
2736
|
+
border-radius: var(--radius-large);
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
/* ── Drag handle visibility on hover ── */
|
|
2740
|
+
:global(.header-hover-area:hover .drag-handle-container) {
|
|
2741
|
+
opacity: 1 !important;
|
|
2742
|
+
}
|
|
2743
|
+
.props-editor-container:not(:has(.header-hover-area)) {
|
|
2744
|
+
position: relative;
|
|
2745
|
+
}
|
|
2746
|
+
.props-editor-container:not(:has(.header-hover-area)):hover :global(.drag-handle-container) {
|
|
2747
|
+
opacity: 1 !important;
|
|
2748
|
+
}
|
|
2749
|
+
:global(.drag-handle-container) {
|
|
2750
|
+
opacity: 0;
|
|
2751
|
+
transition: opacity 0.3s ease;
|
|
2752
|
+
}
|
|
2753
|
+
:global(.widgetic-colors .drag-handle svg) {
|
|
2754
|
+
color: hsl(var(--grey2)) !important;
|
|
2755
|
+
transition: color 0.2s ease !important;
|
|
2756
|
+
}
|
|
2757
|
+
:global(.widgetic-colors .drag-handle:hover svg) {
|
|
2758
|
+
color: hsl(var(--grey3)) !important;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
/* ── Button hover states within widgetic-colors ── */
|
|
2762
|
+
:global(.widgetic-colors button.bg-white),
|
|
2763
|
+
:global(.widgetic-colors button.bg-white:hover) {
|
|
2764
|
+
background-color: white !important;
|
|
2765
|
+
}
|
|
2766
|
+
:global(.widgetic-colors button span) {
|
|
2767
|
+
line-height: 1;
|
|
2768
|
+
}
|
|
2769
|
+
:global(.widgetic-colors button svg) {
|
|
2770
|
+
color: hsl(var(--grey5));
|
|
2771
|
+
}
|
|
2772
|
+
:global(.widgetic-colors button:hover svg) {
|
|
2773
|
+
color: hsl(var(--grey7));
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
/* ── Design tab content spacing ── */
|
|
2777
|
+
:global(.design-tab-content[data-state="active"]) {
|
|
2778
|
+
display: block;
|
|
2779
|
+
margin-top: var(--has-both-schemas, 1rem);
|
|
2780
|
+
}
|
|
2781
|
+
.props-editor-container:has(.tabs-header) {
|
|
2782
|
+
--has-both-schemas: 1rem;
|
|
2783
|
+
}
|
|
2784
|
+
:global(.content-items-wrapper) {
|
|
2785
|
+
padding-top: 1rem;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
/* Style all chevrons across the editor */
|
|
2789
|
+
:global([data-radix-accordion-trigger] > svg),
|
|
2790
|
+
:global(.accordion-trigger > svg),
|
|
2791
|
+
:global(.generic-content-card button > svg),
|
|
2792
|
+
:global(.content-item-card button > svg) {
|
|
2793
|
+
width: 24px !important;
|
|
2794
|
+
height: 24px !important;
|
|
2795
|
+
flex-shrink: 0 !important;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
/* Style accordion items */
|
|
2799
|
+
:global(.accordion-item) {
|
|
2800
|
+
border: 2px solid hsl(var(--grey2));
|
|
2801
|
+
border-radius: 0;
|
|
2802
|
+
overflow: hidden;
|
|
2803
|
+
margin: 0;
|
|
2804
|
+
margin-bottom: -2px;
|
|
2805
|
+
background-color: white;
|
|
2806
|
+
position: relative;
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
/* Remove default Radix borders and add our own */
|
|
2810
|
+
:global(.accordion-trigger),
|
|
2811
|
+
:global([data-radix-accordion-trigger]) {
|
|
2812
|
+
border: none !important;
|
|
2813
|
+
outline: none !important;
|
|
2814
|
+
position: relative;
|
|
2815
|
+
padding: 0 16px !important;
|
|
2816
|
+
height: 52px !important;
|
|
2817
|
+
box-sizing: border-box;
|
|
2818
|
+
display: flex !important;
|
|
2819
|
+
align-items: center !important;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
/* Add our own border using a pseudo-element */
|
|
2823
|
+
:global(.accordion-trigger)::after {
|
|
2824
|
+
content: '';
|
|
2825
|
+
position: absolute;
|
|
2826
|
+
bottom: -2px;
|
|
2827
|
+
left: 0;
|
|
2828
|
+
right: 0;
|
|
2829
|
+
height: 2px;
|
|
2830
|
+
background-color: hsl(var(--grey2));
|
|
2831
|
+
opacity: 1;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
/* Only hide the border after the content has retracted */
|
|
2835
|
+
:global([data-state="closed"] .accordion-trigger)::after {
|
|
2836
|
+
transition: opacity 0ms linear 200ms;
|
|
2837
|
+
opacity: 0;
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
/* Show border immediately when opening */
|
|
2841
|
+
:global([data-state="open"] .accordion-trigger)::after {
|
|
2842
|
+
transition: none;
|
|
2843
|
+
opacity: 1;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
/* Adjust the trigger content layout */
|
|
2847
|
+
:global(.accordion-trigger) {
|
|
2848
|
+
display: flex !important;
|
|
2849
|
+
align-items: center !important;
|
|
2850
|
+
justify-content: space-between !important;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
/* First item - rounded at top only */
|
|
2854
|
+
:global(.accordion-item[data-position="first"]) {
|
|
2855
|
+
border-top-left-radius: var(--radius-small);
|
|
2856
|
+
border-top-right-radius: var(--radius-small);
|
|
2857
|
+
border-bottom-left-radius: 0;
|
|
2858
|
+
border-bottom-right-radius: 0;
|
|
2859
|
+
margin-top: 0; /* Explicitly remove top margin */
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
/* Last item - rounded at bottom only */
|
|
2863
|
+
:global(.accordion-item[data-position="last"]) {
|
|
2864
|
+
margin-bottom: 0;
|
|
2865
|
+
border-top-left-radius: 0;
|
|
2866
|
+
border-top-right-radius: 0;
|
|
2867
|
+
border-bottom-left-radius: var(--radius-small);
|
|
2868
|
+
border-bottom-right-radius: var(--radius-small);
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
/* Middle items - no rounded corners */
|
|
2872
|
+
:global(.accordion-item[data-position="middle"]) {
|
|
2873
|
+
border-radius: 0;
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
:global(.content-items) {
|
|
2877
|
+
position: relative;
|
|
2878
|
+
transition: min-height 0.2s ease-out;
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
:global(.drag-handle) {
|
|
2882
|
+
opacity: 1 !important;
|
|
2883
|
+
display: flex !important;
|
|
2884
|
+
align-items: center !important;
|
|
2885
|
+
justify-content: center !important;
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2888
|
+
:global(.draggable-mirror) {
|
|
2889
|
+
opacity: 1 !important;
|
|
2890
|
+
transform-origin: 50% 50%;
|
|
2891
|
+
z-index: 100;
|
|
2892
|
+
pointer-events: none;
|
|
2893
|
+
background-color: white !important;
|
|
2894
|
+
border: 2px solid hsl(var(--grey2)) !important;
|
|
2895
|
+
border-radius: var(--radius-small) !important;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
:global(.draggable-source--is-dragging) {
|
|
2899
|
+
opacity: 0.3 !important;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
:global(html.draggable--is-dragging) {
|
|
2903
|
+
cursor: move !important;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
:global([data-scrollable="false"] .scrollbar) {
|
|
2907
|
+
display: none;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
:global(.content-item .drag-handle) {
|
|
2911
|
+
transition: opacity 0.2s ease;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
:global(.content-item:hover .drag-handle span) {
|
|
2915
|
+
opacity: 0;
|
|
2916
|
+
}
|
|
2917
|
+
|
|
2918
|
+
:global(.content-item:hover .drag-handle div) {
|
|
2919
|
+
display: flex;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
:global(.content-item .number) {
|
|
2923
|
+
transition: opacity 0.2s ease;
|
|
2924
|
+
opacity: 1; /* Explicitly set opacity */
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
:global(.content-item:hover .number) {
|
|
2928
|
+
opacity: 0;
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
:global(.content-item .drag-icon) {
|
|
2932
|
+
transition: opacity 0.2s ease;
|
|
2933
|
+
opacity: 0;
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2936
|
+
:global(.content-item:hover .drag-icon) {
|
|
2937
|
+
display: flex !important;
|
|
2938
|
+
opacity: 1;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
/* Debug styles */
|
|
2942
|
+
:global(.drag-handle) {
|
|
2943
|
+
border: none;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
:global(.content-item:hover .drag-handle) {
|
|
2947
|
+
border-color: #e5e7eb;
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
/* Replace with new simplified drag handle styles */
|
|
2951
|
+
:global(.drag-handle) {
|
|
2952
|
+
transition: color 0.2s ease;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
/* Ensure inputs maintain their container width */
|
|
2956
|
+
:global(.props-editor-container input),
|
|
2957
|
+
:global(.props-editor-container select),
|
|
2958
|
+
:global(.props-editor-container textarea),
|
|
2959
|
+
:global(.props-editor-container .input-wrapper),
|
|
2960
|
+
:global(.props-editor-container .input-container),
|
|
2961
|
+
:global(.props-editor-container .color-picker-popover) {
|
|
2962
|
+
width: 100%;
|
|
2963
|
+
min-width: 0;
|
|
2964
|
+
box-sizing: border-box;
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
/* Force width constraints on accordion content */
|
|
2968
|
+
:global(.accordion-content) {
|
|
2969
|
+
width: 100%;
|
|
2970
|
+
min-width: 0;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
/* Ensure popover content stays within viewport */
|
|
2974
|
+
:global(.popover-content) {
|
|
2975
|
+
max-width: calc(100vw - 2rem);
|
|
2976
|
+
min-width: 0;
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
/* Ensure text inputs can expand as needed */
|
|
2980
|
+
:global(.props-editor input[type="text"]),
|
|
2981
|
+
:global(.props-editor textarea) {
|
|
2982
|
+
min-height: fit-content;
|
|
2983
|
+
}
|
|
2984
|
+
|
|
2985
|
+
/* Update scrollbar styling to match */
|
|
2986
|
+
:global([data-scrollable="true"] .scrollbar) {
|
|
2987
|
+
width: 8px !important; /* Thinner scrollbar */
|
|
2988
|
+
margin-right: 2px !important; /* Add padding from the right */
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
:global([data-scrollable="true"] .thumb) {
|
|
2992
|
+
background-color: hsl(var(--grey1)) !important;
|
|
2993
|
+
border-radius: 3px !important;
|
|
2994
|
+
margin-right: 2px !important;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
:global([data-scrollable="true"] .thumb:hover) {
|
|
2998
|
+
background-color: hsl(var(--muted-foreground) / 0.6) !important;
|
|
2999
|
+
}
|
|
3000
|
+
|
|
3001
|
+
:global([data-scrollable="true"] .track) {
|
|
3002
|
+
background-color: transparent !important;
|
|
3003
|
+
margin-right: 2px !important;
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
:global([data-scrollable="true"]) {
|
|
3007
|
+
padding-right: 2px !important; /* Add padding for scrollbar */
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
/* Add these styles to ensure proper z-index layering */
|
|
3011
|
+
:global(.props-editor) {
|
|
3012
|
+
position: relative;
|
|
3013
|
+
z-index: 0;
|
|
3014
|
+
}
|
|
3015
|
+
|
|
3016
|
+
:global(.props-editor .color-suggestions) {
|
|
3017
|
+
z-index: 99999 !important; /* Force highest z-index */
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
/* Update existing styles to ensure proper stacking */
|
|
3021
|
+
:global(.draggable-mirror) {
|
|
3022
|
+
z-index: 100;
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
:global(.popover-content) {
|
|
3026
|
+
z-index: 50;
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
/* Ensure text area and its dropdowns are above other content */
|
|
3030
|
+
:global(.props-editor .text-area-input) {
|
|
3031
|
+
position: relative;
|
|
3032
|
+
z-index: 1;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
:global(.props-editor .text-area-input .color-suggestions) {
|
|
3036
|
+
z-index: 99999 !important;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
/* Make any input controller that's being interacted with appear on top */
|
|
3040
|
+
:global(.props-editor .input-controller) {
|
|
3041
|
+
position: relative;
|
|
3042
|
+
z-index: 1;
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3045
|
+
:global(.props-editor .input-controller:focus-within),
|
|
3046
|
+
:global(.props-editor .input-controller[data-state="open"]) {
|
|
3047
|
+
z-index: 99999 !important; /* Bring to front when active */
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
/* Specific handling for text area controller */
|
|
3051
|
+
:global(.props-editor .text-area-input) {
|
|
3052
|
+
position: relative;
|
|
3053
|
+
z-index: 1;
|
|
3054
|
+
}
|
|
3055
|
+
|
|
3056
|
+
:global(.props-editor .text-area-input:focus-within),
|
|
3057
|
+
:global(.props-editor .text-area-input[data-state="open"]) {
|
|
3058
|
+
z-index: 99999 !important;
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
/* Ensure dropdowns and suggestions always appear above */
|
|
3062
|
+
:global(.props-editor .color-suggestions),
|
|
3063
|
+
:global(.props-editor .popover-content),
|
|
3064
|
+
:global(.props-editor .dropdown-content) {
|
|
3065
|
+
z-index: 99999 !important;
|
|
3066
|
+
}
|
|
3067
|
+
|
|
3068
|
+
/* Add these specific styles to remove focus borders from cards */
|
|
3069
|
+
:global(.content-item .card),
|
|
3070
|
+
:global(.content-item [data-radix-collapsible-trigger]),
|
|
3071
|
+
:global(.content-item [data-radix-collapsible-content]) {
|
|
3072
|
+
outline: none !important;
|
|
3073
|
+
box-shadow: none !important;
|
|
3074
|
+
--tw-ring-offset-width: 0 !important;
|
|
3075
|
+
--tw-ring-width: 0 !important;
|
|
3076
|
+
--tw-ring-color: transparent !important;
|
|
3077
|
+
--tw-ring-offset-shadow: none !important;
|
|
3078
|
+
--tw-ring-shadow: none !important;
|
|
3079
|
+
}
|
|
3080
|
+
|
|
3081
|
+
/* Target the collapsible trigger button specifically */
|
|
3082
|
+
:global(.content-item [data-radix-collapsible-trigger] button),
|
|
3083
|
+
:global(.content-item [data-state]),
|
|
3084
|
+
:global(.content-item [aria-expanded]) {
|
|
3085
|
+
outline: none !important;
|
|
3086
|
+
box-shadow: none !important;
|
|
3087
|
+
border: none !important;
|
|
3088
|
+
--tw-ring-offset-width: 0 !important;
|
|
3089
|
+
--tw-ring-width: 0 !important;
|
|
3090
|
+
--tw-ring-color: transparent !important;
|
|
3091
|
+
--tw-ring-offset-shadow: none !important;
|
|
3092
|
+
--tw-ring-shadow: none !important;
|
|
3093
|
+
}
|
|
3094
|
+
|
|
3095
|
+
/* Style all cards and collapsible elements in Content tab */
|
|
3096
|
+
:global(.content-item-card),
|
|
3097
|
+
:global(.generic-content-card) {
|
|
3098
|
+
border: 2px solid hsl(var(--grey2)) !important;
|
|
3099
|
+
border-radius: var(--radius-small) !important;
|
|
3100
|
+
background-color: white !important;
|
|
3101
|
+
position: relative !important;
|
|
3102
|
+
box-shadow: none !important;
|
|
3103
|
+
}
|
|
3104
|
+
|
|
3105
|
+
/* Preserve card border but remove focus ring */
|
|
3106
|
+
:global(.content-item-card),
|
|
3107
|
+
:global(.generic-content-card) {
|
|
3108
|
+
border: 2px solid hsl(var(--grey2)) !important;
|
|
3109
|
+
}
|
|
3110
|
+
|
|
3111
|
+
/* Style separator */
|
|
3112
|
+
:global(.content-item-card) :global(.separator),
|
|
3113
|
+
:global(.generic-content-card) :global(.separator) {
|
|
3114
|
+
height: 2px !important;
|
|
3115
|
+
background-color: hsl(var(--grey2)) !important;
|
|
3116
|
+
border: none !important;
|
|
3117
|
+
margin: 0 !important;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
/* Add hover effect to content items */
|
|
3121
|
+
:global(.content-item-card [data-radix-collapsible-trigger]),
|
|
3122
|
+
:global(.generic-content-card [data-radix-collapsible-trigger]) {
|
|
3123
|
+
height: 52px !important;
|
|
3124
|
+
box-sizing: border-box !important;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
/* Remove the existing height declaration from this rule */
|
|
3128
|
+
:global(.content-item-card [data-radix-collapsible-trigger])::before,
|
|
3129
|
+
:global(.generic-content-card [data-radix-collapsible-trigger])::before {
|
|
3130
|
+
height: 52px !important;
|
|
3131
|
+
}
|
|
3132
|
+
|
|
3133
|
+
/* Add hover effect for title text */
|
|
3134
|
+
:global(.content-item-card [data-radix-collapsible-trigger] h4),
|
|
3135
|
+
:global(.generic-content-card [data-radix-collapsible-trigger] h3) {
|
|
3136
|
+
text-align: left !important;
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
/* Add hover overlay for the entire trigger area */
|
|
3140
|
+
:global(.content-item-card [data-radix-collapsible-trigger])::before,
|
|
3141
|
+
:global(.generic-content-card [data-radix-collapsible-trigger])::before {
|
|
3142
|
+
content: '';
|
|
3143
|
+
position: absolute;
|
|
3144
|
+
inset: 0;
|
|
3145
|
+
background-color: transparent;
|
|
3146
|
+
transition: background-color 0.2s ease;
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
:global(.content-item-card [data-radix-collapsible-trigger]:hover)::before,
|
|
3150
|
+
:global(.generic-content-card [data-radix-collapsible-trigger]:hover)::before {
|
|
3151
|
+
background-color: hsl(var(--grey1));
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
/* Add bottom border to collapsible triggers */
|
|
3155
|
+
:global(.content-item-card [data-radix-collapsible-trigger])::after,
|
|
3156
|
+
:global(.generic-content-card [data-radix-collapsible-trigger])::after {
|
|
3157
|
+
content: '';
|
|
3158
|
+
position: absolute;
|
|
3159
|
+
bottom: 0;
|
|
3160
|
+
left: 0;
|
|
3161
|
+
right: 0;
|
|
3162
|
+
height: 2px;
|
|
3163
|
+
background-color: hsl(var(--grey2));
|
|
3164
|
+
opacity: 1;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/* Only hide the border after the content has retracted */
|
|
3168
|
+
:global([data-state="closed"] [data-radix-collapsible-trigger])::after {
|
|
3169
|
+
transition: opacity 0ms linear 200ms;
|
|
3170
|
+
opacity: 0;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
/* Show border immediately when opening */
|
|
3174
|
+
:global([data-state="open"] [data-radix-collapsible-trigger])::after {
|
|
3175
|
+
transition: none;
|
|
3176
|
+
opacity: 1;
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
/* Update chevron rotation animation to match Design tab */
|
|
3180
|
+
:global(.content-item-card [data-radix-collapsible-trigger] .chevron),
|
|
3181
|
+
:global(.generic-content-card [data-radix-collapsible-trigger] .chevron) {
|
|
3182
|
+
transform: rotate(0deg);
|
|
3183
|
+
transition: transform 250ms cubic-bezier(0.61, 0, 0.32, 1) !important;
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
:global(.content-item-card [data-state="open"] .chevron),
|
|
3187
|
+
:global(.generic-content-card [data-state="open"] .chevron) {
|
|
3188
|
+
transform: rotate(180deg);
|
|
3189
|
+
}
|
|
3190
|
+
|
|
3191
|
+
/* Make buttons and drag handle clickable */
|
|
3192
|
+
:global(.content-item-card [data-radix-collapsible-trigger] button),
|
|
3193
|
+
:global(.content-item-card [data-radix-collapsible-trigger] .drag-handle),
|
|
3194
|
+
:global(.generic-content-card [data-radix-collapsible-trigger] button) {
|
|
3195
|
+
position: relative;
|
|
3196
|
+
z-index: 2;
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
/* Remove cursor pointer from content area */
|
|
3200
|
+
:global(.content-item-card [data-radix-collapsible-content]),
|
|
3201
|
+
:global(.generic-content-card [data-radix-collapsible-content]) {
|
|
3202
|
+
cursor: default !important;
|
|
3203
|
+
}
|
|
3204
|
+
|
|
3205
|
+
/* Keep text left-aligned */
|
|
3206
|
+
:global(.content-item-card h4),
|
|
3207
|
+
:global(.generic-content-card h3) {
|
|
3208
|
+
text-align: left !important;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
/* Add this new rule to remove focus borders */
|
|
3212
|
+
:global(.content-item *:focus),
|
|
3213
|
+
:global(.content-item *:focus-visible),
|
|
3214
|
+
:global([data-radix-collapsible-trigger]:focus),
|
|
3215
|
+
:global([data-radix-collapsible-trigger]:focus-visible) {
|
|
3216
|
+
outline: none !important;
|
|
3217
|
+
box-shadow: none !important;
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
/* Update existing focus style rules to be more specific */
|
|
3221
|
+
:global(.content-item button:focus),
|
|
3222
|
+
:global(.content-item button:focus-visible),
|
|
3223
|
+
:global(.content-item [role="button"]:focus),
|
|
3224
|
+
:global(.content-item [role="button"]:focus-visible) {
|
|
3225
|
+
outline: none !important;
|
|
3226
|
+
box-shadow: none !important;
|
|
3227
|
+
-webkit-tap-highlight-color: transparent !important;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
/* Add specific rule for collapsible triggers */
|
|
3231
|
+
:global(.content-item [data-radix-collapsible-trigger]) {
|
|
3232
|
+
--tw-ring-offset-width: 0 !important;
|
|
3233
|
+
--tw-ring-width: 0 !important;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
/* Add more specific focus removal for card elements */
|
|
3237
|
+
:global(.content-item-card:focus),
|
|
3238
|
+
:global(.content-item-card:focus-within),
|
|
3239
|
+
:global(.content-item-card [data-radix-collapsible-trigger]:focus),
|
|
3240
|
+
:global(.content-item-card [data-radix-collapsible-trigger]:focus-visible) {
|
|
3241
|
+
outline: none !important;
|
|
3242
|
+
box-shadow: none !important;
|
|
3243
|
+
--tw-ring-width: 0 !important;
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
/* Target button elements specifically */
|
|
3247
|
+
:global(.content-items button),
|
|
3248
|
+
:global(.content-items button *),
|
|
3249
|
+
:global(.content-items [role="button"]),
|
|
3250
|
+
:global(.content-items [role="button"] *) {
|
|
3251
|
+
outline: none !important;
|
|
3252
|
+
box-shadow: none !important;
|
|
3253
|
+
--tw-ring-offset-width: 0 !important;
|
|
3254
|
+
--tw-ring-width: 0 !important;
|
|
3255
|
+
--tw-ring-color: transparent !important;
|
|
3256
|
+
--tw-ring-offset-shadow: none !important;
|
|
3257
|
+
--tw-ring-shadow: none !important;
|
|
3258
|
+
-webkit-tap-highlight-color: transparent !important;
|
|
3259
|
+
}
|
|
3260
|
+
|
|
3261
|
+
/* Keep only hover effect */
|
|
3262
|
+
:global(.content-items button:hover) {
|
|
3263
|
+
background-color: transparent !important;
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
/* Add styles for input controller layout */
|
|
3267
|
+
:global(.input-controller) {
|
|
3268
|
+
display: flex !important;
|
|
3269
|
+
align-items: center !important;
|
|
3270
|
+
justify-content: space-between !important;
|
|
3271
|
+
width: 100% !important;
|
|
3272
|
+
position: relative !important;
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
:global(.input-controller > div:first-child) {
|
|
3276
|
+
flex: 1 !important;
|
|
3277
|
+
min-width: 0 !important;
|
|
3278
|
+
margin-right: 16px !important;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
:global(.input-controller > div:last-child) {
|
|
3282
|
+
width: 185px !important;
|
|
3283
|
+
flex-shrink: 0 !important;
|
|
3284
|
+
}
|
|
3285
|
+
|
|
3286
|
+
/* New animation system */
|
|
3287
|
+
:global(.accordion-content) {
|
|
3288
|
+
overflow: hidden;
|
|
3289
|
+
will-change: transform, opacity;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
:global([data-state="open"] .accordion-content) {
|
|
3293
|
+
animation: accordionOpen 0ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
:global([data-state="closed"] .accordion-content) {
|
|
3297
|
+
animation: accordionClose 0ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
@keyframes accordionOpen {
|
|
3301
|
+
0% {
|
|
3302
|
+
opacity: 0;
|
|
3303
|
+
transform: translateY(-8px);
|
|
3304
|
+
}
|
|
3305
|
+
100% {
|
|
3306
|
+
opacity: 1;
|
|
3307
|
+
transform: translateY(0);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
@keyframes accordionClose {
|
|
3312
|
+
0% {
|
|
3313
|
+
opacity: 1;
|
|
3314
|
+
transform: translateY(0);
|
|
3315
|
+
}
|
|
3316
|
+
100% {
|
|
3317
|
+
opacity: 0;
|
|
3318
|
+
transform: translateY(-8px);
|
|
3319
|
+
}
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
/* Chevron animation */
|
|
3323
|
+
:global(.accordion-trigger svg) {
|
|
3324
|
+
transition: transform 200ms ease; /* Increase from 50ms to 200ms to match Content tab */
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
:global([data-state="open"] .accordion-trigger svg) {
|
|
3328
|
+
transform: rotate(180deg);
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
/* Add this to match how we fixed card items */
|
|
3332
|
+
:global(.content-items button:focus),
|
|
3333
|
+
:global(.content-items button:focus-visible),
|
|
3334
|
+
:global(.content-items button[role="button"]:focus),
|
|
3335
|
+
:global(.content-items button[role="button"]:focus-visible) {
|
|
3336
|
+
outline: none !important;
|
|
3337
|
+
box-shadow: none !important;
|
|
3338
|
+
--tw-ring-offset-width: 0 !important;
|
|
3339
|
+
--tw-ring-width: 0 !important;
|
|
3340
|
+
--tw-ring-color: transparent !important;
|
|
3341
|
+
--tw-ring-offset-shadow: none !important;
|
|
3342
|
+
--tw-ring-shadow: none !important;
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
/* Target the button and its container */
|
|
3346
|
+
:global(.content-items button),
|
|
3347
|
+
:global(.content-items button *),
|
|
3348
|
+
:global(.content-items [role="button"]),
|
|
3349
|
+
:global(.content-items [role="button"] *) {
|
|
3350
|
+
outline: none !important;
|
|
3351
|
+
box-shadow: none !important;
|
|
3352
|
+
--tw-ring-offset-width: 0 !important;
|
|
3353
|
+
--tw-ring-width: 0 !important;
|
|
3354
|
+
--tw-ring-color: transparent !important;
|
|
3355
|
+
--tw-ring-offset-shadow: none !important;
|
|
3356
|
+
--tw-ring-shadow: none !important;
|
|
3357
|
+
-webkit-tap-highlight-color: transparent !important;
|
|
3358
|
+
}
|
|
3359
|
+
|
|
3360
|
+
/* Add this new style for double-height controllers */
|
|
3361
|
+
.double-height {
|
|
3362
|
+
height: 104px !important; /* Double the standard height (48 * 2) + gap (8) */
|
|
3363
|
+
}
|
|
3364
|
+
|
|
3365
|
+
/* Ensure padding is removed from all containers */
|
|
3366
|
+
:global(.accordion-content),
|
|
3367
|
+
:global(.card-content) {
|
|
3368
|
+
padding: 0 !important;
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
/* Specific style for font controllers */
|
|
3372
|
+
:global(.input-controller.double-height) {
|
|
3373
|
+
height: 104px !important;
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
/* Ensure content containers maintain width */
|
|
3377
|
+
:global(.accordion-content > div),
|
|
3378
|
+
:global(.card-content > div) {
|
|
3379
|
+
width: 100%;
|
|
3380
|
+
box-sizing: border-box;
|
|
3381
|
+
}
|
|
3382
|
+
|
|
3383
|
+
/* Add padding to content containers */
|
|
3384
|
+
:global(.accordion-content > div),
|
|
3385
|
+
:global(.card-content > div) {
|
|
3386
|
+
padding: 16px 16px !important;
|
|
3387
|
+
}
|
|
3388
|
+
|
|
3389
|
+
/* Height variations for different controllers */
|
|
3390
|
+
.position-height {
|
|
3391
|
+
height: 185px !important; /* Position controller */
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
.fixed-position-height {
|
|
3395
|
+
height: 110px !important; /* Fixed position controller */
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
.text-area-preview-height {
|
|
3399
|
+
height: 92px !important; /* Text area preview controller */
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
/* Specific styles for other controllers */
|
|
3403
|
+
:global(.input-controller.position-height) {
|
|
3404
|
+
height: 185px !important;
|
|
3405
|
+
}
|
|
3406
|
+
|
|
3407
|
+
:global(.input-controller.fixed-position-height) {
|
|
3408
|
+
height: 110px !important;
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
:global(.input-controller.text-area-preview-height) {
|
|
3412
|
+
height: 92px !important;
|
|
3413
|
+
}
|
|
3414
|
+
|
|
3415
|
+
/* Special handling for position controller */
|
|
3416
|
+
.position-controller {
|
|
3417
|
+
position: relative;
|
|
3418
|
+
z-index: 1;
|
|
3419
|
+
}
|
|
3420
|
+
|
|
3421
|
+
/* When active or dragging, position it relative to the root stacking context */
|
|
3422
|
+
.position-controller:hover,
|
|
3423
|
+
.position-controller:focus-within,
|
|
3424
|
+
.position-dragging {
|
|
3425
|
+
position: relative;
|
|
3426
|
+
z-index: 99999 !important;
|
|
3427
|
+
}
|
|
3428
|
+
|
|
3429
|
+
/* Ensure accordion items don't create new stacking contexts */
|
|
3430
|
+
:global(.accordion-item) {
|
|
3431
|
+
position: static !important;
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
:global(.accordion-content) {
|
|
3435
|
+
position: relative;
|
|
3436
|
+
z-index: 1;
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
/* Override any potential stacking contexts in the hierarchy */
|
|
3440
|
+
:global(.scroll-viewport),
|
|
3441
|
+
:global(.scroll-area),
|
|
3442
|
+
:global([data-radix-scroll-area-viewport]) {
|
|
3443
|
+
position: relative !important;
|
|
3444
|
+
transform: none !important;
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
/* Ensure the position controller can pierce through all layers */
|
|
3448
|
+
.position-controller:hover *,
|
|
3449
|
+
.position-controller:focus-within *,
|
|
3450
|
+
.position-dragging * {
|
|
3451
|
+
z-index: 99999 !important;
|
|
3452
|
+
}
|
|
3453
|
+
|
|
3454
|
+
/* Add new 'only' state */
|
|
3455
|
+
:global(.accordion-item[data-position="only"]) {
|
|
3456
|
+
border-radius: var(--radius-small) !important;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
/* Modify existing position rules */
|
|
3460
|
+
:global(.accordion-item[data-position="first"]) {
|
|
3461
|
+
border-top-left-radius: var(--radius-small);
|
|
3462
|
+
border-top-right-radius: var(--radius-small);
|
|
3463
|
+
border-bottom-left-radius: 0;
|
|
3464
|
+
border-bottom-right-radius: 0;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
3467
|
+
:global(.accordion-item[data-position="last"]) {
|
|
3468
|
+
border-top-left-radius: 0;
|
|
3469
|
+
border-top-right-radius: 0;
|
|
3470
|
+
border-bottom-left-radius: var(--radius-small);
|
|
3471
|
+
border-bottom-right-radius: var(--radius-small);
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
:global(.accordion-item[data-position="middle"]) {
|
|
3475
|
+
border-radius: 0;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
/* Override tab hover states */
|
|
3479
|
+
:global([role="tab"][data-state="active"]) {
|
|
3480
|
+
background-color: white !important;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
:global([role="tab"][data-state="active"]:hover) {
|
|
3484
|
+
background-color: white !important;
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
:global([role="tab"]:hover) {
|
|
3488
|
+
background-color: transparent !important;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
/* TAB colors in all contexts */
|
|
3492
|
+
/* Card & Collapsible buttons hover states */
|
|
3493
|
+
:global(.card .widgetic-colors button:hover),
|
|
3494
|
+
:global(.collapsible .widgetic-colors button:hover) {
|
|
3495
|
+
background-color: white !important;
|
|
3496
|
+
}
|
|
3497
|
+
|
|
3498
|
+
/* Card & Collapsible Icons color states */
|
|
3499
|
+
:global(.card .widgetic-colors button:hover svg),
|
|
3500
|
+
:global(.collapsible .widgetic-colors button:hover svg) {
|
|
3501
|
+
color: hsl(var(--grey7)) !important;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
/* Ensure scroll area maintains border radius */
|
|
3505
|
+
:global([data-radix-scroll-area-viewport]) {
|
|
3506
|
+
border-bottom-right-radius: var(--radius-large) !important;
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3509
|
+
:global(.scroll-area) {
|
|
3510
|
+
border-bottom-right-radius: var(--radius-large) !important;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
:global(.scroll-viewport) {
|
|
3514
|
+
border-bottom-right-radius: var(--radius-large) !important;
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
/* Ensure scrollbar doesn't affect border radius */
|
|
3518
|
+
:global([data-radix-scroll-area-scrollbar]) {
|
|
3519
|
+
border-bottom-right-radius: var(--radius-large) !important;
|
|
3520
|
+
padding-right: 2px !important;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
:global([data-radix-scroll-area-thumb]) {
|
|
3524
|
+
border-radius: var(--radius-small) !important;
|
|
3525
|
+
margin-right: 2px !important;
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
/* Add z-index management for tooltips */
|
|
3529
|
+
:global(.props-editor-tooltip) {
|
|
3530
|
+
z-index: 99999 !important;
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
/* Drag Handle Visibility */
|
|
3534
|
+
.header-hover-area {
|
|
3535
|
+
position: relative;
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
.header-hover-area:hover .drag-handle-container {
|
|
3539
|
+
opacity: 1;
|
|
3540
|
+
}
|
|
3541
|
+
|
|
3542
|
+
.drag-handle-container {
|
|
3543
|
+
transition: opacity 0.2s ease;
|
|
3544
|
+
cursor: move; /* Make sure cursor is 'move' on the handle */
|
|
3545
|
+
z-index: 999; /* Ensure it's always on top */
|
|
3546
|
+
}
|
|
3547
|
+
|
|
3548
|
+
.drag-handle-container:hover {
|
|
3549
|
+
opacity: 1 !important;
|
|
3550
|
+
}
|
|
3551
|
+
|
|
3552
|
+
/* Drag Handle Icon */
|
|
3553
|
+
:global(.drag-handle svg) {
|
|
3554
|
+
color: hsl(var(--grey2)) !important;
|
|
3555
|
+
transition: color 0.2s ease !important;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
:global(.drag-handle:hover svg) {
|
|
3559
|
+
color: hsl(var(--grey5)) !important;
|
|
3560
|
+
}
|
|
3561
|
+
|
|
3562
|
+
/* Content Item Hover Styles */
|
|
3563
|
+
:global(.content-item-card [data-radix-collapsible-trigger] h4),
|
|
3564
|
+
:global(.generic-content-card [data-radix-collapsible-trigger] h3) {
|
|
3565
|
+
text-align: left !important;
|
|
3566
|
+
}
|
|
3567
|
+
|
|
3568
|
+
/* Add new specific hover styles for content titles */
|
|
3569
|
+
:global(.content-item-trigger:hover .content-title),
|
|
3570
|
+
:global(.generic-content-trigger:hover .content-title) {
|
|
3571
|
+
text-decoration: underline !important;
|
|
3572
|
+
}
|
|
3573
|
+
|
|
3574
|
+
/* Background hover effects */
|
|
3575
|
+
:global(.content-item-card [data-radix-collapsible-trigger])::before,
|
|
3576
|
+
:global(.generic-content-card [data-radix-collapsible-trigger])::before {
|
|
3577
|
+
content: '';
|
|
3578
|
+
position: absolute;
|
|
3579
|
+
inset: 0;
|
|
3580
|
+
background-color: transparent;
|
|
3581
|
+
transition: background-color 0.2s ease;
|
|
3582
|
+
}
|
|
3583
|
+
|
|
3584
|
+
:global(.content-item-card [data-radix-collapsible-trigger]:hover)::before,
|
|
3585
|
+
:global(.generic-content-card [data-radix-collapsible-trigger]:hover)::before {
|
|
3586
|
+
background-color: hsl(var(--grey1));
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3589
|
+
/* Add new button hover styles */
|
|
3590
|
+
:global(.hover-button:hover .hover-button-inner) {
|
|
3591
|
+
background-color: hsl(var(--grey1));
|
|
3592
|
+
}
|
|
3593
|
+
|
|
3594
|
+
:global(.hover-button:hover span) {
|
|
3595
|
+
text-decoration: underline;
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
/* Add Draggable Sort Animation styles */
|
|
3599
|
+
:global(.content-item) {
|
|
3600
|
+
position: relative;
|
|
3601
|
+
z-index: 1;
|
|
3602
|
+
will-change: transform;
|
|
3603
|
+
transform: translate3d(0, 0, 0);
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
:global(.draggable-container--is-dragging) {
|
|
3607
|
+
cursor: grabbing;
|
|
3608
|
+
}
|
|
3609
|
+
|
|
3610
|
+
:global(.draggable-source--is-dragging) {
|
|
3611
|
+
opacity: 0.3;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
:global(.draggable-source--is-dragging .content-title) {
|
|
3615
|
+
text-decoration: none !important;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
:global(.draggable-source--placed) {
|
|
3619
|
+
visibility: visible !important;
|
|
3620
|
+
transition: none !important;
|
|
3621
|
+
opacity: 1 !important;
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
:global(.content-items--is-dragging .content-item:not(.draggable-source--is-dragging)) {
|
|
3625
|
+
transition: transform 200ms ease-in-out;
|
|
3626
|
+
}
|
|
3627
|
+
|
|
3628
|
+
:global(.content-item.draggable-mirror) {
|
|
3629
|
+
transform: scale(1.05);
|
|
3630
|
+
}
|
|
3631
|
+
|
|
3632
|
+
:global(.content-item.draggable--original) {
|
|
3633
|
+
opacity: 0;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
:global(.content-item.draggable-source--is-dragging) {
|
|
3637
|
+
opacity: 0.3;
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3640
|
+
:global(.content-item.draggable-source--is-dragging .content-title) {
|
|
3641
|
+
text-decoration: none !important;
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3644
|
+
:global(.content-item.draggable-mirror .content-title) {
|
|
3645
|
+
text-decoration: none !important;
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
:global(.draggable-mirror .drag-handle svg) {
|
|
3649
|
+
color: hsl(var(--grey3)) !important;
|
|
3650
|
+
}
|
|
3651
|
+
|
|
3652
|
+
:global(.draggable--original) {
|
|
3653
|
+
display: none;
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3656
|
+
:global(.content-items--is-dragging) {
|
|
3657
|
+
cursor: grabbing;
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
/* Prevent hover effects during dragging */
|
|
3661
|
+
:global(.content-items--is-dragging .content-item-card [data-radix-collapsible-trigger]:hover)::before {
|
|
3662
|
+
background-color: transparent;
|
|
3663
|
+
}
|
|
3664
|
+
|
|
3665
|
+
:global(.content-items--is-dragging .content-item:not(.draggable-mirror) .content-title:hover) {
|
|
3666
|
+
text-decoration: none !important;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
:global(.content-items--is-dragging .content-item-trigger:hover .content-title) {
|
|
3670
|
+
text-decoration: none !important;
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
:global(.content-items--is-dragging .content-item.draggable--over) {
|
|
3674
|
+
transform: scale(1.015) !important;
|
|
3675
|
+
}
|
|
3676
|
+
|
|
3677
|
+
:global(.content-item.draggable-mirror) {
|
|
3678
|
+
transform: scale(1.05);
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
:global(.content-item.draggable--original) {
|
|
3682
|
+
opacity: 0;
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
:global(.content-item.draggable-source--is-dragging) {
|
|
3686
|
+
opacity: 0.3;
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
:global(.content-item.draggable-source--is-dragging .content-title) {
|
|
3690
|
+
text-decoration: none !important;
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3693
|
+
:global(.content-item.draggable-mirror .content-title) {
|
|
3694
|
+
text-decoration: none !important;
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
/* Update drag handle hover area and content title behavior */
|
|
3698
|
+
:global(.content-item .drag-handle) {
|
|
3699
|
+
width: 56px !important;
|
|
3700
|
+
display: flex !important;
|
|
3701
|
+
align-items: center !important;
|
|
3702
|
+
justify-content: center !important;
|
|
3703
|
+
height: 100% !important;
|
|
3704
|
+
cursor: move !important;
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
/* Keep the icon size consistent */
|
|
3708
|
+
:global(.content-item .drag-handle svg) {
|
|
3709
|
+
width: 24px !important;
|
|
3710
|
+
height: 24px !important;
|
|
3711
|
+
}
|
|
3712
|
+
|
|
3713
|
+
/* Prevent title underline when hovering drag handle */
|
|
3714
|
+
:global(.content-item .drag-handle:hover ~ .content-title) {
|
|
3715
|
+
text-decoration: none !important;
|
|
3716
|
+
}
|
|
3717
|
+
|
|
3718
|
+
:global(.content-item .drag-handle:hover ~ h4) {
|
|
3719
|
+
text-decoration: none !important;
|
|
3720
|
+
}
|
|
3721
|
+
|
|
3722
|
+
/* Ensure drag handle maintains proper positioning */
|
|
3723
|
+
:global(.content-item-trigger .drag-handle) {
|
|
3724
|
+
position: relative;
|
|
3725
|
+
z-index: 3;
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
/* Remove any previous left padding from the flex container */
|
|
3729
|
+
:global(.content-item-trigger .flex) {
|
|
3730
|
+
padding-left: 0 !important;
|
|
3731
|
+
}
|
|
3732
|
+
|
|
3733
|
+
:global(.draggable-mirror .drag-handle svg) {
|
|
3734
|
+
color: hsl(var(--grey3)) !important;
|
|
3735
|
+
}
|
|
3736
|
+
|
|
3737
|
+
:global(.draggable--original) {
|
|
3738
|
+
display: none;
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
:global(.content-items--is-dragging) {
|
|
3742
|
+
cursor: grabbing;
|
|
3743
|
+
}
|
|
3744
|
+
|
|
3745
|
+
|
|
3746
|
+
:global(.content-item) {
|
|
3747
|
+
contain: layout style;
|
|
3748
|
+
will-change: transform;
|
|
3749
|
+
transform: translate3d(0, 0, 0);
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
:global(.draggable-mirror) {
|
|
3753
|
+
contain: layout style;
|
|
3754
|
+
will-change: transform;
|
|
3755
|
+
transform: translate3d(0, 0, 0);
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
|
|
3759
|
+
:global(.content-items--is-dragging .content-item:not(.draggable-source--is-dragging)) {
|
|
3760
|
+
transition: transform 200ms cubic-bezier(0.2, 0, 0.2, 1);
|
|
3761
|
+
will-change: transform;
|
|
3762
|
+
}
|
|
3763
|
+
|
|
3764
|
+
/* Animation classes */
|
|
3765
|
+
:global(.design-tab-content[data-state="active"]) {
|
|
3766
|
+
animation: none;
|
|
3767
|
+
transform: none;
|
|
3768
|
+
}
|
|
3769
|
+
|
|
3770
|
+
:global(.design-tab-content.initial-load[data-state="active"]) {
|
|
3771
|
+
animation: slideUp 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
3772
|
+
transform: none;
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
:global(.design-tab-content.animate-slide-left[data-state="active"]) {
|
|
3776
|
+
animation: slideFromLeft 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
3777
|
+
transform: none;
|
|
3778
|
+
}
|
|
3779
|
+
|
|
3780
|
+
:global(.content-tab-content.animate-slide-right[data-state="active"]) {
|
|
3781
|
+
animation: slideFromRight 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
3782
|
+
transform: none;
|
|
3783
|
+
}
|
|
3784
|
+
|
|
3785
|
+
@keyframes slideFromLeft {
|
|
3786
|
+
from {
|
|
3787
|
+
opacity: 0;
|
|
3788
|
+
transform: translateX(-20px);
|
|
3789
|
+
}
|
|
3790
|
+
to {
|
|
3791
|
+
opacity: 1;
|
|
3792
|
+
transform: none;
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
@keyframes slideFromRight {
|
|
3797
|
+
from {
|
|
3798
|
+
opacity: 0;
|
|
3799
|
+
transform: translateX(20px);
|
|
3800
|
+
}
|
|
3801
|
+
to {
|
|
3802
|
+
opacity: 1;
|
|
3803
|
+
transform: none;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
@keyframes slideUp {
|
|
3808
|
+
from {
|
|
3809
|
+
opacity: 0;
|
|
3810
|
+
transform: translateY(20px);
|
|
3811
|
+
}
|
|
3812
|
+
to {
|
|
3813
|
+
opacity: 1;
|
|
3814
|
+
transform: none;
|
|
3815
|
+
}
|
|
3816
|
+
}
|
|
3817
|
+
|
|
3818
|
+
@keyframes fadeInUp {
|
|
3819
|
+
from {
|
|
3820
|
+
opacity: 0;
|
|
3821
|
+
transform: translateY(10px);
|
|
3822
|
+
}
|
|
3823
|
+
to {
|
|
3824
|
+
opacity: 1;
|
|
3825
|
+
transform: translateY(0);
|
|
3826
|
+
}
|
|
3827
|
+
}
|
|
3828
|
+
|
|
3829
|
+
|
|
3830
|
+
|
|
3831
|
+
@keyframes slideUp {
|
|
3832
|
+
from {
|
|
3833
|
+
opacity: 0;
|
|
3834
|
+
transform: translateY(20px);
|
|
3835
|
+
}
|
|
3836
|
+
to {
|
|
3837
|
+
opacity: 1;
|
|
3838
|
+
transform: none;
|
|
3839
|
+
}
|
|
3840
|
+
}
|
|
3841
|
+
|
|
3842
|
+
@keyframes slideRight {
|
|
3843
|
+
from {
|
|
3844
|
+
opacity: 0;
|
|
3845
|
+
transform: translateX(20px);
|
|
3846
|
+
}
|
|
3847
|
+
to {
|
|
3848
|
+
opacity: 1;
|
|
3849
|
+
transform: none;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
|
|
3853
|
+
:global(.animate-slide-up) {
|
|
3854
|
+
animation: slideUp 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards !important;
|
|
3855
|
+
}
|
|
3856
|
+
|
|
3857
|
+
:global(.animate-slide-right) {
|
|
3858
|
+
animation: slideRight 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards !important;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
/* Add these styles at the end of the style block */
|
|
3862
|
+
|
|
3863
|
+
/* Empty state animations */
|
|
3864
|
+
:global(.content-tab-content .empty-state) {
|
|
3865
|
+
opacity: 0;
|
|
3866
|
+
transform: translateY(10px);
|
|
3867
|
+
animation: fadeInUp 300ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
|
3868
|
+
animation-delay: 100ms;
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
@keyframes fadeInUp {
|
|
3872
|
+
from {
|
|
3873
|
+
opacity: 0;
|
|
3874
|
+
transform: translateY(10px);
|
|
3875
|
+
}
|
|
3876
|
+
to {
|
|
3877
|
+
opacity: 1;
|
|
3878
|
+
transform: translateY(0);
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
|
|
3882
|
+
/* Draggable mirror styles - styling only, no positioning */
|
|
3883
|
+
:global(.draggable-mirror) {
|
|
3884
|
+
opacity: 1 !important;
|
|
3885
|
+
background-color: white !important;
|
|
3886
|
+
border: 2px solid hsl(var(--grey2)) !important;
|
|
3887
|
+
border-radius: var(--radius-small) !important;
|
|
3888
|
+
box-shadow: none !important;
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
/* Style for item being hovered over (potential drop target) */
|
|
3892
|
+
:global(.content-items--is-dragging .content-item.draggable--over) {
|
|
3893
|
+
transform: scale(1.015) !important;
|
|
3894
|
+
border: 2px solid hsl(var(--grey2)) !important;
|
|
3895
|
+
border-radius: var(--radius-small) !important;
|
|
3896
|
+
background-color: hsl(var(--grey1)) !important;
|
|
3897
|
+
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05) !important;
|
|
3898
|
+
z-index: 10 !important;
|
|
3899
|
+
}
|
|
3900
|
+
|
|
3901
|
+
/* Remove all borders and shadows from draggable mirror */
|
|
3902
|
+
:global(.draggable-mirror) {
|
|
3903
|
+
border: 2px solid hsl(var(--grey2)) !important;
|
|
3904
|
+
border-radius: var(--radius-small) !important;
|
|
3905
|
+
box-shadow: none !important;
|
|
3906
|
+
}
|
|
3907
|
+
|
|
3908
|
+
/* Remove borders from all children inside the mirror */
|
|
3909
|
+
:global(.draggable-mirror *) {
|
|
3910
|
+
border: none !important;
|
|
3911
|
+
border-top: none !important;
|
|
3912
|
+
border-bottom: none !important;
|
|
3913
|
+
border-left: none !important;
|
|
3914
|
+
border-right: none !important;
|
|
3915
|
+
}
|
|
3916
|
+
|
|
3917
|
+
/* Make the original item completely transparent during dragging */
|
|
3918
|
+
:global(.draggable-source--is-dragging) {
|
|
3919
|
+
opacity: 0 !important;
|
|
3920
|
+
}
|
|
3921
|
+
|
|
3922
|
+
:global(body.dragging-preserve-borders .form-input),
|
|
3923
|
+
:global(body.dragging-preserve-borders [id^="number-input"]),
|
|
3924
|
+
:global(body.dragging-preserve-borders .number-input-button),
|
|
3925
|
+
:global(body.dragging-preserve-borders input),
|
|
3926
|
+
:global(body.dragging-preserve-borders select),
|
|
3927
|
+
:global(body.dragging-preserve-borders textarea),
|
|
3928
|
+
:global(body.dragging-preserve-borders button) {
|
|
3929
|
+
border: 1px solid hsl(var(--grey2)) !important;
|
|
3930
|
+
}
|
|
3931
|
+
|
|
3932
|
+
/* Add a new rule to force fixed heights during dragging */
|
|
3933
|
+
:global(.dragging-active .content-item) {
|
|
3934
|
+
height: 56px !important;
|
|
3935
|
+
min-height: 56px !important;
|
|
3936
|
+
max-height: 56px !important;
|
|
3937
|
+
overflow: hidden !important;
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
:global(.dragging-active [data-radix-collapsible-content]) {
|
|
3941
|
+
height: 0 !important;
|
|
3942
|
+
display: none !important;
|
|
3943
|
+
overflow: hidden !important;
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
/* Add explicit transition for content tab chevrons */
|
|
3947
|
+
:global(.content-item-card .chevron),
|
|
3948
|
+
:global(.generic-content-card .chevron) {
|
|
3949
|
+
transition: transform 200ms ease !important; /* Make explicit matching transition */
|
|
3950
|
+
}
|
|
3951
|
+
</style>
|
|
3952
|
+
|