@@ -51,12 +51,16 @@ pub const IntersectionObserver = struct {
5151 // new IntersectionObserver(callback)
5252 // new IntersectionObserver(callback, options) [not supported yet]
5353 pub fn constructor (callback : Env.Callback , options_ : ? IntersectionObserverOptions , state : * SessionState ) ! IntersectionObserver {
54- if (options_ != null ) return error .IntersectionObserverOptionsNotYetSupported ;
55- const options = IntersectionObserverOptions {
54+ var options = IntersectionObserverOptions {
5655 .root = parser .documentToNode (parser .documentHTMLToDocument (state .document .? )),
5756 .rootMargin = "0px 0px 0px 0px" ,
58- .threshold = &[ _ ] f32 { 0.0 } ,
57+ .threshold = & default_threshold ,
5958 };
59+ if (options_ ) | * o | {
60+ if (o .root ) | root | {
61+ options .root = root ;
62+ } // Other properties are not used due to the way we render
63+ }
6064
6165 return .{
6266 .callback = callback ,
@@ -103,6 +107,7 @@ const IntersectionObserverOptions = struct {
103107 rootMargin : ? []const u8 ,
104108 threshold : ? []const f32 ,
105109};
110+ const default_threshold = [_ ]f32 {0.0 };
106111
107112// https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserverEntry
108113// https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
@@ -127,19 +132,126 @@ pub const IntersectionObserverEntry = struct {
127132 }
128133
129134 // A Boolean value which is true if the target element intersects with the intersection observer's root. If this is true, then, the IntersectionObserverEntry describes a transition into a state of intersection; if it's false, then you know the transition is from intersecting to not-intersecting.
130- pub fn isIntersecting (_ : * const IntersectionObserverEntry ) bool {
135+ pub fn get_isIntersecting (_ : * const IntersectionObserverEntry ) bool {
131136 return true ;
132137 }
133138
134139 // Returns a DOMRectReadOnly for the intersection observer's root.
135140 pub fn get_rootBounds (self : * const IntersectionObserverEntry ) ! Element.DOMRect {
136- // TODO self.options.root can be an Element or a Document when Options are supported
137- const element = (try parser .documentGetDocumentElement (parser .documentHTMLToDocument (self .state .document .? ))).? ;
141+ const root = self .options .root .? ;
142+ const root_type = try parser .nodeType (root );
143+
144+ var element : * parser.Element = undefined ;
145+ switch (root_type ) {
146+ .element = > element = parser .nodeToElement (root ),
147+ .document = > {
148+ const doc = parser .nodeToDocument (root );
149+ element = (try parser .documentGetDocumentElement (doc )).? ;
150+ },
151+ else = > return error .InvalidState ,
152+ }
153+
138154 return try self .state .renderer .getRect (element );
139155 }
140156
141157 // The Element whose intersection with the root changed.
142158 pub fn get_target (self : * const IntersectionObserverEntry ) * parser.Element {
143159 return self .target ;
144160 }
161+
162+ // TODO: pub fn get_time(self: *const IntersectionObserverEntry)
145163};
164+
165+ const testing = @import ("../../testing.zig" );
166+ test "Browser.DOM.IntersectionObserver" {
167+ var runner = try testing .jsRunner (testing .tracking_allocator , .{});
168+ defer runner .deinit ();
169+
170+ try runner .testCases (&.{
171+ .{ "new IntersectionObserver(() => {}).observe(document.documentElement);" , "undefined" },
172+ }, .{});
173+
174+ try runner .testCases (&.{
175+ .{ "let count_a = 0;" , "undefined" },
176+ .{ "const a1 = document.createElement('div');" , "undefined" },
177+ .{ "new IntersectionObserver(entries => {count_a += 1;}).observe(a1);" , "undefined" },
178+ .{ "count_a;" , "1" },
179+ }, .{});
180+
181+ // This test is documenting current behavior, not correct behavior.
182+ // Currently every time observe is called, the callback is called with all entries. 1 + 2 = 3
183+ try runner .testCases (&.{
184+ .{ "let count_b = 0;" , "undefined" },
185+ .{ "let observer_b = new IntersectionObserver(entries => {count_b = entries.length;});" , "undefined" },
186+ .{ "const b1 = document.createElement('div');" , "undefined" },
187+ .{ "observer_b.observe(b1);" , "undefined" },
188+ .{ "count_b;" , "1" },
189+ .{ "const b2 = document.createElement('div');" , "undefined" },
190+ .{ "observer_b.observe(b2);" , "undefined" },
191+ .{ "count_b;" , "2" },
192+ }, .{});
193+
194+ // Unobserve
195+ try runner .testCases (&.{
196+ .{ "let count_c = 0;" , "undefined" },
197+ .{ "let observer_c = new IntersectionObserver(entries => { count_c = entries.length;});" , "undefined" },
198+ .{ "const c1 = document.createElement('div');" , "undefined" },
199+ .{ "observer_c.observe(c1);" , "undefined" },
200+ .{ "count_c;" , "1" },
201+ .{ "observer_c.unobserve(c1);" , "undefined" },
202+ .{ "const c2 = document.createElement('div');" , "undefined" },
203+ .{ "observer_c.observe(c2);" , "undefined" },
204+ .{ "count_c;" , "1" },
205+ }, .{});
206+
207+ // Disconnect
208+ try runner .testCases (&.{
209+ .{ "let observer_d = new IntersectionObserver(entries => {});" , "undefined" },
210+ .{ "let d1 = document.createElement('div');" , "undefined" },
211+ .{ "observer_d.observe(d1);" , "undefined" },
212+ .{ "observer_d.disconnect();" , "undefined" },
213+ .{ "observer_d.takeRecords().length;" , "0" },
214+ }, .{});
215+
216+ // takeRecords
217+ try runner .testCases (&.{
218+ .{ "let observer_e = new IntersectionObserver(entries => {});" , "undefined" },
219+ .{ "let e1 = document.createElement('div');" , "undefined" },
220+ .{ "observer_e.observe(e1);" , "undefined" },
221+ .{ "const e2 = document.createElement('div');" , "undefined" },
222+ .{ "observer_e.observe(e2);" , "undefined" },
223+ .{ "observer_e.takeRecords().length;" , "2" },
224+ }, .{});
225+
226+ // Entry
227+ try runner .testCases (&.{
228+ .{ "let entry;" , "undefined" },
229+ .{ "new IntersectionObserver(entries => { entry = entries[0]; }).observe(document.createElement('div'));" , "undefined" },
230+ .{ "entry.boundingClientRect.x;" , "1" },
231+ .{ "entry.intersectionRatio;" , "1" },
232+ .{ "entry.intersectionRect.x;" , "1" },
233+ .{ "entry.intersectionRect.y;" , "0" },
234+ .{ "entry.intersectionRect.width;" , "1" },
235+ .{ "entry.intersectionRect.height;" , "1" },
236+ .{ "entry.isIntersecting;" , "true" },
237+ .{ "entry.rootBounds.x;" , "2" }, // This is not the prefered behaviour, the Window rect should wrap all elements so x -> 0
238+ .{ "entry.rootBounds.width;" , "1" }, // width -> clientWidth
239+ .{ "entry.rootBounds.height;" , "1" },
240+ .{ "entry.target;" , "[object HTMLDivElement]" },
241+ }, .{});
242+
243+ // Options
244+ try runner .testCases (&.{
245+ .{ "const new_root = document.createElement('span');" , "undefined" },
246+ .{ "let new_entry;" , "undefined" },
247+ .{
248+ \\ const new_observer = new IntersectionObserver(
249+ \\ entries => { new_entry = entries[0]; },
250+ \\ {root: new_root, rootMargin: '0px 0px 0px 0px', threshold: [0]});
251+ ,
252+ "undefined" ,
253+ },
254+ .{ "new_observer.observe(document.createElement('div'));" , "undefined" },
255+ .{ "new_entry.rootBounds.x;" , "3" },
256+ }, .{});
257+ }
0 commit comments