Skip to content

Instantly share code, notes, and snippets.

@hunterloftis
Last active November 11, 2021 20:26
Show Gist options
  • Save hunterloftis/07e74dcd5caffe3a78eb3e5814d49f08 to your computer and use it in GitHub Desktop.
Save hunterloftis/07e74dcd5caffe3a78eb3e5814d49f08 to your computer and use it in GitHub Desktop.
import { ReactiveController, LitElement } from 'Lit';
/*
class Component extends LitElement {
bounds = new BoundsControllerBasic(this);
render() {
// use this.bounds.width, this.bounds.height
}
updated(changedProps) {
// no way to know whether or not dimensions were changed in this update;
// the controller reduces component-author flexibility relative to states and properties.
}
}
*/
export class BoundsControllerBasic implements ReactiveController {
public width = 0;
public height = 0;
protected host: LitElement;
protected observer: ResizeObserver;
constructor(host: LitElement) {
this.host = host;
this.observer = new ResizeObserver(() => this.updateDimensions());
host.addController(this);
}
public hostConnected(): void {
this.observer.observe(this.host);
}
public hostDisconnected(): void {
this.observer.unobserve(this.host);
}
protected updateDimensions(): void {
const { width, height } = this.host.getBoundingClientRect();
this.width = width;
this.height = height;
this.host.requestUpdate();
}
}
/*
class Component extends LitElement {
bounds = new BoundsControllerCallback(this, () => this.onResize);
render() {
// use this.bounds.width, this.bounds.height
}
onResize() {
// we know a resize happened
// but we're adding extra lifecycle callbacks alongside built-in Lit stuff
// and it doesn't seem idiomatic.
// In order to re-render we must either set a state/property or call requestUpdate()
}
}
*/
export class BoundsControllerCallback implements ReactiveController {
public width = 0;
public height = 0;
protected host: LitElement;
protected observer: ResizeObserver;
protected callback: Function;
constructor(host: LitElement, callback: Function) {
this.host = host;
this.observer = new ResizeObserver(() => this.updateDimensions());
this.callback = callback ?? host.requestUpdate;
host.addController(this);
}
public hostConnected(): void {
this.observer.observe(this.host);
}
public hostDisconnected(): void {
this.observer.unobserve(this.host);
}
protected updateDimensions(): void {
const { width, height } = this.host.getBoundingClientRect();
this.width = width;
this.height = height;
this.callback();
}
}
/*
class Component extends LitElement {
bounds = new BoundsControllerEvents(this);
constructor() {
super();
this.bounds.addEventListener('resize', () => this.onResize());
}
render() {
// use this.bounds.width, this.bounds.height
}
onResize() {
// we know a resize happened
// but we're adding boilerplate to listen to the resize event
// and it doesn't seem idiomatic.
// In order to re-render we must either set a state/property or call requestUpdate()
}
}
*/
export class BoundsControllerEvents
extends EventTarget
implements ReactiveController {
public width = 0;
public height = 0;
protected host: LitElement;
protected observer: ResizeObserver;
constructor(host: LitElement) {
super();
this.host = host;
this.observer = new ResizeObserver(() => this.updateDimensions());
host.addController(this);
}
public hostConnected(): void {
this.observer.observe(this.host);
}
public hostDisconnected(): void {
this.observer.unobserve(this.host);
}
protected updateDimensions(): void {
const { width, height } = this.host.getBoundingClientRect();
this.width = width;
this.height = height;
this.dispatchEvent(new CustomEvent('resize'));
}
}
/*
class Component extends LitElement {
bounds = new BoundsControllerString(this, 'bounds');
render() {
// use this.bounds.width, this.bounds.height
}
updated(changedProps) {
if (changedProps.has(this.bounds.key)) {
// we can respond specifically to BoundsController changes now,
// in a way that feels aligned with property and state changes.
// but we're pushing responsiblity for choosing a unique key onto the user.
}
}
}
*/
export class BoundsControllerString implements ReactiveController {
public width = 0;
public height = 0;
public key: string;
protected host: LitElement;
protected observer: ResizeObserver;
constructor(host: LitElement, key: string) {
this.host = host;
this.key = key;
this.observer = new ResizeObserver(() => this.updateDimensions());
host.addController(this);
}
public hostConnected(): void {
this.observer.observe(this.host);
}
public hostDisconnected(): void {
this.observer.unobserve(this.host);
}
protected updateDimensions(): void {
const { width, height } = this.host.getBoundingClientRect();
const previous = (({ width, height }) => ({ width, height }))(this);
this.width = width;
this.height = height;
this.host.requestUpdate(this.key, previous);
}
}
/*
class Component extends LitElement {
bounds = new BoundsControllerSymbol(this);
render() {
// use this.bounds.width, this.bounds.height
}
updated(changedProps) {
if (changedProps.has(this.bounds.key)) {
// we can respond specifically to BoundsController changes now,
// in a way that feels aligned with property and state changes.
const prev = changedProps.get(this.bounds.key);
console.log('resized from', prev.width, prev.height, 'to', this.bounds.width, this.bounds.height);
}
}
}
*/
export class BoundsControllerSymbol implements ReactiveController {
public key = Symbol('bounds-key');
public width = 0;
public height = 0;
protected host: LitElement;
protected observer: ResizeObserver;
constructor(host: LitElement) {
this.host = host;
this.observer = new ResizeObserver(() => this.updateDimensions());
host.addController(this);
}
public hostConnected(): void {
this.observer.observe(this.host);
}
public hostDisconnected(): void {
this.observer.unobserve(this.host);
}
protected updateDimensions(): void {
const { width, height } = this.host.getBoundingClientRect();
const previous = (({ width, height }) => ({ width, height }))(this);
this.width = width;
this.height = height;
this.host.requestUpdate(this.key, previous);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment