From d16dd5d7caa9e9d992bcd765d980ebc23d796528 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 24 Feb 2023 21:20:44 +0800 Subject: [PATCH] feat: add naturalWidth and naturalHeight bindings (#7857) Closes #7771 --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen --- site/content/docs/03-template-syntax.md | 16 ++++ src/compiler/compile/nodes/Binding.ts | 4 +- src/compiler/compile/nodes/Element.ts | 7 ++ .../render_dom/wrappers/Element/index.ts | 4 + .../natural-dimension-binding/expected.js | 95 +++++++++++++++++++ .../natural-dimension-binding/input.svelte | 8 ++ 6 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 test/js/samples/natural-dimension-binding/expected.js create mode 100644 test/js/samples/natural-dimension-binding/input.svelte diff --git a/site/content/docs/03-template-syntax.md b/site/content/docs/03-template-syntax.md index 74a2069c68..f0bbf67dea 100644 --- a/site/content/docs/03-template-syntax.md +++ b/site/content/docs/03-template-syntax.md @@ -751,6 +751,22 @@ Videos additionally have readonly `videoWidth` and `videoHeight` bindings. > ``` +##### Image element bindings + +--- + +Image elements (``) have two readonly bindings: + +* `naturalWidth` (readonly) — the original width of the image, available after the image has loaded +* `naturalHeight` (readonly) — the original height of the image, available after the image has loaded + +```sv + +``` + ##### Block-level element bindings --- diff --git a/src/compiler/compile/nodes/Binding.ts b/src/compiler/compile/nodes/Binding.ts index 46112a03a3..9a62b68850 100644 --- a/src/compiler/compile/nodes/Binding.ts +++ b/src/compiler/compile/nodes/Binding.ts @@ -22,7 +22,9 @@ const read_only_media_attributes = new Set([ 'seeking', 'ended', 'videoHeight', - 'videoWidth' + 'videoWidth', + 'naturalWidth', + 'naturalHeight' ]); export default class Binding extends Node { diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 4d1cb758fe..1ac75faffd 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -918,6 +918,13 @@ export default class Element extends Node { } else if (is_void(this.name)) { return component.error(binding, compiler_errors.invalid_binding_on(binding.name, `void elements like <${this.name}>. Use a wrapper element instead`)); } + } else if ( + name === 'naturalWidth' || + name === 'naturalHeight' + ) { + if (this.name !== 'img') { + return component.error(binding, compiler_errors.invalid_binding_element_with('', name)); + } } else if ( name === 'textContent' || name === 'innerHTML' diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 8a8f59810b..39ca782af0 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -137,6 +137,10 @@ const events = [ event_names: ['toggle'], filter: (node: Element, _name: string) => node.name === 'details' + }, + { + event_names: ['load'], + filter: (_: Element, name: string) => name === 'naturalHeight' || name === 'naturalWidth' } ]; diff --git a/test/js/samples/natural-dimension-binding/expected.js b/test/js/samples/natural-dimension-binding/expected.js new file mode 100644 index 0000000000..3920c61009 --- /dev/null +++ b/test/js/samples/natural-dimension-binding/expected.js @@ -0,0 +1,95 @@ +/* generated by Svelte vX.Y.Z */ +import { + SvelteComponent, + add_render_callback, + attr, + detach, + element, + init, + insert, + listen, + noop, + safe_not_equal, + set_data, + space, + src_url_equal, + text +} from "svelte/internal"; + +function create_fragment(ctx) { + let img; + let img_src_value; + let t0; + let t1; + let t2; + let t3; + let mounted; + let dispose; + + return { + c() { + img = element("img"); + t0 = space(); + t1 = text(/*naturalWidth*/ ctx[0]); + t2 = text(" x "); + t3 = text(/*naturalHeight*/ ctx[1]); + if (!src_url_equal(img.src, img_src_value = "something.jpg")) attr(img, "src", img_src_value); + if (/*naturalWidth*/ ctx[0] === void 0 || /*naturalHeight*/ ctx[1] === void 0) add_render_callback(() => /*img_load_handler*/ ctx[2].call(img)); + }, + m(target, anchor) { + insert(target, img, anchor); + insert(target, t0, anchor); + insert(target, t1, anchor); + insert(target, t2, anchor); + insert(target, t3, anchor); + + if (!mounted) { + dispose = listen(img, "load", /*img_load_handler*/ ctx[2]); + mounted = true; + } + }, + p(ctx, [dirty]) { + if (dirty & /*naturalWidth*/ 1) set_data(t1, /*naturalWidth*/ ctx[0]); + if (dirty & /*naturalHeight*/ 2) set_data(t3, /*naturalHeight*/ ctx[1]); + }, + i: noop, + o: noop, + d(detaching) { + if (detaching) detach(img); + if (detaching) detach(t0); + if (detaching) detach(t1); + if (detaching) detach(t2); + if (detaching) detach(t3); + mounted = false; + dispose(); + } + }; +} + +function instance($$self, $$props, $$invalidate) { + let { naturalWidth = 0 } = $$props; + let { naturalHeight = 0 } = $$props; + + function img_load_handler() { + naturalWidth = this.naturalWidth; + naturalHeight = this.naturalHeight; + $$invalidate(0, naturalWidth); + $$invalidate(1, naturalHeight); + } + + $$self.$$set = $$props => { + if ('naturalWidth' in $$props) $$invalidate(0, naturalWidth = $$props.naturalWidth); + if ('naturalHeight' in $$props) $$invalidate(1, naturalHeight = $$props.naturalHeight); + }; + + return [naturalWidth, naturalHeight, img_load_handler]; +} + +class Component extends SvelteComponent { + constructor(options) { + super(); + init(this, options, instance, create_fragment, safe_not_equal, { naturalWidth: 0, naturalHeight: 1 }); + } +} + +export default Component; \ No newline at end of file diff --git a/test/js/samples/natural-dimension-binding/input.svelte b/test/js/samples/natural-dimension-binding/input.svelte new file mode 100644 index 0000000000..67a7a947fa --- /dev/null +++ b/test/js/samples/natural-dimension-binding/input.svelte @@ -0,0 +1,8 @@ + + + + +{naturalWidth} x {naturalHeight}