@granite-js/react-native 1.0.29 → 1.0.31

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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @granite-js/react-native
2
2
 
3
+ ## 1.0.31
4
+
5
+ ### Patch Changes
6
+
7
+ - f8aee14: fix: impression area debounced handlers
8
+ - @granite-js/blur-view@1.0.31
9
+ - @granite-js/brownfield-module@1.0.31
10
+ - @granite-js/cli@1.0.31
11
+ - @granite-js/jest@1.0.31
12
+ - @granite-js/mpack@1.0.31
13
+ - @granite-js/native@1.0.31
14
+ - @granite-js/plugin-core@1.0.31
15
+ - @granite-js/style-utils@1.0.31
16
+ - @granite-js/video@1.0.31
17
+
18
+ ## 1.0.30
19
+
20
+ ### Patch Changes
21
+
22
+ - Updated dependencies [4b2e737]
23
+ - @granite-js/video@1.0.30
24
+ - @granite-js/native@1.0.30
25
+ - @granite-js/blur-view@1.0.30
26
+ - @granite-js/jest@1.0.30
27
+ - @granite-js/style-utils@1.0.30
28
+ - @granite-js/brownfield-module@1.0.30
29
+ - @granite-js/cli@1.0.30
30
+ - @granite-js/mpack@1.0.30
31
+ - @granite-js/plugin-core@1.0.30
32
+
3
33
  ## 1.0.29
4
34
 
5
35
  ### Patch Changes
@@ -39,11 +39,13 @@ export interface IntersectionObserverOptions {
39
39
  */
40
40
  root: Root;
41
41
  rootMargin?: RootMargin;
42
- threshold?: number | number[];
42
+ onLayoutThreshold?: number;
43
+ onScrollThreshold?: number;
43
44
  }
44
45
  export type IntersectionObserverCallback = (entries: IntersectionObserverEntry[]) => void;
45
46
  export declare const defaultRootMargin: RootMargin;
46
- export declare const defaultThreshold = 0;
47
+ export declare const defaultOnLayoutThreshold = 300;
48
+ export declare const defaultOnScrollThreshold = 100;
47
49
  /**
48
50
  * @kind class
49
51
  * @name IntersectionObserver
@@ -57,10 +59,12 @@ declare class IntersectionObserver {
57
59
  protected callback: IntersectionObserverCallback;
58
60
  protected options: IntersectionObserverOptions;
59
61
  protected targets: Element[];
62
+ protected onLayoutHandler: () => void;
63
+ protected onScrollHandler: () => void;
60
64
  constructor(callback: IntersectionObserverCallback, options: IntersectionObserverOptions);
61
65
  protected measureTarget: (target: Element) => void;
62
- protected handleLayout: () => void;
63
- protected handleScroll: () => void;
66
+ protected createOnLayoutHandler(): () => void;
67
+ protected createOnScrollHandler(): import("es-toolkit").ThrottledFunction<() => void>;
64
68
  observe(target: Element): void;
65
69
  unobserve(target: Element): void;
66
70
  }
@@ -1,6 +1,6 @@
1
1
  import { type ComponentProps } from 'react';
2
2
  import { ScrollView } from 'react-native';
3
- import { RootMargin } from './IntersectionObserver';
3
+ import type { IntersectionObserverOptions, RootMargin } from './IntersectionObserver';
4
4
  export interface IOComponentProps {
5
5
  rootMargin?: RootMargin;
6
6
  }
@@ -16,5 +16,5 @@ export interface IOComponentProps {
16
16
  * @param {string[]} [methods] - List of event handler names from BaseComponent to be bound.
17
17
  * @returns {React.ComponentType} - Returns a wrapped component that can use Intersection Observer functionality.
18
18
  */
19
- declare function withIO<CompProps extends Pick<ComponentProps<typeof ScrollView>, 'horizontal' | 'scrollEventThrottle' | 'onContentSizeChange' | 'onLayout' | 'onScroll'>>(BaseComponent: React.ComponentType<CompProps>, methods: string[]): typeof BaseComponent;
19
+ declare function withIO<CompProps extends Pick<ComponentProps<typeof ScrollView>, 'horizontal' | 'scrollEventThrottle' | 'onContentSizeChange' | 'onLayout' | 'onScroll'>>(BaseComponent: React.ComponentType<CompProps>, methods: string[], options?: Omit<IntersectionObserverOptions, 'root' | 'rootMargin'>): typeof BaseComponent;
20
20
  export default withIO;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@granite-js/react-native",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "The Granite Framework",
5
5
  "bin": {
6
6
  "granite": "./bin/cli.js"
@@ -91,7 +91,7 @@
91
91
  "@babel/core": "7.28.5",
92
92
  "@babel/preset-env": "7.28.5",
93
93
  "@babel/preset-typescript": "7.28.5",
94
- "@granite-js/native": "1.0.29",
94
+ "@granite-js/native": "1.0.31",
95
95
  "@testing-library/dom": "^10.4.0",
96
96
  "@testing-library/react": "^16.1.0",
97
97
  "@types/babel__core": "^7",
@@ -113,21 +113,21 @@
113
113
  "zod": "^4.1.12"
114
114
  },
115
115
  "peerDependencies": {
116
- "@granite-js/native": "1.0.29",
116
+ "@granite-js/native": "1.0.31",
117
117
  "@types/react": "*",
118
118
  "brick-module": "*",
119
119
  "react": "*",
120
120
  "react-native": "*"
121
121
  },
122
122
  "dependencies": {
123
- "@granite-js/blur-view": "1.0.29",
124
- "@granite-js/brownfield-module": "1.0.29",
125
- "@granite-js/cli": "1.0.29",
126
- "@granite-js/jest": "1.0.29",
127
- "@granite-js/mpack": "1.0.29",
128
- "@granite-js/plugin-core": "1.0.29",
129
- "@granite-js/style-utils": "1.0.29",
130
- "@granite-js/video": "1.0.29",
123
+ "@granite-js/blur-view": "1.0.31",
124
+ "@granite-js/brownfield-module": "1.0.31",
125
+ "@granite-js/cli": "1.0.31",
126
+ "@granite-js/jest": "1.0.31",
127
+ "@granite-js/mpack": "1.0.31",
128
+ "@granite-js/plugin-core": "1.0.31",
129
+ "@granite-js/style-utils": "1.0.31",
130
+ "@granite-js/video": "1.0.31",
131
131
  "@standard-schema/spec": "^1.0.0",
132
132
  "es-toolkit": "^1.39.8",
133
133
  "react-native-url-polyfill": "3.0.0"
@@ -45,7 +45,8 @@ export interface IntersectionObserverOptions {
45
45
  */
46
46
  root: Root;
47
47
  rootMargin?: RootMargin;
48
- threshold?: number | number[];
48
+ onLayoutThreshold?: number;
49
+ onScrollThreshold?: number;
49
50
  }
50
51
 
51
52
  export type IntersectionObserverCallback = (entries: IntersectionObserverEntry[]) => void;
@@ -57,7 +58,8 @@ export const defaultRootMargin: RootMargin = {
57
58
  bottom: 0,
58
59
  };
59
60
 
60
- export const defaultThreshold = 0;
61
+ export const defaultOnLayoutThreshold = 300;
62
+ export const defaultOnScrollThreshold = 100;
61
63
 
62
64
  /**
63
65
  * @kind class
@@ -72,13 +74,15 @@ class IntersectionObserver {
72
74
  protected callback: IntersectionObserverCallback;
73
75
  protected options: IntersectionObserverOptions;
74
76
  protected targets: Element[];
77
+ protected onLayoutHandler: () => void;
78
+ protected onScrollHandler: () => void;
75
79
 
76
80
  constructor(callback: IntersectionObserverCallback, options: IntersectionObserverOptions) {
77
81
  this.callback = callback;
78
82
  this.options = options;
79
83
  this.targets = [];
80
- this.options.root.onLayout = this.handleLayout;
81
- this.options.root.onScroll = this.handleScroll;
84
+ this.onLayoutHandler = this.options.root.onLayout = this.createOnLayoutHandler();
85
+ this.onScrollHandler = this.options.root.onScroll = this.createOnScrollHandler();
82
86
  }
83
87
 
84
88
  protected measureTarget = (target: Element) => {
@@ -91,111 +95,117 @@ class IntersectionObserver {
91
95
  width,
92
96
  height,
93
97
  };
94
- this.handleScroll();
98
+ this.onScrollHandler();
95
99
  });
96
100
  }
97
101
  };
98
102
 
99
- protected handleLayout = throttle(
100
- () => {
101
- for (let index = 0; index < this.targets.length; index += 1) {
102
- const target = this.targets[index];
103
+ protected createOnLayoutHandler(): () => void {
104
+ const onLayoutThreshold = this.options?.onLayoutThreshold || defaultOnLayoutThreshold;
105
+ return throttle(
106
+ () => {
107
+ for (let index = 0; index < this.targets.length; index += 1) {
108
+ const target = this.targets[index];
103
109
 
104
- if (target != null) {
105
- this.measureTarget(target);
106
- }
107
- }
108
- },
109
- 300,
110
- { edges: ['trailing'] }
111
- ) as () => void;
112
-
113
- protected handleScroll = throttle(
114
- () => {
115
- const rootMargin = this.options?.rootMargin || defaultRootMargin;
116
-
117
- const {
118
- horizontal,
119
- current: { contentOffset, contentSize, layoutMeasurement },
120
- } = this.options.root;
121
- if (
122
- contentSize.width <= 0 ||
123
- contentSize.height <= 0 ||
124
- layoutMeasurement.width <= 0 ||
125
- layoutMeasurement.height <= 0
126
- ) {
127
- return;
128
- }
129
- const contentOffsetWithLayout = horizontal
130
- ? contentOffset.x + layoutMeasurement.width
131
- : contentOffset.y + layoutMeasurement.height;
132
- const changedTargets: IntersectionObserverEntry[] = [];
133
- for (let index = 0; index < this.targets.length; index += 1) {
134
- const target = this.targets[index];
135
-
136
- if (target == null) {
137
- continue;
138
- }
139
-
140
- const targetLayout = target.layout;
141
- if (!targetLayout || targetLayout.width === 0 || targetLayout.height === 0) {
142
- continue;
110
+ if (target != null) {
111
+ this.measureTarget(target);
112
+ }
143
113
  }
114
+ },
115
+ onLayoutThreshold,
116
+ { edges: ['leading', 'trailing'] }
117
+ );
118
+ }
144
119
 
145
- const previousIntersectionRatio = target.intersectionRatio;
146
-
147
- let isIntersecting = false;
148
- let intersectionRatio = previousIntersectionRatio;
149
-
150
- if (horizontal) {
151
- const visibleTargetMinX = Math.max(contentOffset.x - (rootMargin.left || 0), targetLayout.x);
152
- const visibleTargetMaxX = Math.min(
153
- contentOffsetWithLayout + (rootMargin.left || 0),
154
- targetLayout.x + targetLayout.width
155
- );
156
- const visibleHeight = Math.max(visibleTargetMaxX - visibleTargetMinX, 0);
157
-
158
- intersectionRatio = visibleHeight / targetLayout.height;
159
- isIntersecting =
160
- contentOffsetWithLayout + (rootMargin.right || 0) >= targetLayout.x &&
161
- contentOffset.x - (rootMargin.left || 0) <= targetLayout.x + targetLayout.width;
162
- } else {
163
- const visibleTargetMinY = Math.max(contentOffset.y - (rootMargin.top || 0), targetLayout.y);
164
- const visibleTargetMaxY = Math.min(
165
- contentOffsetWithLayout + (rootMargin.bottom || 0),
166
- targetLayout.y + targetLayout.height
167
- );
168
- const visibleHeight = Math.max(visibleTargetMaxY - visibleTargetMinY, 0);
169
-
170
- intersectionRatio = visibleHeight / targetLayout.height;
171
- isIntersecting =
172
- contentOffsetWithLayout + (rootMargin.bottom || 0) >= targetLayout.y &&
173
- contentOffset.y - (rootMargin.top || 0) <= targetLayout.y + targetLayout.height;
120
+ protected createOnScrollHandler() {
121
+ const onScrollThreshold = this.options?.onScrollThreshold || defaultOnScrollThreshold;
122
+ return throttle(
123
+ () => {
124
+ const rootMargin = this.options?.rootMargin || defaultRootMargin;
125
+
126
+ const {
127
+ horizontal,
128
+ current: { contentOffset, contentSize, layoutMeasurement },
129
+ } = this.options.root;
130
+ if (
131
+ contentSize.width <= 0 ||
132
+ contentSize.height <= 0 ||
133
+ layoutMeasurement.width <= 0 ||
134
+ layoutMeasurement.height <= 0
135
+ ) {
136
+ return;
174
137
  }
175
-
176
- intersectionRatio = Math.floor(intersectionRatio * 100) / 100;
177
-
178
- if (target.inView !== isIntersecting || target.intersectionRatio !== intersectionRatio) {
179
- target.inView = isIntersecting;
180
- target.intersectionRatio = intersectionRatio;
181
-
182
- changedTargets.push({
183
- target,
184
- isIntersecting,
185
- intersectionRatio,
186
- });
138
+ const contentOffsetWithLayout = horizontal
139
+ ? contentOffset.x + layoutMeasurement.width
140
+ : contentOffset.y + layoutMeasurement.height;
141
+ const changedTargets: IntersectionObserverEntry[] = [];
142
+ for (let index = 0; index < this.targets.length; index += 1) {
143
+ const target = this.targets[index];
144
+
145
+ if (target == null) {
146
+ continue;
147
+ }
148
+
149
+ const targetLayout = target.layout;
150
+ if (!targetLayout || targetLayout.width === 0 || targetLayout.height === 0) {
151
+ continue;
152
+ }
153
+
154
+ const previousIntersectionRatio = target.intersectionRatio;
155
+
156
+ let isIntersecting = false;
157
+ let intersectionRatio = previousIntersectionRatio;
158
+
159
+ if (horizontal) {
160
+ const visibleTargetMinX = Math.max(contentOffset.x - (rootMargin.left || 0), targetLayout.x);
161
+ const visibleTargetMaxX = Math.min(
162
+ contentOffsetWithLayout + (rootMargin.left || 0),
163
+ targetLayout.x + targetLayout.width
164
+ );
165
+ const visibleHeight = Math.max(visibleTargetMaxX - visibleTargetMinX, 0);
166
+
167
+ intersectionRatio = visibleHeight / targetLayout.height;
168
+ isIntersecting =
169
+ contentOffsetWithLayout + (rootMargin.right || 0) >= targetLayout.x &&
170
+ contentOffset.x - (rootMargin.left || 0) <= targetLayout.x + targetLayout.width;
171
+ } else {
172
+ const visibleTargetMinY = Math.max(contentOffset.y - (rootMargin.top || 0), targetLayout.y);
173
+ const visibleTargetMaxY = Math.min(
174
+ contentOffsetWithLayout + (rootMargin.bottom || 0),
175
+ targetLayout.y + targetLayout.height
176
+ );
177
+ const visibleHeight = Math.max(visibleTargetMaxY - visibleTargetMinY, 0);
178
+
179
+ intersectionRatio = visibleHeight / targetLayout.height;
180
+ isIntersecting =
181
+ contentOffsetWithLayout + (rootMargin.bottom || 0) >= targetLayout.y &&
182
+ contentOffset.y - (rootMargin.top || 0) <= targetLayout.y + targetLayout.height;
183
+ }
184
+
185
+ intersectionRatio = Math.floor(intersectionRatio * 100) / 100;
186
+
187
+ if (target.inView !== isIntersecting || target.intersectionRatio !== intersectionRatio) {
188
+ target.inView = isIntersecting;
189
+ target.intersectionRatio = intersectionRatio;
190
+
191
+ changedTargets.push({
192
+ target,
193
+ isIntersecting,
194
+ intersectionRatio,
195
+ });
196
+ }
187
197
  }
188
- }
189
- this.callback(changedTargets);
190
- },
191
- 100,
192
- { edges: ['trailing'] }
193
- ) as () => void;
198
+ this.callback(changedTargets);
199
+ },
200
+ onScrollThreshold,
201
+ { edges: ['leading', 'trailing'] }
202
+ );
203
+ }
194
204
 
195
205
  public observe(target: Element) {
196
206
  const index = this.targets.indexOf(target);
197
207
  if (index < 0) {
198
- target.onLayout = this.handleLayout;
208
+ target.onLayout = this.onLayoutHandler;
199
209
  this.targets.push(target);
200
210
  }
201
211
  }
@@ -2,7 +2,7 @@ import { type ComponentProps, PureComponent, RefObject, createRef } from 'react'
2
2
  import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView, View } from 'react-native';
3
3
  import IOContext, { IOContextValue } from './IOContext';
4
4
  import IOManager from './IOManager';
5
- import { Root, RootMargin } from './IntersectionObserver';
5
+ import type { IntersectionObserverOptions, Root, RootMargin } from './IntersectionObserver';
6
6
 
7
7
  export interface IOComponentProps {
8
8
  rootMargin?: RootMargin;
@@ -25,7 +25,11 @@ function withIO<
25
25
  ComponentProps<typeof ScrollView>,
26
26
  'horizontal' | 'scrollEventThrottle' | 'onContentSizeChange' | 'onLayout' | 'onScroll'
27
27
  >,
28
- >(BaseComponent: React.ComponentType<CompProps>, methods: string[]) {
28
+ >(
29
+ BaseComponent: React.ComponentType<CompProps>,
30
+ methods: string[],
31
+ options?: Omit<IntersectionObserverOptions, 'root' | 'rootMargin'>
32
+ ) {
29
33
  type ScrollableComponentProps = CompProps & IOComponentProps;
30
34
  const IOScrollableComponent = class extends PureComponent<ScrollableComponentProps> {
31
35
  protected node: any;
@@ -75,6 +79,7 @@ function withIO<
75
79
  get rootMargin() {
76
80
  return self.props.rootMargin;
77
81
  },
82
+ ...options,
78
83
  });
79
84
  this.manager = manager;
80
85
  this.contextValue = {