@glomex/glomex-dialog
Advanced tools
Comparing version 1.12.6 to 1.13.0
@@ -36,18 +36,63 @@ { | ||
{ | ||
"name": "mode" | ||
"name": "mode", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "Can take the values \"hidden\", \"inline\", \"dock\" or \"lightbox\"." | ||
}, | ||
{ | ||
"name": "aspect-ratio" | ||
"name": "aspect-ratio", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "The aspect-ratio for the inline element. Default is 16:9" | ||
}, | ||
{ | ||
"name": "dock-target" | ||
"name": "dock-target", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "A dom-element with position:fixed where mode=dock should animate to" | ||
}, | ||
{ | ||
"name": "dock-target-inset" | ||
"name": "dock-target-inset", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "Defines the position of the dock using inset" | ||
}, | ||
{ | ||
"name": "dock-aspect-ratio" | ||
"name": "dock-aspect-ratio", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "The aspect-ratio when the element is mode=dock" | ||
}, | ||
{ | ||
"name": "dock-downscale" | ||
"name": "dock-mode", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "When set to \"sticky\" it behaves similar to \"position: sticky\" in CSS (with a max width of 400px). If undefined docks the content to a corner." | ||
}, | ||
{ | ||
"name": "dock-sticky-target-top", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "The top distance for dock-mode=sticky in pixels (defaults to 0)" | ||
}, | ||
{ | ||
"name": "dock-sticky-aspect-ratio", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "The aspect-ratio when the element is docked for dock-mode=sticky" | ||
}, | ||
{ | ||
"name": "dock-downscale", | ||
"type": { | ||
"text": "string" | ||
}, | ||
"description": "Do you want to scale the element when mode=dock" | ||
} | ||
@@ -54,0 +99,0 @@ ], |
@@ -29,2 +29,7 @@ const NON_VISIBLE_WIDTH = window.innerWidth < 720 ? 320 : 640; | ||
const updateDockStickyAspectRatio = (element, aspectRatio) => { | ||
element.shadowRoot.querySelector('.dock-sticky-target .aspect-ratio-box') | ||
.style.paddingTop = `${(1 / aspectRatio) * 100}%`; | ||
}; | ||
const getAlternativeDockTarget = (element) => { | ||
@@ -45,2 +50,3 @@ const dockTarget = element.getAttribute('dock-target'); | ||
const getDefaultDockTarget = (element) => element.shadowRoot.querySelector('.dock-target'); | ||
const getDockStickyTarget = (element) => element.shadowRoot.querySelector('.dock-sticky-target'); | ||
@@ -65,72 +71,67 @@ const updateDockTargetState = (element) => { | ||
const animateFromTo = (element, { | ||
const moveFromTo = (element, { | ||
from, to, animate = false, aspectRatio, | ||
initialAspectRatio, downscale = false, | ||
transitionDuration, | ||
} = {}) => new Promise((resolve) => { | ||
window.requestAnimationFrame(() => { | ||
const fromRect = from.getBoundingClientRect(); | ||
const toRect = to.getBoundingClientRect(); | ||
const visualViewport = getVisualViewport(); | ||
const fromRect = from.getBoundingClientRect(); | ||
const toRect = to.getBoundingClientRect(); | ||
const visualViewport = getVisualViewport(); | ||
const width = fromRect.width === 0 ? NON_VISIBLE_WIDTH : fromRect.width; | ||
const height = fromRect.height === 0 | ||
? (NON_VISIBLE_WIDTH / initialAspectRatio) | ||
: fromRect.height; | ||
const toHeight = width / aspectRatio; | ||
const width = fromRect.width === 0 ? NON_VISIBLE_WIDTH : fromRect.width; | ||
const height = fromRect.height === 0 | ||
? (NON_VISIBLE_WIDTH / initialAspectRatio) | ||
: fromRect.height; | ||
const toHeight = width / aspectRatio; | ||
element.style.position = 'fixed'; | ||
element.style.width = `${width}px`; | ||
element.style.height = `${height}px`; | ||
if (navigator.userAgent.indexOf('Safari') !== -1 && navigator.userAgent.indexOf('Chrome') === -1) { | ||
// somehow safari animates to the wrong position initially | ||
// and then snaps into place when the aspect-ratio is not stable | ||
element.style.minHeight = `${toHeight}px`; | ||
} | ||
element.style.position = 'fixed'; | ||
element.style.width = `${width}px`; | ||
element.style.height = `${height}px`; | ||
element.style.transform = 'scale(1)'; | ||
element.style.top = `${fromRect.top + visualViewport.offsetTop}px`; | ||
element.style.left = `${fromRect.left + visualViewport.offsetLeft}px`; | ||
// avoid CLS further | ||
element.style.display = 'grid'; | ||
element.style.top = `${fromRect.top + visualViewport.offsetTop}px`; | ||
element.style.left = `${fromRect.left + visualViewport.offsetLeft}px`; | ||
element.style.transform = 'scale(1.001)'; | ||
element.style.overflow = 'hidden'; | ||
const deltaX = toRect.left - fromRect.left; | ||
const deltaY = toRect.top - fromRect.top; | ||
const deltaScale = toRect.width / width; | ||
const deltaX = toRect.left - fromRect.left; | ||
const deltaY = toRect.top - fromRect.top; | ||
const deltaScale = toRect.width / width; | ||
const moveDialog = () => { | ||
element.style.height = `${toHeight}px`; | ||
element.style.transform = `translate(${(deltaX / width) * 100}%, ${(deltaY / toHeight) * 100}%) scale(${deltaScale})`; | ||
element.style.transitionProperty = 'transform, opacity, height'; | ||
element.style.transformOrigin = 'top left'; | ||
element.style.transitionTimingFunction = 'ease-out'; | ||
if (!downscale) { | ||
element.firstElementChild.style.width = `${toRect.width}px`; | ||
element.firstElementChild.style.transform = `scale(${1 / deltaScale})`; | ||
element.firstElementChild.style.transitionProperty = 'width'; | ||
element.firstElementChild.style.transformOrigin = 'top left'; | ||
element.firstElementChild.style.transitionTimingFunction = 'ease-out'; | ||
} | ||
if (animate) { | ||
element.style.transitionDuration = `${DEFAULT_TRANSITION_DURATION}ms`; | ||
element.firstElementChild.style.transitionDuration = `${DEFAULT_TRANSITION_DURATION}ms`; | ||
} else { | ||
element.style.transitionDuration = null; | ||
element.firstElementChild.style.transitionDuration = null; | ||
element.firstElementChild.style.transitionDelay = null; | ||
} | ||
}; | ||
if (!animate) { | ||
moveDialog(); | ||
resolve({ | ||
scale: downscale ? deltaScale : 1, | ||
}); | ||
return; | ||
const moveDialog = () => { | ||
// avoid CLS (see setting to "display: grid" before requestAnimationFrame) | ||
element.style.display = null; | ||
element.style.height = `${toHeight}px`; | ||
element.style.transform = `translate(${(deltaX / width) * 100}%, ${(deltaY / toHeight) * 100}%) scale(${deltaScale})`; | ||
element.style.transitionProperty = 'transform'; | ||
element.style.transformOrigin = 'top left'; | ||
element.style.transitionTimingFunction = 'ease-out'; | ||
if (animate) { | ||
element.style.transitionDuration = `${transitionDuration}ms`; | ||
} else { | ||
element.style.transitionDuration = null; | ||
} | ||
if (!downscale) { | ||
// avoid as best as possible that the contained element is shortly scaled too large | ||
// somehow width+scale is not applied at the same time when the motion is too fast | ||
element.firstElementChild.style.transitionDuration = '0s'; | ||
element.firstElementChild.style.transitionProperty = 'transform'; | ||
element.firstElementChild.style.width = `${toRect.width}px`; | ||
element.firstElementChild.style.transform = `scale(${1 / deltaScale})`; | ||
element.firstElementChild.style.transformOrigin = 'top left'; | ||
} | ||
}; | ||
window.requestAnimationFrame(() => { | ||
moveDialog(); | ||
resolve({ | ||
scale: downscale ? deltaScale : 1, | ||
}); | ||
if (!animate) { | ||
moveDialog(); | ||
resolve({ | ||
scale: downscale ? deltaScale : 1, | ||
}); | ||
return; | ||
} | ||
window.requestAnimationFrame(() => { | ||
moveDialog(); | ||
resolve({ | ||
scale: downscale ? deltaScale : 1, | ||
}); | ||
}); | ||
@@ -206,2 +207,15 @@ }); | ||
* feature as amp-video-docking but without using AMP. | ||
* | ||
* @attr {string} mode - Can take the values "hidden", "inline", "dock" or "lightbox". | ||
* @attr {string} aspect-ratio - The aspect-ratio for the inline element. Default is 16:9 | ||
* @attr {string} dock-target - A dom-element with position:fixed where mode=dock should animate to | ||
* @attr {string} dock-target-inset - Defines the position of the dock using inset | ||
* @attr {string} dock-aspect-ratio - The aspect-ratio when the element is mode=dock | ||
* @attr {string} dock-mode - When set to "sticky" it behaves similar to "position: sticky" in CSS | ||
* (with a max width of 400px). If undefined docks the content to a corner. | ||
* @attr {string} dock-sticky-target-top - The top distance for dock-mode=sticky in pixels | ||
* (defaults to 0) | ||
* @attr {string} dock-sticky-aspect-ratio - The aspect-ratio when the element is docked | ||
* for dock-mode=sticky | ||
* @attr {string} dock-downscale - Do you want to scale the element when mode=dock | ||
*/ | ||
@@ -224,5 +238,10 @@ class GlomexDialogElement extends window.HTMLElement { | ||
.dock-sticky-target { | ||
position: fixed; | ||
pointer-events: none; | ||
visibility: hidden; | ||
} | ||
.aspect-ratio-box { | ||
height: 0; | ||
overflow: hidden; | ||
} | ||
@@ -263,5 +282,6 @@ | ||
top: 0; | ||
right: 0; | ||
bottom: 0; | ||
left: 0; | ||
width: 100%; | ||
height: 100%; | ||
max-width: 100%; | ||
will-change: transform, transition, width, height, top, left, opacity; | ||
@@ -271,4 +291,5 @@ } | ||
.dialog-inverse-scale-element { | ||
will-change: transform, transition, width, height, top, left, opacity; | ||
transform-origin: top left; | ||
width: 100%; | ||
max-width: 100%; | ||
will-change: transition, transform, width; | ||
} | ||
@@ -300,7 +321,7 @@ | ||
:host([mode=dock]) .dialog-content { | ||
contain: strict; | ||
position: absolute; | ||
} | ||
:host([alternative-dock-target]) .drag-handle { | ||
:host([alternative-dock-target]) .drag-handle, | ||
:host([dock-mode=sticky]) .drag-handle { | ||
display: none; | ||
@@ -406,2 +427,5 @@ } | ||
</div> | ||
<div class="dock-sticky-target"> | ||
<div class="aspect-ratio-box"></div> | ||
</div> | ||
<div class="dialog-content" part="dialog-content"> | ||
@@ -418,3 +442,6 @@ <div class="dialog-inverse-scale-element"> | ||
const dockTarget = this.shadowRoot.querySelector('.dock-target'); | ||
Object.assign(dockTarget.style, toPositions(DEFAULT_DOCK_TARGET_INSET)); | ||
const positions = toPositions(DEFAULT_DOCK_TARGET_INSET); | ||
const dockStickyTarget = this.shadowRoot.querySelector('.dock-sticky-target'); | ||
dockStickyTarget.style.top = positions.top; | ||
Object.assign(dockTarget.style, positions); | ||
@@ -446,2 +473,6 @@ this._wasInHiddenMode = false; | ||
])); | ||
updateDockStickyAspectRatio(this, getAspectRatioFromStrings([ | ||
this.getAttribute('dock-sticky-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
])); | ||
@@ -473,2 +504,3 @@ if (!this.hasAttribute('mode')) { | ||
}; | ||
onResize(); | ||
window.addEventListener('resize', onResize); | ||
@@ -518,2 +550,5 @@ this._disconnectResize = () => { | ||
'dock-aspect-ratio', | ||
'dock-mode', | ||
'dock-sticky-target-top', | ||
'dock-sticky-aspect-ratio', | ||
'dock-downscale', | ||
@@ -526,4 +561,18 @@ ]; | ||
const placeholder = this.shadowRoot.querySelector('.placeholder'); | ||
const dockMode = this.getAttribute('dock-mode'); | ||
const transitionDuration = DEFAULT_TRANSITION_DURATION; | ||
const aspectRatios = [ | ||
dockMode === 'sticky' | ||
? this.getAttribute('dock-sticky-aspect-ratio') | ||
: this.getAttribute('dock-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
]; | ||
const moveTo = dockMode === 'sticky' | ||
? getDockStickyTarget(this) | ||
: getAlternativeDockTarget(this) || getDefaultDockTarget(this); | ||
if (name === 'mode') { | ||
if (this._wasInHiddenMode && (newValue === 'lightbox' || newValue === 'dock')) { | ||
if (this._wasInHiddenMode && ( | ||
newValue === 'lightbox' | ||
|| newValue === 'dock' | ||
)) { | ||
placeholder.style.display = 'none'; | ||
@@ -538,8 +587,7 @@ } else { | ||
if (newValue === 'dock') { | ||
this.parentElement.removeAttribute('modal'); | ||
dialogContent.style.zIndex = DOCK_Z_INDEX; | ||
updateDockTargetState(this); | ||
animateFromTo(dialogContent, { | ||
moveFromTo(dialogContent, { | ||
from: placeholder, | ||
to: getAlternativeDockTarget(this) || getDefaultDockTarget(this), | ||
to: moveTo, | ||
animate: !this._wasInHiddenMode, | ||
@@ -549,7 +597,5 @@ initialAspectRatio: getAspectRatioFromStrings([ | ||
]), | ||
aspectRatio: getAspectRatioFromStrings([ | ||
this.getAttribute('dock-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
]), | ||
aspectRatio: getAspectRatioFromStrings(aspectRatios), | ||
downscale: this.getAttribute('dock-downscale'), | ||
transitionDuration, | ||
}).then(({ scale }) => { | ||
@@ -568,4 +614,7 @@ if (this.getAttribute('dock-downscale')) { | ||
const goToInline = () => { | ||
// somehow this avoids CLS when switching between | ||
// position "fixed" => "absolute" | ||
dialogContent.style.display = 'grid'; | ||
dialogContent.style.position = 'absolute'; | ||
dialogContent.style.transform = 'scale(1.001)'; | ||
dialogContent.style.transform = 'scale(1)'; | ||
dialogContent.firstElementChild.style.transform = null; | ||
@@ -576,4 +625,4 @@ dialogContent.firstElementChild.style.width = null; | ||
if (!this._wasInHiddenMode && oldValue === 'dock') { | ||
dialogContent.style.transitionDuration = `${DEFAULT_TRANSITION_DURATION}ms`; | ||
dialogContent.firstElementChild.style.transitionDuration = `${DEFAULT_TRANSITION_DURATION}ms`; | ||
dialogContent.style.transitionDuration = `${transitionDuration}ms`; | ||
dialogContent.firstElementChild.style.transitionDuration = null; | ||
dialogContent.style.transitionTimingFunction = 'ease-out'; | ||
@@ -586,3 +635,3 @@ } | ||
} | ||
}, DEFAULT_TRANSITION_DURATION); | ||
}, transitionDuration); | ||
}; | ||
@@ -592,5 +641,5 @@ if (oldValue === 'dock') { | ||
// so that new position with scroll gets calculated | ||
animateFromTo(dialogContent, { | ||
moveFromTo(dialogContent, { | ||
from: placeholder, | ||
to: getAlternativeDockTarget(this) || getDefaultDockTarget(this), | ||
to: moveTo, | ||
animate: false, | ||
@@ -600,7 +649,5 @@ initialAspectRatio: getAspectRatioFromStrings([ | ||
]), | ||
aspectRatio: getAspectRatioFromStrings([ | ||
this.getAttribute('dock-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
]), | ||
aspectRatio: getAspectRatioFromStrings(aspectRatios), | ||
downscale: this.getAttribute('dock-downscale'), | ||
transitionDuration, | ||
}).then(() => { | ||
@@ -662,5 +709,6 @@ window.requestAnimationFrame(() => { | ||
if (name === 'dock-target-inset') { | ||
const positions = toPositions(newValue) || toPositions(DEFAULT_DOCK_TARGET_INSET); | ||
Object.assign( | ||
this.shadowRoot.querySelector('.dock-target').style, | ||
toPositions(newValue) || toPositions(DEFAULT_DOCK_TARGET_INSET), | ||
positions, | ||
); | ||
@@ -670,2 +718,12 @@ this.refreshDockDialog(); | ||
if (name === 'dock-sticky-target-top') { | ||
this.shadowRoot.querySelector('.dock-sticky-target') | ||
.style.top = `${newValue || 0}px`; | ||
this.refreshDockDialog(); | ||
} | ||
if (name === 'dock-mode') { | ||
this.refreshDockDialog(); | ||
} | ||
if (name === 'aspect-ratio') { | ||
@@ -683,2 +741,9 @@ updatePlaceholderAspectRatio(this, getAspectRatioFromStrings([ | ||
} | ||
if (name === 'dock-sticky-aspect-ratio') { | ||
updateDockStickyAspectRatio(this, getAspectRatioFromStrings([ | ||
this.getAttribute('dock-sticky-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
])); | ||
} | ||
if (name === 'dock-downscale' && newValue) { | ||
@@ -700,7 +765,29 @@ if (newValue) { | ||
const dialogContent = this.shadowRoot.querySelector('.dialog-content'); | ||
if (this.getAttribute('mode') === 'dock') { | ||
const dockStickyTarget = this.shadowRoot.querySelector('.dock-sticky-target'); | ||
const clientRect = this.getBoundingClientRect(); | ||
const mode = this.getAttribute('mode'); | ||
const dockMode = this.getAttribute('dock-mode'); | ||
const aspectRatios = [ | ||
dockMode === 'sticky' | ||
? this.getAttribute('dock-sticky-aspect-ratio') | ||
: this.getAttribute('dock-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
]; | ||
const moveTo = dockMode === 'sticky' | ||
? getDockStickyTarget(this) | ||
: getAlternativeDockTarget(this) || getDefaultDockTarget(this); | ||
let stickyWidth = clientRect.width; | ||
if (stickyWidth >= MAX_DOCK_WIDTH) { | ||
stickyWidth = MAX_DOCK_WIDTH; | ||
} | ||
dockStickyTarget.style.width = `${stickyWidth}px`; | ||
dockStickyTarget.style.height = `${clientRect.height * (stickyWidth / clientRect.width)}px`; | ||
if (mode === 'dock') { | ||
updateDockTargetState(this); | ||
animateFromTo(dialogContent, { | ||
moveFromTo(dialogContent, { | ||
from: this.shadowRoot.querySelector('.placeholder'), | ||
to: getAlternativeDockTarget(this) || getDefaultDockTarget(this), | ||
to: moveTo, | ||
animate: false, | ||
@@ -710,7 +797,5 @@ initialAspectRatio: getAspectRatioFromStrings([ | ||
]), | ||
aspectRatio: getAspectRatioFromStrings([ | ||
this.getAttribute('dock-aspect-ratio'), | ||
this.getAttribute('aspect-ratio'), | ||
]), | ||
aspectRatio: getAspectRatioFromStrings(aspectRatios), | ||
downscale: this.getAttribute('dock-downscale'), | ||
transitionDuration: 0, | ||
}).then(({ scale }) => { | ||
@@ -717,0 +802,0 @@ if (this.getAttribute('dock-downscale')) { |
{ | ||
"name": "@glomex/glomex-dialog", | ||
"version": "1.12.6", | ||
"version": "1.13.0", | ||
"description": "A dialog web component that allows docking a video player or putting it in a lightbox", | ||
@@ -5,0 +5,0 @@ "type": "module", |
@@ -520,5 +520,5 @@ # glomex-dialog | ||
### With IntersectionObserver and custom position | ||
### With IntersectionObserver and dock-mode = sticky | ||
This example auto docks the video element when the player gets scrolled out of view. | ||
This example auto docks the video element when the player gets scrolled out of view (similar to `position: sticky`). | ||
@@ -531,5 +531,9 @@ ```js preact | ||
const dialog = useRef(); | ||
const dockMode = useRef(); | ||
const onButtonClick = () => { | ||
dialog.current.setAttribute('mode', select.current.value); | ||
}; | ||
const onDockModeChange = () => { | ||
dialog.current.setAttribute('dock-mode', dockMode.current.value); | ||
} | ||
@@ -551,3 +555,5 @@ let onceVisible = false; | ||
} | ||
if (entries[0].intersectionRatio < 1 && glomexDialog.getAttribute('mode') !== 'dock') { | ||
if (entries[0].intersectionRatio < 1 && ( | ||
currentMode !== 'dock' | ||
)) { | ||
glomexDialog.setAttribute('mode', 'dock'); | ||
@@ -558,3 +564,4 @@ } else if (entries[0].intersectionRatio === 1) { | ||
}, { | ||
threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1] | ||
threshold: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1], | ||
rootMargin: '-48px 0px 0px 0px' | ||
}); | ||
@@ -573,11 +580,22 @@ if (dialog.current) { | ||
<p> | ||
<select ref=${select}> | ||
<option value="hidden" selected>hidden</option> | ||
<option value="inline">inline</option> | ||
<option value="dock">dock</option> | ||
<option value="lightbox">lightbox</option> | ||
</select> | ||
<label> | ||
Mode | ||
<select style="margin-left:1em;" ref=${select}> | ||
<option value="hidden">hidden</option> | ||
<option value="inline">inline</option> | ||
<option value="dock" selected>dock</option> | ||
<option value="sticky">sticky</option> | ||
<option value="lightbox">lightbox</option> | ||
</select> | ||
</label> | ||
<label style="margin-left:1em;" > | ||
Dock-Mode | ||
<select onChange="${onDockModeChange}" style="margin-left:1em;" ref=${dockMode}> | ||
<option value="">none</option> | ||
<option value="sticky" selected>sticky</option> | ||
</select> | ||
</label> | ||
<button onClick=${onButtonClick} class="button">Switch Dialog Mode</button> | ||
</p> | ||
<glomex-dialog ref=${dialog} mode="inline" dock-target-inset="50px 10px auto auto"> | ||
<glomex-dialog ref=${dialog} mode="inline" dock-mode="sticky" dock-target-inset="48px 10px auto auto" dock-sticky-target-top="48"> | ||
<div slot="dialog-element"> | ||
@@ -603,3 +621,8 @@ <div style="position: relative;"> | ||
<!-- The intersection-observer-code is custom in the above example --> | ||
<glomex-dialog mode="inline" dock-target-inset="50px 10px auto auto"> | ||
<glomex-dialog | ||
mode="inline" | ||
dock-target-inset="50px 10px auto auto" | ||
dock-mode="sticky" | ||
dock-sticky-target-top="48" | ||
> | ||
<!-- ... --> | ||
@@ -606,0 +629,0 @@ </glomex-dialog> |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
110397
1039
654