From e075c9c011db74f6ce70ebf4c97cb95eb7f60a4e Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 1 May 2024 14:32:13 -0500 Subject: [PATCH 1/4] Revert previous change --- src/web/assets/cp/src/css/_main.scss | 1 - .../assets/cp/src/js/TableElementIndexView.js | 22 ------------------- 2 files changed, 23 deletions(-) diff --git a/src/web/assets/cp/src/css/_main.scss b/src/web/assets/cp/src/css/_main.scss index 9c8444e205a..cc3233f21ad 100644 --- a/src/web/assets/cp/src/css/_main.scss +++ b/src/web/assets/cp/src/css/_main.scss @@ -2653,7 +2653,6 @@ h2 + .actions { var(--pane-padding, var(--padding)) * -1 + var(--pane-padding, var(--m)) ); padding: 0 !important; - overscroll-behavior: contain; overflow-x: auto; table.data { diff --git a/src/web/assets/cp/src/js/TableElementIndexView.js b/src/web/assets/cp/src/js/TableElementIndexView.js index 477d1dd297a..296776d7c05 100644 --- a/src/web/assets/cp/src/js/TableElementIndexView.js +++ b/src/web/assets/cp/src/js/TableElementIndexView.js @@ -34,9 +34,6 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({ // Set the sort header this.initTableHeaders(); - this.addListener(Garnish.$win, 'resize', this.setContainerHeight); - this.setContainerHeight(); - // Create the table sorter if ( (this.settings.sortable || @@ -403,25 +400,6 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({ Craft.cp.updateResponsiveTables(); }, - setContainerHeight: function (event) { - window.requestAnimationFrame(() => { - const $tablePane = this.$container.find('.tablepane'); - if (!$tablePane.length) { - return; - } - - const footerHeight = $('#content > #footer').outerHeight(true) || 0; - const margin = parseInt( - getComputedStyle($tablePane[0]).getPropertyValue('--padding'), - 10 - ); - const containerHeight = - window.innerHeight - $tablePane.offset().top - footerHeight - margin; - - $tablePane.css('max-height', containerHeight); - }); - }, - _collapseElement: function ($toggle, force) { if (!force && !$toggle.hasClass('expanded')) { return false; From 10fcb31ea3059c47ebab1b7e7f2c95cbb601228d Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Thu, 2 May 2024 11:59:31 -0500 Subject: [PATCH 2/4] Sticky scrollbar for element indexes --- src/web/assets/cp/src/Craft.js | 1 + src/web/assets/cp/src/css/_main.scss | 6 + .../assets/cp/src/js/CraftProxyScrollbar.js | 104 ++++++++++++++++++ .../assets/cp/src/js/TableElementIndexView.js | 29 +++++ 4 files changed, 140 insertions(+) create mode 100644 src/web/assets/cp/src/js/CraftProxyScrollbar.js diff --git a/src/web/assets/cp/src/Craft.js b/src/web/assets/cp/src/Craft.js index 1b555a0f798..33b82996af6 100644 --- a/src/web/assets/cp/src/Craft.js +++ b/src/web/assets/cp/src/Craft.js @@ -101,3 +101,4 @@ import './js/CraftGlobalSidebar.js'; import './js/CraftDisclosure.js'; import './js/CraftTooltip.js'; import './js/CraftElementLabel'; +import './js/CraftProxyScrollbar'; diff --git a/src/web/assets/cp/src/css/_main.scss b/src/web/assets/cp/src/css/_main.scss index cc3233f21ad..eac73f79d92 100644 --- a/src/web/assets/cp/src/css/_main.scss +++ b/src/web/assets/cp/src/css/_main.scss @@ -3623,6 +3623,12 @@ table { background: transparent; } } + + craft-proxy-scrollbar { + position: sticky; + width: calc(100% + var(--xl) * 2); + margin-inline: calc(var(--xl) * -1); + } } .elements { diff --git a/src/web/assets/cp/src/js/CraftProxyScrollbar.js b/src/web/assets/cp/src/js/CraftProxyScrollbar.js new file mode 100644 index 00000000000..0386389a24b --- /dev/null +++ b/src/web/assets/cp/src/js/CraftProxyScrollbar.js @@ -0,0 +1,104 @@ +/** + * Proxy scrollbar + * + * Display a scrollbar that is synced with another element + * + * @property {string} scroller - The selector of the element that will be scrolled + * @property {string} content - The selector of the element within the scroller containing the overflow content + * @property {boolean} hidden - Whether the scrollbar should be hidden + * @property {HTMLElement} proxy - The element that represents the scrollbar + * @property {HTMLElement} scroller - The element that will be scrolled + * @property {HTMLElement} content - The element within the scroller containing the overflow content + */ +class CraftProxyScrollbar extends HTMLElement { + static observedAttributes = ['hidden']; + + get hidden() { + return this.getAttribute('hidden'); + } + + get hasOverflow() { + return this.content?.scrollWidth > this.scroller?.clientWidth; + } + + connectedCallback() { + this.ignoreScrollEvent = false; + this.animation = false; + + this.scroller = document.querySelector(this.getAttribute('scroller')); + this.content = document.querySelector(this.getAttribute('content')); + + if (!this.scroller || !this.content) { + return; + } + + this.proxy = document.createElement('div'); + this.proxy.style.height = '1px'; + this.proxy.style.width = this.content.getBoundingClientRect().width + 'px'; + + this.appendChild(this.proxy); + + this.addEventListener('scroll', this.syncScroll(this.scroller, this)); + this.scroller.addEventListener( + 'scroll', + this.syncScroll(this, this.scroller) + ); + window.addEventListener('resize', this.handleResize.bind(this)); + + Object.assign(this.style, { + display: this.hasOverflow ? 'block' : 'none', + overflowX: 'scroll', + }); + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name === 'hidden') { + this.style.display = newValue ? 'none' : 'block'; + } + } + + disconnectedCallback() { + this.proxy.remove(); + + this.scroller.removeEventListener( + 'scroll', + this.syncScroll(this.scroller, this) + ); + this.scroller.removeEventListener( + 'scroll', + this.syncScroll(this, this.scroller) + ); + + window.removeEventListener('resize', this.handleResize.bind(this)); + } + + handleResize() { + this.proxy.style.width = this.content.getBoundingClientRect().width + 'px'; + + if (this.hasOverflow) { + this.removeAttribute('hidden'); + } else { + this.setAttribute('hidden', 'true'); + } + } + + syncScroll(a, b) { + return () => { + if (this.ignoreScrollEvent) { + return false; + } + + if (this.animation) { + cancelAnimationFrame(this.animation); + } + + this.animation = requestAnimationFrame(() => { + this.ignoreScrollEvent = true; + a.scrollLeft = b.scrollLeft; + this.ignoreScrollEvent = false; + }); + }; + } +} + +customElements.define('craft-proxy-scrollbar', CraftProxyScrollbar); diff --git a/src/web/assets/cp/src/js/TableElementIndexView.js b/src/web/assets/cp/src/js/TableElementIndexView.js index 296776d7c05..84b550717b2 100644 --- a/src/web/assets/cp/src/js/TableElementIndexView.js +++ b/src/web/assets/cp/src/js/TableElementIndexView.js @@ -34,6 +34,8 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({ // Set the sort header this.initTableHeaders(); + this.createScrollbar(); + // Create the table sorter if ( (this.settings.sortable || @@ -650,4 +652,31 @@ Craft.TableElementIndexView = Craft.BaseElementIndexView.extend({ this.base(); }, + + createScrollbar() { + const footer = document.querySelector('#content > #footer'); + const stickyScrollbar = document.createElement('craft-proxy-scrollbar'); + stickyScrollbar.setAttribute('scroller', '.tablepane'); + stickyScrollbar.setAttribute('content', '.tablepane > table'); + + stickyScrollbar.style.bottom = `${ + footer.getBoundingClientRect().height + 2 + }px`; + + let $scrollbar = $(stickyScrollbar); + const observer = new IntersectionObserver( + ([ev]) => { + if (ev.intersectionRatio < 1) { + $scrollbar.insertAfter(this.$container); + } else { + $scrollbar.remove(); + } + }, + { + rootMargin: '0px 0px -1px 0px', + threshold: [1], + } + ); + observer.observe(footer); + }, }); From 56f74c82272f843cdccbdf9b861e7f622ed248cf Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Thu, 2 May 2024 14:59:20 -0700 Subject: [PATCH 3/4] build --- src/web/assets/cp/dist/cp.js | 2 +- src/web/assets/cp/dist/cp.js.map | 2 +- src/web/assets/cp/dist/css/cp.css | 2 +- src/web/assets/cp/dist/css/cp.css.map | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/web/assets/cp/dist/cp.js b/src/web/assets/cp/dist/cp.js index c2246be99b7..514acb070dc 100644 --- a/src/web/assets/cp/dist/cp.js +++ b/src/web/assets/cp/dist/cp.js @@ -1,3 +1,3 @@ /*! For license information please see cp.js.LICENSE.txt */ -(function(){var __webpack_modules__={463:function(){Craft.Accordion=Garnish.Base.extend({$trigger:null,targetSelector:null,_$target:null,init:function(t){var e=this;this.$trigger=$(t),this.$trigger.data("accordion")&&(console.warn("Double-instantiating an accordion trigger on an element"),this.$trigger.data("accordion").destroy()),this.$trigger.data("accordion",this),this.targetSelector=this.$trigger.attr("aria-controls")?"#".concat(this.$trigger.attr("aria-controls")):null,this.targetSelector&&(this._$target=$(this.targetSelector)),this.addListener(this.$trigger,"click","onTriggerClick"),this.addListener(this.$trigger,"keypress",(function(t){var n=t.keyCode;n!==Garnish.SPACE_KEY&&n!==Garnish.RETURN_KEY||(t.preventDefault(),e.onTriggerClick())}))},onTriggerClick:function(){"true"===this.$trigger.attr("aria-expanded")?this.hideTarget(this._$target):this.showTarget(this._$target)},showTarget:function(t){var e=this;if(t&&t.length){this.showTarget._currentHeight=t.height(),t.removeClass("hidden"),this.$trigger.removeClass("collapsed").addClass("expanded").attr("aria-expanded","true");for(var n=0;n=this.settings.maxItems)){var e=$(t).appendTo(this.$tbody),n=e.find(".delete");this.settings.sortable&&this.sorter.addItems(e),this.$deleteBtns=this.$deleteBtns.add(n),this.addListener(n,"click","handleDeleteBtnClick"),this.totalItems++,this.updateUI()}},reorderItems:function(){var t=this;if(this.settings.sortable){for(var e=[],n=0;n=this.settings.maxItems?$(this.settings.newItemBtnSelector).addClass("hidden"):$(this.settings.newItemBtnSelector).removeClass("hidden"))}},{defaults:{tableSelector:null,noItemsSelector:null,newItemBtnSelector:null,idAttribute:"data-id",nameAttribute:"data-name",sortable:!1,allowDeleteAll:!0,minItems:0,maxItems:null,reorderAction:null,deleteAction:null,reorderSuccessMessage:Craft.t("app","New order saved."),reorderFailMessage:Craft.t("app","Couldn’t save new order."),confirmDeleteMessage:Craft.t("app","Are you sure you want to delete “{name}”?"),deleteSuccessMessage:Craft.t("app","“{name}” deleted."),deleteFailMessage:Craft.t("app","Couldn’t delete “{name}”."),onReorderItems:$.noop,onDeleteItem:$.noop}})},6872:function(){Craft.AssetImageEditor=Garnish.Modal.extend({$body:null,$footer:null,$imageTools:null,$buttons:null,$cancelBtn:null,$replaceBtn:null,$saveBtn:null,$focalPointBtn:null,$editorContainer:null,$straighten:null,$croppingCanvas:null,$spinner:null,$constraintContainer:null,$constraintRadioInputs:null,$customConstraints:null,canvas:null,image:null,viewport:null,focalPoint:null,grid:null,croppingCanvas:null,clipper:null,croppingRectangle:null,cropperHandles:null,cropperGrid:null,croppingShade:null,imageStraightenAngle:0,viewportRotation:0,originalWidth:0,originalHeight:0,imageVerticeCoords:null,zoomRatio:1,animationInProgress:!1,currentView:"",assetId:null,cacheBust:null,draggingCropper:!1,scalingCropper:!1,draggingFocal:!1,previousMouseX:0,previousMouseY:0,shiftKeyHeld:!1,editorHeight:0,editorWidth:0,cropperState:!1,scaleFactor:1,flipData:{},focalPointState:!1,maxImageSize:null,lastLoadedDimensions:null,imageIsLoading:!1,mouseMoveEvent:null,croppingConstraint:!1,constraintOrientation:"landscape",showingCustomConstraint:!1,saving:!1,renderImage:null,renderCropper:null,_queue:null,init:function(t,e){var n=this;this._queue=new Craft.Queue,this.cacheBust=Date.now(),this.setSettings(e,Craft.AssetImageEditor.defaults),null===this.settings.allowDegreeFractions&&(this.settings.allowDegreeFractions=Craft.isImagick),Garnish.prefersReducedMotion()&&(this.settings.animationDuration=1),this.assetId=t,this.flipData={x:0,y:0},this.$container=$('').appendTo(Garnish.$bod),this.$body=$('
').appendTo(this.$container),this.$footer=$('