From def1890f4ff0cccb9573bfc79984fbe1258ed318 Mon Sep 17 00:00:00 2001 From: Ben McCann <322311+benmccann@users.noreply.github.com> Date: Tue, 11 Apr 2023 02:25:53 -0700 Subject: [PATCH 1/4] chore: bump @jridgewell/sourcemap-codec (#8458) --- package-lock.json | 14 +++++++------- package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2c894b1605..3f32f9633f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "devDependencies": { "@ampproject/remapping": "^0.3.0", - "@jridgewell/sourcemap-codec": "^1.4.14", + "@jridgewell/sourcemap-codec": "^1.4.15", "@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^11.2.1", @@ -184,9 +184,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "node_modules/@nodelib/fs.scandir": { @@ -5499,9 +5499,9 @@ "dev": true }, "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, "@nodelib/fs.scandir": { diff --git a/package.json b/package.json index dc44115e39..25aafb817a 100644 --- a/package.json +++ b/package.json @@ -120,7 +120,7 @@ "homepage": "https://svelte.dev", "devDependencies": { "@ampproject/remapping": "^0.3.0", - "@jridgewell/sourcemap-codec": "^1.4.14", + "@jridgewell/sourcemap-codec": "^1.4.15", "@rollup/plugin-commonjs": "^11.0.0", "@rollup/plugin-json": "^6.0.0", "@rollup/plugin-node-resolve": "^11.2.1", From 3a7685fef554f0ae2480146eb7c2d0ec944b28ac Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 11 Apr 2023 11:44:19 +0200 Subject: [PATCH 2/4] fix: special-case width/height attribute during spread (#8412) fixes #6752 --------- Co-authored-by: Ben McCann <322311+benmccann@users.noreply.github.com> Co-authored-by: Tan Li Hau --- src/runtime/internal/dom.ts | 11 ++++++++++- .../samples/spread-width-height-attributes/_config.js | 4 ++++ .../spread-width-height-attributes/main.svelte | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 test/runtime/samples/spread-width-height-attributes/_config.js create mode 100644 test/runtime/samples/spread-width-height-attributes/main.svelte diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 34049b3580..8a78accb50 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -306,6 +306,15 @@ export function attr(node: Element, attribute: string, value?: string) { else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value); } +/** + * List of attributes that should always be set through the attr method, + * because updating them through the property setter doesn't work reliably. + * In the example of `width`/`height`, the problem is that the setter only + * accepts numeric values, but the attribute can also be set to a string like `50%`. + * If this list becomes too big, rethink this approach. + */ +const always_set_through_set_attribute = ['width', 'height']; + export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) { // @ts-ignore const descriptors = Object.getOwnPropertyDescriptors(node.__proto__); @@ -316,7 +325,7 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes node.style.cssText = attributes[key]; } else if (key === '__value') { (node as any).value = node[key] = attributes[key]; - } else if (descriptors[key] && descriptors[key].set) { + } else if (descriptors[key] && descriptors[key].set && always_set_through_set_attribute.indexOf(key) === -1) { node[key] = attributes[key]; } else { attr(node, key, attributes[key]); diff --git a/test/runtime/samples/spread-width-height-attributes/_config.js b/test/runtime/samples/spread-width-height-attributes/_config.js new file mode 100644 index 0000000000..cf2dc7efde --- /dev/null +++ b/test/runtime/samples/spread-width-height-attributes/_config.js @@ -0,0 +1,4 @@ +export default { + // https://github.com/sveltejs/svelte/issues/6752 + html: '' +}; diff --git a/test/runtime/samples/spread-width-height-attributes/main.svelte b/test/runtime/samples/spread-width-height-attributes/main.svelte new file mode 100644 index 0000000000..b91b008457 --- /dev/null +++ b/test/runtime/samples/spread-width-height-attributes/main.svelte @@ -0,0 +1 @@ + From 0adc09da9714bb0fcc7fafdbee569ea7cad4fae5 Mon Sep 17 00:00:00 2001 From: Cymaera <69355340+TheCymaera@users.noreply.github.com> Date: Tue, 11 Apr 2023 18:17:58 +0800 Subject: [PATCH 3/4] feat: add support for resize observer bindings (#8022) Implements ResizeObserver bindings: #5524 (comment) Continuation of: #5963 Related to #7583 --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> --- elements/index.d.ts | 5 ++ src/compiler/compile/nodes/Binding.ts | 3 +- src/compiler/compile/nodes/Element.ts | 7 +- .../render_dom/wrappers/Element/Binding.ts | 8 ++- .../render_dom/wrappers/Element/index.ts | 32 +++++++-- src/compiler/utils/patterns.ts | 6 ++ .../internal/ResizeObserverSingleton.ts | 67 +++++++++++++++++++ src/runtime/internal/dom.ts | 8 ++- 8 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 src/runtime/internal/ResizeObserverSingleton.ts diff --git a/elements/index.d.ts b/elements/index.d.ts index 7595d767bf..ac32ae94c3 100644 --- a/elements/index.d.ts +++ b/elements/index.d.ts @@ -546,6 +546,11 @@ export interface HTMLAttributes extends AriaAttributes, D */ 'bind:innerText'?: string | undefined | null; + readonly 'bind:contentRect'?: DOMRectReadOnly | undefined | null; + readonly 'bind:contentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 + readonly 'bind:borderBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 + readonly 'bind:devicePixelContentBoxSize'?: Array<{ blockSize: number; inlineSize: number }> | undefined | null; // TODO make this ResizeObserverSize once we require TS>=4.4 + // SvelteKit 'data-sveltekit-keepfocus'?: true | '' | 'off' | undefined | null; 'data-sveltekit-noscroll'?: true | '' | 'off' | undefined | null; diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 0c29f7ec67..303506222f 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -3,7 +3,7 @@ import get_object from '../utils/get_object'; import Expression from './shared/Expression'; import Component from '../Component'; import TemplateScope from './shared/TemplateScope'; -import { regex_dimensions } from '../../utils/patterns'; +import { regex_dimensions, regex_box_size } from '../../utils/patterns'; import { Node as ESTreeNode } from 'estree'; import { TemplateNode } from '../../interfaces'; import Element from './Element'; @@ -92,6 +92,7 @@ export default class Binding extends Node { this.is_readonly = regex_dimensions.test(this.name) || + regex_box_size.test(this.name) || (isElement(parent) && ((parent.is_media_node() && read_only_media_attributes.has(this.name)) || (parent.name === 'input' && type === 'file')) /* TODO others? */); diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 1678ea1caa..2410904d63 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -12,7 +12,7 @@ import Text from './Text'; import { namespaces } from '../../utils/namespaces'; import map_children from './shared/map_children'; import { is_name_contenteditable, get_contenteditable_attr } from '../utils/contenteditable'; -import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character } from '../../utils/patterns'; +import { regex_dimensions, regex_starts_with_newline, regex_non_whitespace_character, regex_box_size } from '../../utils/patterns'; import fuzzymatch from '../../utils/fuzzymatch'; import list from '../../utils/list'; import Let from './Let'; @@ -1090,7 +1090,10 @@ export default class Element extends Node { } else if (contenteditable && !contenteditable.is_static) { return component.error(contenteditable, compiler_errors.dynamic_contenteditable_attribute); } - } else if (name !== 'this') { + } else if ( + name !== 'this' && + !regex_box_size.test(name) + ) { return component.error(binding, compiler_errors.invalid_binding(binding.name)); } }); diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index 642e4694b1..01da1f0e12 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -11,6 +11,7 @@ import { Node, Identifier } from 'estree'; import add_to_set from '../../../utils/add_to_set'; import mark_each_block_bindings from '../shared/mark_each_block_bindings'; import handle_select_value_binding from './handle_select_value_binding'; +import { regex_box_size } from '../../../../utils/patterns'; export default class BindingWrapper { node: Binding; @@ -455,7 +456,12 @@ function get_value_from_dom( return x`$$value`; } - // node.name === 'input' && node.get_static_attribute_value('type') === 'range' }, + // resize events { event_names: ['elementresize'], filter: (_node: Element, name: string) => regex_dimensions.test(name) }, + { + event_names: ['elementresizecontentbox'], + filter: (_node: Element, name: string) => + regex_content_rect.test(name) ?? regex_content_box_size.test(name) + }, + + { + event_names: ['elementresizeborderbox'], + filter: (_node: Element, name: string) => + regex_border_box_size.test(name) + }, + + { + event_names: ['elementresizedevicepixelcontentbox'], + filter: (_node: Element, name: string) => + regex_device_pixel_content_box_size.test(name) + }, // media events { event_names: ['timeupdate'], @@ -747,13 +765,19 @@ export default class ElementWrapper extends Wrapper { `); binding_group.events.forEach(name => { - if (name === 'elementresize') { - // special case + const resizeListenerFunctions = { + elementresize: 'add_iframe_resize_listener', + elementresizecontentbox: 'resize_observer_content_box.observe', + elementresizeborderbox: 'resize_observer_border_box.observe', + elementresizedevicepixelcontentbox: 'resize_observer_device_pixel_content_box.observe' + }; + + if (name in resizeListenerFunctions) { const resize_listener = block.get_unique_name(`${this.var.name}_resize_listener`); block.add_variable(resize_listener); block.chunks.mount.push( - b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));` + b`${resize_listener} = @${resizeListenerFunctions[name]}(${this.var}, ${callee}.bind(${this.var}));` ); block.chunks.destroy.push( diff --git a/src/compiler/utils/patterns.ts b/src/compiler/utils/patterns.ts index 9429d47227..f0fb08c7c9 100644 --- a/src/compiler/utils/patterns.ts +++ b/src/compiler/utils/patterns.ts @@ -22,3 +22,9 @@ export const regex_ends_with_underscore = /_$/; export const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; export const regex_dimensions = /^(?:offset|client)(?:Width|Height)$/; + +export const regex_content_rect = /^(?:contentRect)$/; +export const regex_content_box_size = /^(?:contentBoxSize)$/; +export const regex_border_box_size = /^(?:borderBoxSize)$/; +export const regex_device_pixel_content_box_size = /^(?:devicePixelContentBoxSize)$/; +export const regex_box_size = /^(?:contentRect|contentBoxSize|borderBoxSize|devicePixelContentBoxSize)$/; diff --git a/src/runtime/internal/ResizeObserverSingleton.ts b/src/runtime/internal/ResizeObserverSingleton.ts new file mode 100644 index 0000000000..6d1e5b567b --- /dev/null +++ b/src/runtime/internal/ResizeObserverSingleton.ts @@ -0,0 +1,67 @@ +/** + * Resize observer singleton. + * One listener per element only! + * https://groups.google.com/a/chromium.org/g/blink-dev/c/z6ienONUb5A/m/F5-VcUZtBAAJ + */ +export class ResizeObserverSingleton { + constructor(readonly options?: ResizeObserverOptions) {} + + observe(element: Element, listener: Listener) { + this._listeners.set(element, listener); + this._getObserver().observe(element, this.options); + return () => { + this._listeners.delete(element); + this._observer.unobserve(element); // this line can probably be removed + }; + } + + static readonly entries: WeakMap = 'WeakMap' in globalThis ? new WeakMap() : undefined; + + private readonly _listeners: WeakMap = 'WeakMap' in globalThis ? new WeakMap() : undefined; + private _observer?: ResizeObserver; + private _getObserver() { + return this._observer ?? (this._observer = new ResizeObserver((entries) => { + for (const entry of entries) { + ResizeObserverSingleton.entries.set(entry.target, entry); + this._listeners.get(entry.target)?.(entry); + } + })); + } +} + +type Listener = (entry: ResizeObserverEntry)=>any; + +// TODO: Remove this +interface ResizeObserverSize { + readonly blockSize: number; + readonly inlineSize: number; +} + +interface ResizeObserverEntry { + readonly borderBoxSize: readonly ResizeObserverSize[]; + readonly contentBoxSize: readonly ResizeObserverSize[]; + readonly contentRect: DOMRectReadOnly; + readonly devicePixelContentBoxSize: readonly ResizeObserverSize[]; + readonly target: Element; +} + +type ResizeObserverBoxOptions = 'border-box' | 'content-box' | 'device-pixel-content-box'; + +interface ResizeObserverOptions { + box?: ResizeObserverBoxOptions; +} + +interface ResizeObserver { + disconnect(): void; + observe(target: Element, options?: ResizeObserverOptions): void; + unobserve(target: Element): void; +} + +interface ResizeObserverCallback { + (entries: ResizeObserverEntry[], observer: ResizeObserver): void; +} + +declare let ResizeObserver: { + prototype: ResizeObserver; + new(callback: ResizeObserverCallback): ResizeObserver; +}; diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 8a78accb50..4ffa9e4742 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,3 +1,4 @@ +import { ResizeObserverSingleton } from './ResizeObserverSingleton'; import { contenteditable_truthy_values, has_prop } from './utils'; // Track which nodes are claimed during hydration. Unclaimed nodes can then be removed from the DOM @@ -698,7 +699,7 @@ export function is_crossorigin() { return crossorigin; } -export function add_resize_listener(node: HTMLElement, fn: () => void) { +export function add_iframe_resize_listener(node: HTMLElement, fn: () => void) { const computed_style = getComputedStyle(node); if (computed_style.position === 'static') { @@ -746,6 +747,11 @@ export function add_resize_listener(node: HTMLElement, fn: () => void) { }; } +export const resize_observer_content_box = new ResizeObserverSingleton({ box: 'content-box' }); +export const resize_observer_border_box = new ResizeObserverSingleton({ box: 'border-box' }); +export const resize_observer_device_pixel_content_box = new ResizeObserverSingleton({ box: 'device-pixel-content-box' }); +export { ResizeObserverSingleton }; + export function toggle_class(element, name, toggle) { element.classList[toggle ? 'add' : 'remove'](name); } From 56351a3fabbc8ebd44723aa724b8050ab19a6dcd Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Tue, 11 Apr 2023 12:19:30 +0200 Subject: [PATCH 4/4] chore: update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5568dce50..ea9545aa65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Svelte changelog +## Unreleased + +* Handle `width`/`height` attributes when spreading ([#6752](https://github.com/sveltejs/svelte/issues/6752)) +* Add support for resize observer bindings (`
`) ([#8022](https://github.com/sveltejs/svelte/pull/8022)) + ## 3.58.0 * Add `bind:innerText` for `contenteditable` elements ([#3311](https://github.com/sveltejs/svelte/issues/3311))