Skip to content

Commit

Permalink
fix(bottom-sheet): resolve an issue that is causing the sheet to not …
Browse files Browse the repository at this point in the history
…correctly set its height when the sheet is open in the first render
  • Loading branch information
mimshins committed Dec 30, 2024
1 parent 6e7ec85 commit e0892b3
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 53 deletions.
151 changes: 101 additions & 50 deletions packages/web-components/src/bottom-sheet/bottom-sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import {
SnappedEvent,
} from "./events";
import { dismiss } from "./icons";
import type { SnapToCallbackArgument, StatusEnum } from "./types";
import type { MetaData, SnapToCallbackArgument, StatusEnum } from "./types";

export class BottomSheet extends LitElement {
public static override readonly shadowRootOptions = {
Expand Down Expand Up @@ -206,21 +206,13 @@ export class BottomSheet extends LitElement {
}
}

runAfterRepaint(() => {
const actionBarSlot = getRenderRootSlot(
this.renderRoot,
Slots.ACTION_BAR,
);

const bodySlot = getRenderRootSlot(this.renderRoot, Slots.BODY);
const headerSlot = getRenderRootSlot(this.renderRoot, Slots.HEADER);
const actionBarSlot = getRenderRootSlot(this.renderRoot, Slots.ACTION_BAR);
const bodySlot = getRenderRootSlot(this.renderRoot, Slots.BODY);
const headerSlot = getRenderRootSlot(this.renderRoot, Slots.HEADER);

this._hasActionBarSlot =
(actionBarSlot?.assignedNodes() ?? []).length > 0;

this._hasBodySlot = (bodySlot?.assignedNodes() ?? []).length > 0;
this._hasHeaderSlot = (headerSlot?.assignedNodes() ?? []).length > 0;
});
this._hasActionBarSlot = (actionBarSlot?.assignedNodes() ?? []).length > 0;
this._hasBodySlot = (bodySlot?.assignedNodes() ?? []).length > 0;
this._hasHeaderSlot = (headerSlot?.assignedNodes() ?? []).length > 0;
}

public override connectedCallback(): void {
Expand Down Expand Up @@ -349,6 +341,21 @@ export class BottomSheet extends LitElement {
return minSnap;
}

/**
* Returns the metadata of the bottom sheet.
*/
public get metaData(): MetaData {
return {
actionBarHeight: this._actionBarHeight,
bodyHeight: this._bodyHeight,
headerHeight: this._headerHeight,
height: this._height,
snapPoints: this.snapPoints,
totalHeight:
this._actionBarHeight + this._bodyHeight + this._headerHeight,
};
}

/**
* Gets the default snap points for the bottom sheet.
*
Expand Down Expand Up @@ -401,9 +408,22 @@ export class BottomSheet extends LitElement {
public set open(openState: boolean) {
if (openState === this._open) return;

this._open = openState;
const initialOpenState = !this.hasUpdated && openState && !this._open;

void this._toggleOpenState(openState);
const toggle = () => {
this._open = openState;

void this._toggleOpenState(openState);
};

if (initialOpenState) {
runAfterRepaint(() => {
const prevOpen = this._open;

toggle();
this.requestUpdate("open", prevOpen);
});
} else toggle();
}

public get open() {
Expand Down Expand Up @@ -542,7 +562,7 @@ export class BottomSheet extends LitElement {
}

if (last) {
void this._snapTo(newY);
void this._snapTo(newY, false);

this._isGrabbing = false;

Expand All @@ -558,28 +578,34 @@ export class BottomSheet extends LitElement {
return memo as unknown;
}

private async _snapTo(snapPoint: number) {
private async _snapTo(snapPoint: number, strict: boolean) {
if (!this._root) return Promise.resolve();

const { closestPoint } = [0, ...this.snapPoints].reduce(
(result, currentPoint) => {
const distance = Math.abs(snapPoint - currentPoint);

if (result.minDistance > distance) {
result.minDistance = distance;
result.closestPoint = currentPoint;
}

return result;
},
{
minDistance: Infinity,
closestPoint: NaN,
} as {
minDistance: number;
closestPoint: number;
},
);
let closestPoint: number;

if (!strict) {
const closest = [0, ...this.snapPoints].reduce(
(result, currentPoint) => {
const distance = Math.abs(snapPoint - currentPoint);

if (result.minDistance > distance) {
result.minDistance = distance;
result.closestPoint = currentPoint;
}

return result;
},
{
minDistance: Infinity,
closestPoint: NaN,
} as {
minDistance: number;
closestPoint: number;
},
);

closestPoint = closest.closestPoint;
} else closestPoint = snapPoint;

if (Number.isNaN(closestPoint)) return Promise.resolve();

Expand Down Expand Up @@ -627,6 +653,29 @@ export class BottomSheet extends LitElement {
return Promise.resolve(cleanup());
}

/**
* Strictly snaps to the provided or resolved snap point.
*/
public strictSnapTo(numberOrCallback: number | SnapToCallbackArgument) {
if (isSSR()) return Promise.resolve();

const snapPoint =
typeof numberOrCallback === "number"
? numberOrCallback
: numberOrCallback(this.metaData);

if (!this.open) {
this._open = true;

this.requestUpdate("open", false);

return this._toggleOpenState(true, {
point: snapPoint,
strict: true,
});
} else return this._snapTo(snapPoint, true);
}

/**
* When given a number it'll find the closest snap point,
* so you don't need to know the exact value.
Expand All @@ -638,21 +687,18 @@ export class BottomSheet extends LitElement {
const snapPoint =
typeof numberOrCallback === "number"
? numberOrCallback
: numberOrCallback({
actionBarHeight: this._actionBarHeight,
bodyHeight: this._bodyHeight,
headerHeight: this._headerHeight,
height: this._height,
snapPoints: this.snapPoints,
});
: numberOrCallback(this.metaData);

if (!this.open) {
this._open = true;

this.requestUpdate("open", false);

return this._toggleOpenState(true, snapPoint);
} else return this._snapTo(snapPoint);
return this._toggleOpenState(true, {
point: snapPoint,
strict: false,
});
} else return this._snapTo(snapPoint, false);
}

/**
Expand Down Expand Up @@ -683,14 +729,19 @@ export class BottomSheet extends LitElement {
if (!eventAllowed) this.open = true;
}

private async _toggleOpenState(openState: boolean, snapPoint?: number) {
private async _toggleOpenState(
openState: boolean,
snapOpts?: { point: number; strict: boolean },
) {
// Skip transition if not connected or elements are not assigned
if (!this.isConnected || !this._root || !this._container) {
this._status = openState ? Status.OPENED : Status.CLOSED;

return;
}

const { point = null, strict = false } = snapOpts ?? {};

const handleTransitionEnd = (event: TransitionEvent) => {
if (event.propertyName === "height") {
// Finish the animation when height transition ends
Expand Down Expand Up @@ -726,11 +777,11 @@ export class BottomSheet extends LitElement {
this._status = openState ? Status.OPENING : Status.CLOSING;

const openToSnapPoint =
snapPoint ?? this.snapPoints[0] ?? this.defaultSnapPoints[0];
point ?? this.snapPoints[0] ?? this.defaultSnapPoints[0];

if (openState) {
// It's opening, so we have to snap
await this._snapTo(openToSnapPoint);
await this._snapTo(openToSnapPoint, strict);
} else this._height = 0;

// Wait for the animation to complete
Expand Down
7 changes: 7 additions & 0 deletions packages/web-components/src/bottom-sheet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export * from "./events";
* @fires {HideEvent} hide - Fired when the bottom-sheet is hidden (cancelable).
* @fires {ShowEvent} show - Fired when the bottom-sheet is shown (cancelable).
*
* @member {MetaData} metaData
* @description - Returns the metadata of the bottom sheet.
*
* @member {number[]} snapPoints
* @description - The snap points for bottom sheet to snap to.
* Note that snap points will be sorted sorted, no matter
Expand All @@ -66,6 +69,10 @@ export * from "./events";
* Use the callback method to resolve the snap point.
* @param {number | Function} numberOrCallback
*
* @method strictSnapTo
* @description - Strictly snaps to the provided or resolved snap point.
* @param {number | Function} numberOrCallback
*
* @method show
* @description - Opens the bottom sheet if it is not already open.
* Dispatches a cancelable ShowEvent ("show").
Expand Down
12 changes: 9 additions & 3 deletions packages/web-components/src/bottom-sheet/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { Status } from "./constants";

export type SnapToCallbackArgument = (snapProps: {
export type MetaData = {
/**
* The snap points currently in use.
*/
Expand All @@ -18,9 +18,15 @@ export type SnapToCallbackArgument = (snapProps: {
*/
bodyHeight: number;
/**
* The height of the bottom sheet.
* The total height of the bottom sheet (action-bar + header + body).
*/
totalHeight: number;
/**
* The current height of the bottom sheet.
*/
height: number;
}) => number;
};

export type SnapToCallbackArgument = (data: MetaData) => number;

export type StatusEnum = (typeof Status)[keyof typeof Status];

0 comments on commit e0892b3

Please sign in to comment.