resize-iframe 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) [year] [fullname]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,239 @@
1
+ # resize-iframe
2
+
3
+ Iframes that size themselves to their content, cross-origin included. One script on
4
+ each page, no build step, no dependencies, MIT.
5
+
6
+ - 📐 Auto-resizes on content, DOM, style, font, image and viewport changes
7
+ - ↔️ Vertical, horizontal, or both
8
+ - 🔒 Messages matched against the frame's own window, which cannot be forged
9
+ - 🍪 Storage Access API support for third-party embeds
10
+ - 🧩 Use `iframeResize()` or the `<resize-iframe>` element
11
+ - 📦 ~5KB unminified, across both files
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install resize-iframe
17
+ ```
18
+
19
+ Or from a CDN — the parent page needs `resize-iframe.js`, the framed page needs
20
+ `resize-iframe-child.js`:
21
+
22
+ ```html
23
+ <script type="module" src="https://unpkg.com/resize-iframe/resize-iframe.js"></script>
24
+ <script src="https://unpkg.com/resize-iframe/resize-iframe-child.js"></script>
25
+ ```
26
+
27
+ ## Setting up the parent page
28
+
29
+ Give the iframe a percentage width and an initial height, then let the library
30
+ control the other dimension. Starting at `100vh` makes loading look smoother — the
31
+ content below the iframe only appears once it has sized itself.
32
+
33
+ ```html
34
+ <style>
35
+ #myIframe { width: 100%; height: 100vh; }
36
+ </style>
37
+
38
+ <iframe id="myIframe" src="https://anotherdomain.com/iframe.html"></iframe>
39
+
40
+ <script type="module">
41
+ import { iframeResize } from 'resize-iframe';
42
+
43
+ const [frame] = iframeResize({ direction: 'vertical' }, '#myIframe');
44
+ </script>
45
+ ```
46
+
47
+ `iframeResize(options, target)` takes a CSS selector, an element, or a list of
48
+ elements, and returns one handle per iframe. Both arguments are optional — with no
49
+ target it binds every iframe on the page.
50
+
51
+ Or use the element, which calls `iframeResize` for you:
52
+
53
+ ```html
54
+ <script type="module" src="https://unpkg.com/resize-iframe/resize-iframe.js"></script>
55
+
56
+ <resize-iframe src="https://anotherdomain.com/iframe.html" title="Pricing table"></resize-iframe>
57
+ ```
58
+
59
+ ## Setting up the child page
60
+
61
+ ```html
62
+ <script src="https://unpkg.com/resize-iframe/resize-iframe-child.js"></script>
63
+ ```
64
+
65
+ That alone auto-resizes the frame. A cross-origin parent cannot measure your
66
+ content, so without this script nothing happens — the parent logs a warning after
67
+ five seconds saying exactly that.
68
+
69
+ The script tag takes two optional attributes:
70
+
71
+ | Attribute | Default | Description |
72
+ | -------------------- | -------------------- | ---------------------------------------------------- |
73
+ | `data-parent-origin` | `*` | Only report size to this embedder |
74
+ | `data-size-selector` | `[data-iframe-size]` | Measure these elements instead of the body's children |
75
+
76
+ Marking your content wrapper with `data-iframe-size` is the fix for a page that
77
+ measures larger than it looks — an overlay, a decorative element, a stray margin.
78
+
79
+ ## Third-party embedding and cookies
80
+
81
+ Once your page is embedded on someone else's domain, browsers partition its
82
+ storage: cookies set first-party are invisible, so sessions and logins break before
83
+ sizing is ever the problem. The
84
+ [Storage Access API](https://developer.mozilla.org/en-US/docs/Web/API/Storage_Access_API)
85
+ is the way back, and the child script wires it up.
86
+
87
+ **Parent** — grant the permission and, if you sandbox, the matching token:
88
+
89
+ ```html
90
+ <resize-iframe
91
+ src="https://embed.example.com/app"
92
+ allow="storage-access"
93
+ sandbox="allow-scripts allow-same-origin allow-storage-access-by-user-activation">
94
+ </resize-iframe>
95
+ ```
96
+
97
+ **Child** — probe on load and, if access is missing, ask for it from a click:
98
+
99
+ ```html
100
+ <button id="continue" hidden>Continue</button>
101
+
102
+ <script>
103
+ addEventListener('load', async () => {
104
+ if (await parentIframe.hasStorageAccess()) return;
105
+
106
+ continue.hidden = false;
107
+ continue.onclick = async () => {
108
+ if (await parentIframe.requestStorageAccess()) location.reload();
109
+ };
110
+ });
111
+ </script>
112
+ ```
113
+
114
+ Three things decide whether this works, none of them optional:
115
+
116
+ 1. **A user gesture inside the frame.** The request is denied outright without a
117
+ click or tap in the embedded page. The parent cannot supply one for you, which
118
+ is why the button has to live in the child.
119
+ 2. **A prior first-party visit.** Browsers only grant access to sites the user has
120
+ used directly. If your embed can be a user's first contact with your domain,
121
+ open it in a popup or new tab first, let them interact, then request access.
122
+ 3. **Reload after granting.** Anything your page read at startup ran without
123
+ cookies. Reloading is cruder than re-fetching, and far easier to get right.
124
+
125
+ Probe the API, never the browser. Safari and Chrome both partition storage, and a
126
+ `/safari/i.test(navigator.userAgent)` gate silently skips the prompt everywhere
127
+ else — the failure is invisible, because the page loads fine and just acts logged
128
+ out.
129
+
130
+ The parent is told the outcome and can render its own fallback:
131
+
132
+ ```javascript
133
+ iframeResize({
134
+ onStorageAccess: ({ hasAccess }) => {
135
+ if (!hasAccess) showOpenInNewTabLink();
136
+ },
137
+ });
138
+ ```
139
+
140
+ ## Options
141
+
142
+ | Option | Default | Description |
143
+ | ----------------- | ------------ | --------------------------------------------------------------------- |
144
+ | `direction` | `'vertical'` | `'vertical'`, `'horizontal'`, `'both'`, or `'none'` |
145
+ | `offsetSize` | `0` | Pixels added to the computed size, positive or negative |
146
+ | `warningTimeout` | `5000` | Warn if the frame has not responded in this many ms; `0` to silence it |
147
+ | `onReady` | — | `(iframe) => void`, called on the first size message |
148
+ | `onResized` | — | `({ iframe, height, width }) => void` |
149
+ | `onMessage` | — | `({ iframe, message }) => void` |
150
+ | `onStorageAccess` | — | `({ iframe, hasAccess }) => void` |
151
+
152
+ With `direction: 'both'` give your content an intrinsic width, or a `width: 100%`
153
+ layout will chase the frame it is being measured in.
154
+
155
+ There is no `checkOrigin` option. Messages are matched against the frame's own
156
+ window, which the browser sets and a page cannot forge, and that holds even after
157
+ the frame navigates to another domain — the case an origin allowlist has to be
158
+ turned off for.
159
+
160
+ ## Element attributes
161
+
162
+ | Attribute | Default | Description |
163
+ | ------------- | ------------------ | ---------------------------------------- |
164
+ | `src` | — | URL to embed |
165
+ | `title` | `Embedded content` | Accessible name for the iframe |
166
+ | `direction` | `vertical` | As above |
167
+ | `offset-size` | `0` | As above |
168
+ | `min-h` | — | Minimum height constraint |
169
+ | `max-h` | — | Maximum height constraint |
170
+ | `allow` | — | Passed through, e.g. `storage-access` |
171
+ | `sandbox` | — | Passed through |
172
+
173
+ ## Methods and events
174
+
175
+ Parent, on the handle returned by `iframeResize` (also at `iframe.iframeResizer`):
176
+
177
+ ```javascript
178
+ frame.sendMessage({ hello: 'world' }, 'https://anotherdomain.com');
179
+ frame.disconnect(); // call before removing the iframe from the page
180
+ ```
181
+
182
+ Child, on `window.parentIframe`:
183
+
184
+ ```javascript
185
+ parentIframe.sendMessage('ping'); // → parent's onMessage / 'frame-message' event
186
+ parentIframe.autoResize(false); // pause resizing; returns the current state
187
+ parentIframe.resize(); // nudge, for a change neither observer sees
188
+ parentIframe.hasStorageAccess(); // → Promise<boolean>
189
+ parentIframe.requestStorageAccess(); // → Promise<boolean>, from a click handler
190
+ ```
191
+
192
+ Events fire on the iframe and bubble, so `<resize-iframe>` re-emits them:
193
+
194
+ ```javascript
195
+ document.querySelector('resize-iframe').addEventListener('resize', (e) => {
196
+ console.log(`Frame is now ${e.detail.height}px tall`);
197
+ });
198
+ ```
199
+
200
+ `ready`, `resize`, `frame-message`, and `storage-access` are available.
201
+
202
+ ## TypeScript
203
+
204
+ Types ship with the package. JSX typing for the element is picked up automatically
205
+ by React 18 and below, Preact, and Solid. React 19 reads `React.JSX` instead, so
206
+ add this once:
207
+
208
+ ```ts
209
+ import type { ResizeIframeAttributes } from 'resize-iframe';
210
+
211
+ declare module 'react' {
212
+ namespace JSX {
213
+ interface IntrinsicElements {
214
+ 'resize-iframe': ResizeIframeAttributes;
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## Testing
221
+
222
+ ```bash
223
+ npx serve
224
+ ```
225
+
226
+ Open `resize-iframe-test.html`; it prints ten PASS lines.
227
+
228
+ ## Browser Support
229
+
230
+ - Chrome/Edge 80+
231
+ - Firefox 75+
232
+ - Safari 13.1+
233
+
234
+ Storage Access API support is probed at runtime, so browsers without it simply
235
+ report access as granted.
236
+
237
+ ## Licence
238
+
239
+ MIT
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "resize-iframe",
3
+ "version": "0.1.0",
4
+ "description": "Iframes that size themselves to their content, cross-origin included. No build step, no dependencies.",
5
+ "type": "module",
6
+ "main": "resize-iframe.js",
7
+ "module": "resize-iframe.js",
8
+ "types": "resize-iframe.d.ts",
9
+ "files": [
10
+ "resize-iframe.js",
11
+ "resize-iframe-child.js",
12
+ "resize-iframe.d.ts"
13
+ ],
14
+ "keywords": [
15
+ "iframe",
16
+ "iframe-resizer",
17
+ "resize",
18
+ "web-components",
19
+ "custom-element",
20
+ "postmessage",
21
+ "storage-access",
22
+ "third-party-cookies"
23
+ ],
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/jagreehal/resize-iframe"
27
+ },
28
+ "author": "Jag Reehal <jag@jagreehal.com>",
29
+ "license": "MIT"
30
+ }
@@ -0,0 +1,113 @@
1
+ // Load this inside the framed page:
2
+ // <script src=".../resize-iframe-child.js"></script>
3
+ // Optional attributes on that script tag:
4
+ // data-parent-origin="https://parent.example" report size only to that embedder
5
+ // data-size-selector=".content" measure these elements instead
6
+ // Or mark elements in the page with data-iframe-size to the same effect.
7
+ const config = document.currentScript?.dataset ?? {};
8
+ const targetOrigin = config.parentOrigin || '*';
9
+ const sizeSelector = config.sizeSelector || '[data-iframe-size]';
10
+
11
+ const start = () => {
12
+ let last = { height: 0, width: 0 };
13
+ let queued = false;
14
+ let auto = true;
15
+
16
+ // Measure the bottom and right edges of the marked elements, or of body's
17
+ // children. Never body itself: it stretches to fill the frame in quirks mode
18
+ // or under `body { height: 100% }`, which ratchets the frame larger and never
19
+ // lets it shrink back.
20
+ // ponytail: body's own bottom/right padding and margin are ignored — zero them
21
+ // if that gap matters, or wrap your content and mark it with data-iframe-size.
22
+ const measure = () => {
23
+ const marked = document.querySelectorAll(sizeSelector);
24
+ const elements = marked.length ? marked : document.body.children;
25
+ if (!elements.length) {
26
+ const body = document.body.getBoundingClientRect();
27
+ return { height: Math.ceil(body.bottom), width: Math.ceil(body.right) };
28
+ }
29
+ let height = 0;
30
+ let width = 0;
31
+ for (const element of elements) {
32
+ const box = element.getBoundingClientRect();
33
+ height = Math.max(height, box.bottom);
34
+ width = Math.max(width, box.right);
35
+ }
36
+ return { height: Math.ceil(height), width: Math.ceil(width) };
37
+ };
38
+
39
+ const post = () => {
40
+ queued = false;
41
+ const size = measure();
42
+ if (size.height === last.height && size.width === last.width) return;
43
+ last = size;
44
+ parent.postMessage({ 'resize-iframe': size }, targetOrigin);
45
+ };
46
+
47
+ // Batch to one measurement per frame: mutations arrive in bursts and every
48
+ // measure() forces a layout.
49
+ const schedule = () => {
50
+ if (queued || !auto) return;
51
+ queued = true;
52
+ requestAnimationFrame(post);
53
+ };
54
+
55
+ // ResizeObserver catches reflow (images, fonts, viewport). MutationObserver
56
+ // catches DOM and style changes, which the observer misses entirely whenever
57
+ // body is stretched to the frame and so never changes size itself.
58
+ new ResizeObserver(schedule).observe(document.body);
59
+ new MutationObserver(schedule).observe(document.body, {
60
+ subtree: true,
61
+ childList: true,
62
+ attributes: true,
63
+ characterData: true,
64
+ });
65
+ addEventListener('load', schedule); // images and fonts landing late
66
+
67
+ // Third-party cookies: an embedded page gets partitioned storage until the user
68
+ // grants access. Probe the API rather than sniffing the browser — Safari and
69
+ // Chrome both partition, and which browsers do is not a stable list.
70
+ const hasStorageAccess = () =>
71
+ document.hasStorageAccess?.().catch(() => false) ?? Promise.resolve(true);
72
+
73
+ const reportStorageAccess = async () => {
74
+ const access = await hasStorageAccess();
75
+ parent.postMessage({ 'resize-iframe-storage': { hasAccess: access } }, targetOrigin);
76
+ return access;
77
+ };
78
+
79
+ window.parentIframe = {
80
+ autoResize(state) {
81
+ if (state !== undefined) auto = state;
82
+ if (auto) schedule();
83
+ return auto;
84
+ },
85
+ resize() {
86
+ post(); // nudge, for the rare change neither observer sees
87
+ },
88
+ sendMessage(message, origin = targetOrigin) {
89
+ parent.postMessage({ 'resize-iframe-message': message }, origin);
90
+ },
91
+ hasStorageAccess,
92
+ // MUST be called from a click or tap handler in this page: the browser denies
93
+ // the request without a user gesture here, and the parent cannot supply one.
94
+ // Reload afterwards if your page reads cookies during startup.
95
+ async requestStorageAccess() {
96
+ if (!document.requestStorageAccess) return true;
97
+ try {
98
+ await document.requestStorageAccess();
99
+ } catch {
100
+ // Denied, or the user has never visited this site first-party.
101
+ }
102
+ return reportStorageAccess();
103
+ },
104
+ };
105
+
106
+ reportStorageAccess();
107
+ post();
108
+ };
109
+
110
+ if (parent !== window) {
111
+ if (document.body) start();
112
+ else addEventListener('DOMContentLoaded', start);
113
+ }
@@ -0,0 +1,76 @@
1
+ export interface ResizeIframeOptions {
2
+ /** Which dimension the library controls. Default 'vertical'. */
3
+ direction?: 'vertical' | 'horizontal' | 'both' | 'none';
4
+ /** Pixels added to the computed size, positive or negative. Default 0. */
5
+ offsetSize?: number;
6
+ /** Warn if the frame has not responded in this many ms. 0 disables. Default 5000. */
7
+ warningTimeout?: number;
8
+ onReady?: (iframe: HTMLIFrameElement) => void;
9
+ onResized?: (data: {
10
+ iframe: HTMLIFrameElement;
11
+ height: number;
12
+ width: number;
13
+ }) => void;
14
+ onMessage?: (data: { iframe: HTMLIFrameElement; message: unknown }) => void;
15
+ onStorageAccess?: (data: {
16
+ iframe: HTMLIFrameElement;
17
+ hasAccess: boolean;
18
+ }) => void;
19
+ }
20
+
21
+ export interface ResizeIframeHandle {
22
+ /** Call before removing the iframe from the page, or the listener leaks. */
23
+ disconnect(): void;
24
+ sendMessage(message: unknown, targetOrigin?: string): void;
25
+ }
26
+
27
+ /** Binds to a selector, an element, a list of elements, or every iframe on the page. */
28
+ export function iframeResize(
29
+ options?: ResizeIframeOptions,
30
+ target?: string | HTMLIFrameElement | HTMLIFrameElement[] | NodeList
31
+ ): ResizeIframeHandle[];
32
+
33
+ /** Available inside the framed page once resize-iframe-child.js has loaded. */
34
+ export interface ParentIframe {
35
+ autoResize(state?: boolean): boolean;
36
+ resize(): void;
37
+ sendMessage(message: unknown, targetOrigin?: string): void;
38
+ hasStorageAccess(): Promise<boolean>;
39
+ /** Must be called from a click or tap handler in the framed page. */
40
+ requestStorageAccess(): Promise<boolean>;
41
+ }
42
+
43
+ /** Attributes of the <resize-iframe> element. */
44
+ export interface ResizeIframeAttributes {
45
+ src?: string;
46
+ title?: string;
47
+ direction?: 'vertical' | 'horizontal' | 'both' | 'none';
48
+ 'offset-size'?: number | string;
49
+ 'min-h'?: string;
50
+ 'max-h'?: string;
51
+ allow?: string;
52
+ sandbox?: string;
53
+ /** Whatever else the host framework puts on an element: class, ref, key. */
54
+ [attribute: string]: unknown;
55
+ }
56
+
57
+ declare global {
58
+ interface Window {
59
+ parentIframe?: ParentIframe;
60
+ }
61
+
62
+ interface HTMLElementTagNameMap {
63
+ 'resize-iframe': HTMLElement & {
64
+ readonly height: string | undefined;
65
+ sendMessage(message: unknown, targetOrigin?: string): void;
66
+ };
67
+ }
68
+
69
+ // Picked up by React 18 and below, Preact, and Solid. React 19 moved to
70
+ // React.JSX — see the README for the one-line augmentation it needs.
71
+ namespace JSX {
72
+ interface IntrinsicElements {
73
+ 'resize-iframe': ResizeIframeAttributes;
74
+ }
75
+ }
76
+ }
@@ -0,0 +1,151 @@
1
+ // Iframes that size themselves to their content, cross-origin included.
2
+ // Two ways in: iframeResize(options, target) for existing iframes, or the
3
+ // <resize-iframe src="..."> element. Both need resize-iframe-child.js in the
4
+ // framed page — a cross-origin parent cannot measure the content itself.
5
+ const DEFAULTS = {
6
+ direction: 'vertical', // 'vertical' | 'horizontal' | 'both' | 'none'
7
+ offsetSize: 0,
8
+ warningTimeout: 5000,
9
+ onReady: null,
10
+ onResized: null,
11
+ onMessage: null,
12
+ onStorageAccess: null,
13
+ };
14
+
15
+ const toIframes = (target) => {
16
+ if (target == null) return [...document.querySelectorAll('iframe')];
17
+ if (typeof target === 'string') return [...document.querySelectorAll(target)];
18
+ return Array.isArray(target) || target instanceof NodeList ? [...target] : [target];
19
+ };
20
+
21
+ export function iframeResize(options = {}, target) {
22
+ const settings = { ...DEFAULTS, ...options };
23
+ return toIframes(target).map((iframe) => connect(iframe, settings));
24
+ }
25
+
26
+ function connect(iframe, settings) {
27
+ const { direction, offsetSize } = settings;
28
+ let ready = false;
29
+
30
+ const warning = settings.warningTimeout
31
+ ? setTimeout(() => {
32
+ if (!ready) {
33
+ console.warn(
34
+ `[resize-iframe] no response from ${iframe.src || 'iframe'} — is resize-iframe-child.js loaded in the framed page?`
35
+ );
36
+ }
37
+ }, settings.warningTimeout)
38
+ : 0;
39
+
40
+ const emit = (type, detail) =>
41
+ iframe.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true }));
42
+
43
+ const onMessage = (event) => {
44
+ // The browser sets event.source, so it cannot be forged: only messages from
45
+ // this frame's own window get through, wherever it has navigated to. That
46
+ // covers what iframe-resizer's checkOrigin option does, without the config.
47
+ if (event.source !== iframe.contentWindow) return;
48
+ if (typeof event.data !== 'object' || event.data === null) return;
49
+
50
+ if ('resize-iframe-message' in event.data) {
51
+ const message = event.data['resize-iframe-message'];
52
+ settings.onMessage?.({ iframe, message });
53
+ emit('frame-message', message);
54
+ return;
55
+ }
56
+
57
+ if ('resize-iframe-storage' in event.data) {
58
+ const { hasAccess } = event.data['resize-iframe-storage'];
59
+ settings.onStorageAccess?.({ iframe, hasAccess });
60
+ emit('storage-access', { hasAccess });
61
+ return;
62
+ }
63
+
64
+ const size = event.data['resize-iframe'];
65
+ if (!size) return;
66
+ const height = size.height + offsetSize;
67
+ const width = size.width + offsetSize;
68
+
69
+ if (direction === 'vertical' || direction === 'both') iframe.style.height = `${height}px`;
70
+ if (direction === 'horizontal' || direction === 'both') iframe.style.width = `${width}px`;
71
+
72
+ if (!ready) {
73
+ ready = true;
74
+ clearTimeout(warning);
75
+ settings.onReady?.(iframe);
76
+ emit('ready', { iframe });
77
+ }
78
+ settings.onResized?.({ iframe, height, width });
79
+ emit('resize', { height, width });
80
+ };
81
+
82
+ addEventListener('message', onMessage);
83
+
84
+ // Call disconnect() before removing the iframe, or the listener leaks.
85
+ iframe.iframeResizer = {
86
+ disconnect() {
87
+ removeEventListener('message', onMessage);
88
+ clearTimeout(warning);
89
+ delete iframe.iframeResizer;
90
+ },
91
+ sendMessage(message, targetOrigin = '*') {
92
+ iframe.contentWindow?.postMessage(message, targetOrigin);
93
+ },
94
+ };
95
+ return iframe.iframeResizer;
96
+ }
97
+
98
+ class ResizeIframe extends HTMLElement {
99
+ static get observedAttributes() {
100
+ return ['src', 'min-h', 'max-h', 'allow', 'sandbox'];
101
+ }
102
+
103
+ connectedCallback() {
104
+ if (!this.shadowRoot) {
105
+ this.attachShadow({ mode: 'open' }).innerHTML = `
106
+ <style>
107
+ :host { display: block; }
108
+ iframe { display: block; width: 100%; border: 0; }
109
+ </style>
110
+ <iframe part="frame"></iframe>`;
111
+ this.iframe = this.shadowRoot.querySelector('iframe');
112
+ // An iframe without an accessible name is a screen reader dead end.
113
+ this.iframe.title = this.getAttribute('title') || 'Embedded content';
114
+ for (const name of ResizeIframe.observedAttributes) {
115
+ this.attributeChangedCallback(name, null, this.getAttribute(name));
116
+ }
117
+ }
118
+ iframeResize(
119
+ {
120
+ direction: this.getAttribute('direction') || DEFAULTS.direction,
121
+ offsetSize: Number(this.getAttribute('offset-size')) || 0,
122
+ },
123
+ this.iframe
124
+ );
125
+ }
126
+
127
+ disconnectedCallback() {
128
+ this.iframe?.iframeResizer?.disconnect();
129
+ }
130
+
131
+ attributeChangedCallback(name, oldValue, newValue) {
132
+ if (!this.iframe || newValue === null) return;
133
+ if (name === 'src') this.iframe.src = newValue;
134
+ if (name === 'min-h') this.iframe.style.minHeight = newValue;
135
+ if (name === 'max-h') this.iframe.style.maxHeight = newValue;
136
+ // Passed through because third-party embeds need them: storage access is
137
+ // denied outright unless the frame carries allow="storage-access", and a
138
+ // sandboxed frame also needs allow-storage-access-by-user-activation.
139
+ if (name === 'allow' || name === 'sandbox') this.iframe.setAttribute(name, newValue);
140
+ }
141
+
142
+ get height() {
143
+ return this.iframe?.style.height;
144
+ }
145
+
146
+ sendMessage(message, targetOrigin) {
147
+ this.iframe?.iframeResizer?.sendMessage(message, targetOrigin);
148
+ }
149
+ }
150
+
151
+ customElements.define('resize-iframe', ResizeIframe);