Skip to content

Commit

Permalink
feat(field,menu): create a surface client rect api for positioning
Browse files Browse the repository at this point in the history
Expose the field container element to allow anchoring directly to it. This is necessary because if there is supporting text below the field, anchoring a menu to the md-field will cause the menu to anchor below the supporting text which would require some manual pixel-offsets to align correctly.

PiperOrigin-RevId: 516636675
  • Loading branch information
material-web-copybara authored and copybara-github committed Mar 14, 2023
1 parent 27762d8 commit 533ae6c
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 6 deletions.
8 changes: 7 additions & 1 deletion field/lib/field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
import {html, LitElement, nothing, PropertyValues, TemplateResult} from 'lit';
import {property, query, state} from 'lit/decorators.js';
import {classMap} from 'lit/directives/class-map.js';
import {SurfacePositionTarget} from '../../menu/lib/surfacePositionController.js';

import {EASING} from '../../motion/animation.js';

/**
* A field component.
*/
export class Field extends LitElement {
export class Field extends LitElement implements SurfacePositionTarget {
@property({type: Boolean}) disabled = false;
@property({type: Boolean}) error = false;
@property({type: Boolean}) focused = false;
Expand All @@ -36,6 +37,7 @@ export class Field extends LitElement {
private labelAnimation?: Animation;
@query('.label.floating') private readonly floatingLabelEl!: HTMLElement|null;
@query('.label.resting') private readonly restingLabelEl!: HTMLElement|null;
@query('.container') private readonly containerEl!: HTMLElement|null;

protected override update(props: PropertyValues<Field>) {
// Client-side property updates
Expand Down Expand Up @@ -226,4 +228,8 @@ export class Field extends LitElement {
{transform: floatTransform, width}, {transform: restTransform, width}
];
}

getSurfacePositionClientRect() {
return this.containerEl!.getBoundingClientRect();
}
}
24 changes: 19 additions & 5 deletions menu/lib/surfacePositionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ import {StyleInfo} from 'lit/directives/style-map.js';
*/
export type Corner = 'END_START'|'END_END'|'START_START'|'START_END';

/**
* An interface that provides a method to customize the rect from which to
* calculate the anchor positioning. Useful for when you want a surface to
* anchor to an element in your shadow DOM rather than the host element.
*/
export interface SurfacePositionTarget extends HTMLElement {
getSurfacePositionClientRect?: () => DOMRect;
}

/**
* The configurable options for the surface position controller.
*/
Expand All @@ -27,11 +36,11 @@ export interface SurfacePositionControllerProperties {
/**
* The HTMLElement reference of the surface to be positioned.
*/
surfaceEl: HTMLElement|null;
surfaceEl: SurfacePositionTarget|null;
/**
* The HTMLElement reference of the anchor to align to.
*/
anchorEl: HTMLElement|null;
anchorEl: SurfacePositionTarget|null;
/**
* Whether or not the calculation should be relative to the top layer rather
* than relative to the parent of the anchor.
Expand Down Expand Up @@ -147,8 +156,12 @@ export class SurfacePositionController implements ReactiveController {
this.host.requestUpdate();
await this.host.updateComplete;

const surfaceRect = surfaceEl.getBoundingClientRect();
const anchorRect = anchorEl.getBoundingClientRect();
const surfaceRect = surfaceEl.getSurfacePositionClientRect ?
surfaceEl.getSurfacePositionClientRect() :
surfaceEl.getBoundingClientRect();
const anchorRect = anchorEl.getSurfacePositionClientRect ?
anchorEl.getSurfacePositionClientRect() :
anchorEl.getBoundingClientRect();
const [surfaceBlock, surfaceInline] =
surfaceCorner.split('_') as Array<'START'|'END'>;
const [anchorBlock, anchorInline] =
Expand All @@ -159,7 +172,8 @@ export class SurfacePositionController implements ReactiveController {
// statements because it _heavily_ cuts down on nesting and readability
const isTopLayer = topLayerRaw ? 1 : 0;
// LTR depends on the direction of the SURFACE not the anchor.
const isLTR = getComputedStyle(surfaceEl).direction === 'ltr' ? 1 : 0;
const isLTR =
getComputedStyle(surfaceEl as HTMLElement).direction === 'ltr' ? 1 : 0;
const isRTL = isLTR ? 0 : 1;
const isSurfaceInlineStart = surfaceInline === 'START' ? 1 : 0;
const isSurfaceInlineEnd = surfaceInline === 'END' ? 1 : 0;
Expand Down

0 comments on commit 533ae6c

Please sign in to comment.