implement revealers

This commit is contained in:
Aran-Fey
2024-06-14 21:36:16 +02:00
committed by Jakob Pinterits
parent e0cdf216ff
commit 5806497131
2 changed files with 53 additions and 98 deletions

View File

@@ -18,21 +18,11 @@ export type RevealerState = ComponentState & {
export class RevealerComponent extends ComponentBase {
state: Required<RevealerState>;
// Tracks the progress of the animation. Zero means fully collapsed, one
// means fully expanded.
private animationIsRunning: boolean = false;
private lastAnimationTick: number;
private openFractionBeforeEase: number = -1; // Initialized on first state update
private headerElement: HTMLElement;
private labelElement: HTMLElement;
private arrowElement: HTMLElement;
private contentInnerElement: HTMLElement;
private contentOuterElement: HTMLElement;
private headerScale: number;
private labelWidth: number;
private labelHeight: number;
private contentInnerElement: HTMLElement;
private rippleInstance: RippleEffect;
@@ -64,14 +54,14 @@ export class RevealerComponent extends ComponentBase {
'.rio-revealer-arrow'
) as HTMLElement;
this.contentInnerElement = element.querySelector(
'.rio-revealer-content-inner'
) as HTMLElement;
this.contentOuterElement = element.querySelector(
'.rio-revealer-content-outer'
) as HTMLElement;
this.contentInnerElement = element.querySelector(
'.rio-revealer-content-inner'
) as HTMLElement;
// Initialize them
applyIcon(this.arrowElement, 'material/expand-more', 'currentColor');
@@ -85,22 +75,9 @@ export class RevealerComponent extends ComponentBase {
this.rippleInstance.trigger(event);
// Toggle the open state
this.state.is_open = !this.state.is_open;
// Notify the backend
this.setStateAndNotifyBackend({
is_open: this.state.is_open,
is_open: !this.state.is_open,
});
// Update the CSS
if (this.state.is_open) {
element.classList.add('rio-revealer-open');
} else {
element.classList.remove('rio-revealer-open');
}
// Update the UI
this.startAnimationIfNotRunning();
};
// Color change on hover/leave
@@ -145,24 +122,25 @@ export class RevealerComponent extends ComponentBase {
);
// The text style defines the overall scale of the header
let headerScale: number;
if (deltaState.header_style === 'heading1') {
this.headerScale = 2;
headerScale = 2;
} else if (deltaState.header_style === 'heading2') {
this.headerScale = 1.5;
headerScale = 1.5;
} else if (deltaState.header_style === 'heading3') {
this.headerScale = 1.2;
headerScale = 1.2;
} else if (deltaState.header_style === 'text') {
this.headerScale = 1;
headerScale = 1;
} else {
this.headerScale = deltaState.header_style.fontSize;
headerScale = deltaState.header_style.fontSize;
}
// Adapt the header's padding
let cssPadding = `${HEADER_PADDING * this.headerScale}rem`;
let cssPadding = `${HEADER_PADDING * headerScale}rem`;
this.headerElement.style.padding = cssPadding;
// Make the arrow match
let arrowSize = this.headerScale * 1.0;
let arrowSize = headerScale * 1.0;
this.arrowElement.style.width = `${arrowSize}rem`;
this.arrowElement.style.height = `${arrowSize}rem`;
this.arrowElement.style.color = this.labelElement.style.color;
@@ -170,61 +148,46 @@ export class RevealerComponent extends ComponentBase {
// Expand / collapse
if (deltaState.is_open !== undefined) {
// If this is the first state update, initialize the open fraction
if (this.openFractionBeforeEase === -1) {
this.openFractionBeforeEase = deltaState.is_open ? 1 : 0;
}
// Otherwise animate
else {
this.state.is_open = deltaState.is_open;
this.startAnimationIfNotRunning();
}
// Update the CSS
if (this.state.is_open) {
this.element.classList.add('rio-revealer-open');
if (deltaState.is_open) {
this.animateOpen();
} else {
this.element.classList.remove('rio-revealer-open');
this.animateClose();
}
}
}
/// If the animation is not yet running, start it. Does nothing otherwise.
/// This does not modify the state in any way.
startAnimationIfNotRunning() {
// If the animation is already running, do nothing.
if (this.animationIsRunning) {
private animateOpen(): void {
// Do nothing if already expanded
if (this.element.classList.contains('rio-revealer-open')) {
return;
}
// Start the animation
this.animationIsRunning = true;
this.lastAnimationTick = Date.now();
requestAnimationFrame(() => this.animationWorker());
// Update the CSS to trigger the expand animation
this.element.classList.add('rio-revealer-open');
// The components may currently be in flux due to a pending re-layout. If that
// is the case, reading the `scrollHeight` would lead to an incorrect value.
// Wait for the resize to finish before fetching it.
requestAnimationFrame(() => {
let contentHeight = this.contentInnerElement.scrollHeight;
let selfHeight = this.element.scrollHeight;
let headerHeight = this.headerElement.scrollHeight;
let targetHeight = Math.max(
contentHeight,
selfHeight - headerHeight
);
this.contentOuterElement.style.maxHeight = `${targetHeight}px`;
});
}
animationWorker() {
// Update state
let now = Date.now();
let timePassed = now - this.lastAnimationTick;
this.lastAnimationTick = now;
let direction = this.state.is_open ? 1 : -1;
this.openFractionBeforeEase =
this.openFractionBeforeEase + (direction * timePassed) / 200;
// Clamp the open fraction
this.openFractionBeforeEase = Math.max(
0,
Math.min(1, this.openFractionBeforeEase)
);
// If the animation is not yet finished, continue it.
let target = this.state.is_open ? 1 : 0;
if (this.openFractionBeforeEase === target) {
this.animationIsRunning = false;
} else {
requestAnimationFrame(() => this.animationWorker());
private animateClose(): void {
// Do nothing if already collapsed
if (!this.element.classList.contains('rio-revealer-open')) {
return;
}
this.element.classList.remove('rio-revealer-open');
this.contentOuterElement.style.maxHeight = '0';
}
}

View File

@@ -968,8 +968,6 @@ textarea:not(:placeholder-shown) ~ .rio-input-box-label,
border-radius: var(--rio-global-corner-radius-small);
transition: background-color 0.15s ease-out;
overflow: hidden; // Needed by the ripple effect
}
.rio-revealer-header {
@@ -993,22 +991,18 @@ textarea:not(:placeholder-shown) ~ .rio-input-box-label,
transition: transform 0.25s ease-in-out;
}
.rio-revealer-open .rio-revealer-arrow {
.rio-revealer-open > * > .rio-revealer-arrow {
transform: rotate(0deg);
}
.rio-revealer-content-outer {
@include single-container();
overflow: hidden;
transition: height 0.25s ease-in-out;
height: 0;
}
.rio-revealer-open .rio-revealer-content-outer {
height: initial;
flex-grow: 1;
max-height: 0;
transition: max-height 0.25s ease-in-out;
}
.rio-revealer-content-inner {
@@ -1022,7 +1016,7 @@ textarea:not(:placeholder-shown) ~ .rio-input-box-label,
transform 0.35s ease;
}
.rio-revealer-open .rio-revealer-content-inner {
.rio-revealer-open > * > .rio-revealer-content-inner {
opacity: 1;
transform: translateY(0%);
}
@@ -1845,11 +1839,7 @@ textarea:not(:placeholder-shown) ~ .rio-input-box-label,
}
.rio-drawer-content-inner {
order: 1;
}
.rio-drawer-content-inner > * {
position: relative !important;
@include single-container();
}
.rio-drawer-knob {
@@ -2807,6 +2797,8 @@ textarea:not(:placeholder-shown) ~ .rio-input-box-label,
width: 100%;
height: 100%;
overflow: hidden;
@include single-container();
}