From 4adb6bbf040d1cb503332c8177c235de72be2a46 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Thu, 29 Oct 2020 18:15:48 -0400 Subject: [PATCH] Add experimental resize observer --- docs/_sidebar.md | 1 + docs/components/resize-observer.md | 46 +++++++++++++++++ docs/getting-started/changelog.md | 3 +- src/components.d.ts | 17 +++++++ .../resize-observer/resize-observer.scss | 5 ++ .../resize-observer/resize-observer.tsx | 50 +++++++++++++++++++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 docs/components/resize-observer.md create mode 100644 src/components/resize-observer/resize-observer.scss create mode 100644 src/components/resize-observer/resize-observer.tsx diff --git a/docs/_sidebar.md b/docs/_sidebar.md index da14ee03..a4cfb1b1 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -51,6 +51,7 @@ - [Animation](/components/animation.md) - [Format Bytes](/components/format-bytes.md) - [Include](/components/include.md) + - [Resize Observer](/components/resize-observer.md) - [Theme](/components/theme.md) - Design Tokens diff --git a/docs/components/resize-observer.md b/docs/components/resize-observer.md new file mode 100644 index 00000000..738df98d --- /dev/null +++ b/docs/components/resize-observer.md @@ -0,0 +1,46 @@ +# Resize Observer + +[component-header:sl-resize-observer] + +Resize observers offer a thin, declarative interface to the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). + +The resize observer will report changes to the dimensions of the elements it wraps through the `sl-resize` event. When emitted, a collection of [`ResizeObserverEntry`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserverEntry) objects will be attached to `event.detail`, containing the target element and information about its dimensions. + +```html preview +<div class="resize-observer-overview"> + <sl-resize-observer> + <div> + Resize me and watch the console + </div> + </sl-resize-observer> +</div> + +<script> + const container = document.querySelector('.resize-observer-overview'); + const resizeObserver = container.querySelector('sl-resize-observer'); + + resizeObserver.addEventListener('sl-resize', event => { + console.log(event); + }); +</script> + +<style> + .resize-observer-overview div { + display: flex; + width: 12rem; + min-height: 12rem; + min-width: 12rem; + max-width: 100%; + max-height: 50vh; + border: solid 2px var(--sl-input-border-color); + resize: both; + overflow: auto; + align-items: center; + justify-content: center; + text-align: center; + padding: 1rem; + } +</style> +``` + +[component-metadata:sl-resize-observer] diff --git a/docs/getting-started/changelog.md b/docs/getting-started/changelog.md index 47bcfd8a..981975bb 100644 --- a/docs/getting-started/changelog.md +++ b/docs/getting-started/changelog.md @@ -8,7 +8,8 @@ _During the beta period, these restrictions may be relaxed in the event of a mis ## Next -- Added `sl-theme` utility and updated theming documentation +- Added experimental `sl-resize-observer` utility +- Added experimental `sl-theme` utility and updated theming documentation - Fixed a bug where `sl-menu-item` wouldn't render properly in the dark theme - Improved placeholder contrast in dark theme - Updated Boostrap Icons to 1.1.0 diff --git a/src/components.d.ts b/src/components.d.ts index 4dff527d..632f36c4 100644 --- a/src/components.d.ts +++ b/src/components.d.ts @@ -851,6 +851,8 @@ export namespace Components { */ "value": number; } + interface SlResizeObserver { + } interface SlResponsiveEmbed { /** * The aspect ratio of the embedded media in the format of `width:height`, e.g. `16:9`, `4:3`, or `1:1`. Ratios not in this format will be ignored. @@ -1376,6 +1378,12 @@ declare global { prototype: HTMLSlRatingElement; new (): HTMLSlRatingElement; }; + interface HTMLSlResizeObserverElement extends Components.SlResizeObserver, HTMLStencilElement { + } + var HTMLSlResizeObserverElement: { + prototype: HTMLSlResizeObserverElement; + new (): HTMLSlResizeObserverElement; + }; interface HTMLSlResponsiveEmbedElement extends Components.SlResponsiveEmbed, HTMLStencilElement { } var HTMLSlResponsiveEmbedElement: { @@ -1479,6 +1487,7 @@ declare global { "sl-radio": HTMLSlRadioElement; "sl-range": HTMLSlRangeElement; "sl-rating": HTMLSlRatingElement; + "sl-resize-observer": HTMLSlResizeObserverElement; "sl-responsive-embed": HTMLSlResponsiveEmbedElement; "sl-select": HTMLSlSelectElement; "sl-skeleton": HTMLSlSkeletonElement; @@ -2365,6 +2374,12 @@ declare namespace LocalJSX { */ "value"?: number; } + interface SlResizeObserver { + /** + * Emitted when the element is resized. + */ + "onSl-resize"?: (event: CustomEvent<ResizeObserverEntry[]>) => void; + } interface SlResponsiveEmbed { /** * The aspect ratio of the embedded media in the format of `width:height`, e.g. `16:9`, `4:3`, or `1:1`. Ratios not in this format will be ignored. @@ -2735,6 +2750,7 @@ declare namespace LocalJSX { "sl-radio": SlRadio; "sl-range": SlRange; "sl-rating": SlRating; + "sl-resize-observer": SlResizeObserver; "sl-responsive-embed": SlResponsiveEmbed; "sl-select": SlSelect; "sl-skeleton": SlSkeleton; @@ -2783,6 +2799,7 @@ declare module "@stencil/core" { "sl-radio": LocalJSX.SlRadio & JSXBase.HTMLAttributes<HTMLSlRadioElement>; "sl-range": LocalJSX.SlRange & JSXBase.HTMLAttributes<HTMLSlRangeElement>; "sl-rating": LocalJSX.SlRating & JSXBase.HTMLAttributes<HTMLSlRatingElement>; + "sl-resize-observer": LocalJSX.SlResizeObserver & JSXBase.HTMLAttributes<HTMLSlResizeObserverElement>; "sl-responsive-embed": LocalJSX.SlResponsiveEmbed & JSXBase.HTMLAttributes<HTMLSlResponsiveEmbedElement>; "sl-select": LocalJSX.SlSelect & JSXBase.HTMLAttributes<HTMLSlSelectElement>; "sl-skeleton": LocalJSX.SlSkeleton & JSXBase.HTMLAttributes<HTMLSlSkeletonElement>; diff --git a/src/components/resize-observer/resize-observer.scss b/src/components/resize-observer/resize-observer.scss new file mode 100644 index 00000000..03be999b --- /dev/null +++ b/src/components/resize-observer/resize-observer.scss @@ -0,0 +1,5 @@ +@import 'component'; + +:host { + display: contents; +} diff --git a/src/components/resize-observer/resize-observer.tsx b/src/components/resize-observer/resize-observer.tsx new file mode 100644 index 00000000..1c70d916 --- /dev/null +++ b/src/components/resize-observer/resize-observer.tsx @@ -0,0 +1,50 @@ +import { Component, Element, Event, EventEmitter, h } from '@stencil/core'; +import ResizeObserver from 'resize-observer-polyfill'; + +/** + * @since 2.0 + * @status experimental + */ + +@Component({ + tag: 'sl-resize-observer', + styleUrl: 'resize-observer.scss', + shadow: true +}) +export class ResizeObserverUtility { + resizeObserver: ResizeObserver; + observedElements: HTMLElement[] = []; + + @Element() host: HTMLSlResizeObserverElement; + + /** Emitted when the element is resized. */ + @Event({ eventName: 'sl-resize' }) slResize: EventEmitter<ResizeObserverEntry[]>; + + connectedCallback() { + this.resizeObserver = new ResizeObserver(event => this.slResize.emit(event)); + this.handleSlotChange = this.handleSlotChange.bind(this); + } + + disconnectedCallback() { + this.resizeObserver.disconnect(); + } + + handleSlotChange() { + const slot = this.host.shadowRoot.querySelector('slot'); + const elements = slot.assignedElements({ flatten: true }) as HTMLElement[]; + + // Unwatch previous elements + this.observedElements.map(el => this.resizeObserver.unobserve(el)); + this.observedElements = []; + + // Watch new elements + elements.map(el => { + this.resizeObserver.observe(el); + this.observedElements.push(el); + }); + } + + render() { + return <slot onSlotchange={this.handleSlotChange} />; + } +}