@griddo/ax 11.12.1-rc.0 → 11.12.1-rc.10
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/package.json +2 -2
- package/src/__tests__/components/Browser/Browser.test.tsx +1 -1
- package/src/__tests__/components/Fields/IntegrationsField/IntegrationsField.test.tsx +1 -1
- package/src/__tests__/components/Fields/UrlField/UrlField.test.tsx +19 -1
- package/src/__tests__/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/ErrorItem.test.tsx +1 -0
- package/src/__tests__/hooks/modals.test.tsx +303 -0
- package/src/components/Browser/index.tsx +46 -38
- package/src/components/Browser/style.tsx +1 -1
- package/src/components/FloatingPanel/index.tsx +2 -1
- package/src/components/HeadingsPreviewModal/ErrorsBanner/ErrorItem/index.tsx +8 -2
- package/src/components/HeadingsPreviewModal/ErrorsBanner/index.tsx +19 -4
- package/src/components/HeadingsPreviewModal/index.tsx +1 -0
- package/src/components/HeadingsPreviewModal/utils.tsx +12 -34
- package/src/components/KeywordsPreviewModal/index.tsx +13 -2
- package/src/components/Modal/index.tsx +1 -0
- package/src/components/ResizePanel/index.tsx +18 -9
- package/src/components/ResizePanel/style.tsx +2 -2
- package/src/hooks/modals.tsx +11 -24
- package/src/modules/FramePreview/HeadingsOverlay/index.tsx +21 -5
- package/src/modules/FramePreview/HeadingsOverlay/style.tsx +2 -2
- package/src/modules/FramePreview/utils.tsx +4 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@griddo/ax",
|
|
3
3
|
"description": "Griddo Author Experience",
|
|
4
|
-
"version": "11.12.1-rc.
|
|
4
|
+
"version": "11.12.1-rc.10",
|
|
5
5
|
"authors": [
|
|
6
6
|
"Álvaro Sánchez' <alvaro.sanches@secuoyas.com>",
|
|
7
7
|
"Diego M. Béjar <diego.bejar@secuoyas.com>",
|
|
@@ -217,5 +217,5 @@
|
|
|
217
217
|
"publishConfig": {
|
|
218
218
|
"access": "public"
|
|
219
219
|
},
|
|
220
|
-
"gitHead": "
|
|
220
|
+
"gitHead": "eb32fff3cb2388110e448459eca5a251580ffc77"
|
|
221
221
|
}
|
|
@@ -70,7 +70,7 @@ describe("Browser component rendering", () => {
|
|
|
70
70
|
|
|
71
71
|
renderBrowser(defaultProps);
|
|
72
72
|
|
|
73
|
-
expect(screen.
|
|
73
|
+
expect(screen.queryByTestId("nav-actions-wrapper")).not.toBeInTheDocument();
|
|
74
74
|
expect(screen.queryByTestId("navbar-iframe-wrapper")).not.toBeInTheDocument();
|
|
75
75
|
expect(screen.getByTestId("browser-content-wrapper")).toBeInTheDocument();
|
|
76
76
|
});
|
|
@@ -312,7 +312,7 @@ describe("Events", () => {
|
|
|
312
312
|
fireEvent.click(actionMenu[1]);
|
|
313
313
|
|
|
314
314
|
const buttonsDefault = screen.getAllByTestId("button-default");
|
|
315
|
-
fireEvent.click(buttonsDefault[
|
|
315
|
+
fireEvent.click(buttonsDefault[buttonsDefault.length - 1]);
|
|
316
316
|
|
|
317
317
|
const integrationsWithDeletion = initialStore.integrations.integrations.slice(0, 3);
|
|
318
318
|
|
|
@@ -290,6 +290,12 @@ describe("UrlField component rendering", () => {
|
|
|
290
290
|
render(Component, { store });
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
+
// Open FloatingPanel first
|
|
294
|
+
const pageField = screen.getByTestId("page-field");
|
|
295
|
+
await act(async () => {
|
|
296
|
+
fireEvent.click(pageField);
|
|
297
|
+
});
|
|
298
|
+
|
|
293
299
|
const selectComponents = screen.getAllByTestId("select-component");
|
|
294
300
|
|
|
295
301
|
await act(async () => {
|
|
@@ -384,6 +390,12 @@ describe("onClick events", () => {
|
|
|
384
390
|
render(Component, { store });
|
|
385
391
|
});
|
|
386
392
|
|
|
393
|
+
// Open FloatingPanel first
|
|
394
|
+
const pageField = screen.getByTestId("page-field");
|
|
395
|
+
await act(async () => {
|
|
396
|
+
fireEvent.click(pageField);
|
|
397
|
+
});
|
|
398
|
+
|
|
387
399
|
await act(async () => {
|
|
388
400
|
const selectionListItem = screen.getAllByTestId("selection-list-item");
|
|
389
401
|
selectionListItem[0].click();
|
|
@@ -430,10 +442,16 @@ describe("onClick events", () => {
|
|
|
430
442
|
render(Component, { store });
|
|
431
443
|
});
|
|
432
444
|
|
|
433
|
-
const selectionListItem = screen.getAllByTestId("selection-list-item");
|
|
434
445
|
const checkFieldInputs = screen.getAllByTestId("check-field-input");
|
|
435
446
|
const pageField = screen.getByTestId("page-field");
|
|
436
447
|
|
|
448
|
+
// Open FloatingPanel first
|
|
449
|
+
await act(async () => {
|
|
450
|
+
fireEvent.click(pageField);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const selectionListItem = screen.getAllByTestId("selection-list-item");
|
|
454
|
+
|
|
437
455
|
await act(async () => {
|
|
438
456
|
selectionListItem[0].click();
|
|
439
457
|
fireEvent.click(checkFieldInputs[0]);
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { act, renderHook } from "@testing-library/react";
|
|
2
|
+
import "@testing-library/jest-dom";
|
|
3
|
+
|
|
4
|
+
import { useModal, useModals } from "@ax/hooks";
|
|
5
|
+
|
|
6
|
+
describe("useModal hook", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
document.body.classList.remove("modal-open");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
document.body.classList.remove("modal-open");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("should initialize with false when no initialState provided", () => {
|
|
16
|
+
const { result } = renderHook(() => useModal());
|
|
17
|
+
expect(result.current.isOpen).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("should initialize with provided initialState", () => {
|
|
21
|
+
const { result } = renderHook(() => useModal(true));
|
|
22
|
+
expect(result.current.isOpen).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("should toggle modal state when toggleModal is called", () => {
|
|
26
|
+
const { result } = renderHook(() => useModal(false));
|
|
27
|
+
expect(result.current.isOpen).toBe(false);
|
|
28
|
+
|
|
29
|
+
act(() => {
|
|
30
|
+
result.current.toggleModal();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
expect(result.current.isOpen).toBe(true);
|
|
34
|
+
|
|
35
|
+
act(() => {
|
|
36
|
+
result.current.toggleModal();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
expect(result.current.isOpen).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("should add modal-open class to body when modal opens and bodyBlock is true", () => {
|
|
43
|
+
const { result } = renderHook(() => useModal(false, true));
|
|
44
|
+
expect(document.body.classList.contains("modal-open")).toBe(false);
|
|
45
|
+
|
|
46
|
+
act(() => {
|
|
47
|
+
result.current.toggleModal();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should not add modal-open class to body when bodyBlock is false", () => {
|
|
54
|
+
const { result } = renderHook(() => useModal(false, false));
|
|
55
|
+
|
|
56
|
+
act(() => {
|
|
57
|
+
result.current.toggleModal();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
expect(document.body.classList.contains("modal-open")).toBe(false);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should check if modal-open class already exists before adding", () => {
|
|
64
|
+
document.body.classList.add("modal-open");
|
|
65
|
+
|
|
66
|
+
const { result } = renderHook(() => useModal(false, true));
|
|
67
|
+
|
|
68
|
+
act(() => {
|
|
69
|
+
result.current.toggleModal();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Class should still be present
|
|
73
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
74
|
+
|
|
75
|
+
// Count of modal-open should be 1
|
|
76
|
+
const classList = [...document.body.classList];
|
|
77
|
+
const count = classList.filter((c) => c === "modal-open").length;
|
|
78
|
+
expect(count).toBe(1);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("useModals hook", () => {
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
document.body.classList.remove("modal-open");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
afterEach(() => {
|
|
88
|
+
document.body.classList.remove("modal-open");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should initialize all modals as closed", () => {
|
|
92
|
+
const { result } = renderHook(() => useModals(["modal1", "modal2", "modal3"]));
|
|
93
|
+
|
|
94
|
+
expect(result.current.isOpen("modal1")).toBe(false);
|
|
95
|
+
expect(result.current.isOpen("modal2")).toBe(false);
|
|
96
|
+
expect(result.current.isOpen("modal3")).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("should open a modal with openModal", () => {
|
|
100
|
+
const { result } = renderHook(() => useModals(["modal1", "modal2"]));
|
|
101
|
+
|
|
102
|
+
act(() => {
|
|
103
|
+
result.current.openModal("modal1");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
expect(result.current.isOpen("modal1")).toBe(true);
|
|
107
|
+
expect(result.current.isOpen("modal2")).toBe(false);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should close a modal with closeModal", () => {
|
|
111
|
+
const { result } = renderHook(() => useModals(["modal1"]));
|
|
112
|
+
|
|
113
|
+
act(() => {
|
|
114
|
+
result.current.openModal("modal1");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result.current.isOpen("modal1")).toBe(true);
|
|
118
|
+
|
|
119
|
+
act(() => {
|
|
120
|
+
result.current.closeModal("modal1");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(result.current.isOpen("modal1")).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should toggle a modal with toggleModal", () => {
|
|
127
|
+
const { result } = renderHook(() => useModals(["modal1"]));
|
|
128
|
+
|
|
129
|
+
expect(result.current.isOpen("modal1")).toBe(false);
|
|
130
|
+
|
|
131
|
+
act(() => {
|
|
132
|
+
result.current.toggleModal("modal1");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(result.current.isOpen("modal1")).toBe(true);
|
|
136
|
+
|
|
137
|
+
act(() => {
|
|
138
|
+
result.current.toggleModal("modal1");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(result.current.isOpen("modal1")).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("should handle multiple modals independently", () => {
|
|
145
|
+
const { result } = renderHook(() => useModals(["modal1", "modal2", "modal3"]));
|
|
146
|
+
|
|
147
|
+
act(() => {
|
|
148
|
+
result.current.openModal("modal1");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
expect(result.current.isOpen("modal1")).toBe(true);
|
|
152
|
+
expect(result.current.isOpen("modal2")).toBe(false);
|
|
153
|
+
expect(result.current.isOpen("modal3")).toBe(false);
|
|
154
|
+
|
|
155
|
+
act(() => {
|
|
156
|
+
result.current.openModal("modal2");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(result.current.isOpen("modal1")).toBe(true);
|
|
160
|
+
expect(result.current.isOpen("modal2")).toBe(true);
|
|
161
|
+
expect(result.current.isOpen("modal3")).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("should add modal-open class when a modal is opened", () => {
|
|
165
|
+
const { result } = renderHook(() => useModals(["modal1"], true));
|
|
166
|
+
|
|
167
|
+
expect(document.body.classList.contains("modal-open")).toBe(false);
|
|
168
|
+
|
|
169
|
+
act(() => {
|
|
170
|
+
result.current.openModal("modal1");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("should remove modal-open class when all modals are closed", () => {
|
|
177
|
+
const { result } = renderHook(() => useModals(["modal1", "modal2"], true));
|
|
178
|
+
|
|
179
|
+
act(() => {
|
|
180
|
+
result.current.openModal("modal1");
|
|
181
|
+
result.current.openModal("modal2");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
185
|
+
|
|
186
|
+
act(() => {
|
|
187
|
+
result.current.closeModal("modal1");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
191
|
+
|
|
192
|
+
act(() => {
|
|
193
|
+
result.current.closeModal("modal2");
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
expect(document.body.classList.contains("modal-open")).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should close multiple modals individually", () => {
|
|
200
|
+
const { result } = renderHook(() => useModals(["modal1", "modal2", "modal3"]));
|
|
201
|
+
|
|
202
|
+
act(() => {
|
|
203
|
+
result.current.openModal("modal1");
|
|
204
|
+
result.current.openModal("modal2");
|
|
205
|
+
result.current.openModal("modal3");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(result.current.isOpen("modal1")).toBe(true);
|
|
209
|
+
expect(result.current.isOpen("modal2")).toBe(true);
|
|
210
|
+
expect(result.current.isOpen("modal3")).toBe(true);
|
|
211
|
+
|
|
212
|
+
act(() => {
|
|
213
|
+
result.current.closeModal("modal1");
|
|
214
|
+
result.current.closeModal("modal2");
|
|
215
|
+
result.current.closeModal("modal3");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(result.current.isOpen("modal1")).toBe(false);
|
|
219
|
+
expect(result.current.isOpen("modal2")).toBe(false);
|
|
220
|
+
expect(result.current.isOpen("modal3")).toBe(false);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("should not add modal-open class if bodyBlock is false", () => {
|
|
224
|
+
const { result } = renderHook(() => useModals(["modal1"], false));
|
|
225
|
+
|
|
226
|
+
act(() => {
|
|
227
|
+
result.current.openModal("modal1");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
expect(document.body.classList.contains("modal-open")).toBe(false);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it("should not duplicate modal-open class when multiple modals are open", () => {
|
|
234
|
+
const { result } = renderHook(() => useModals(["modal1", "modal2"], true));
|
|
235
|
+
|
|
236
|
+
act(() => {
|
|
237
|
+
result.current.openModal("modal1");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
const classList1 = [...document.body.classList];
|
|
241
|
+
const count1 = classList1.filter((c) => c === "modal-open").length;
|
|
242
|
+
|
|
243
|
+
act(() => {
|
|
244
|
+
result.current.openModal("modal2");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const classList2 = [...document.body.classList];
|
|
248
|
+
const count2 = classList2.filter((c) => c === "modal-open").length;
|
|
249
|
+
|
|
250
|
+
expect(count1).toBe(1);
|
|
251
|
+
expect(count2).toBe(1);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should handle isOpen callback correctly", () => {
|
|
255
|
+
const { result } = renderHook(() => useModals(["modal1"]));
|
|
256
|
+
|
|
257
|
+
act(() => {
|
|
258
|
+
result.current.openModal("modal1");
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
const isOpenResult = result.current.isOpen("modal1");
|
|
262
|
+
expect(isOpenResult).toBe(true);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
describe("useModal and useModals working together", () => {
|
|
267
|
+
beforeEach(() => {
|
|
268
|
+
document.body.classList.remove("modal-open");
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
afterEach(() => {
|
|
272
|
+
document.body.classList.remove("modal-open");
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("should verify both hooks add modal-open class independently", () => {
|
|
276
|
+
const { result: modalResult } = renderHook(() => useModal(false, true));
|
|
277
|
+
const { result: modalsResult } = renderHook(() => useModals(["modal1"], true));
|
|
278
|
+
|
|
279
|
+
// useModal should add class
|
|
280
|
+
act(() => {
|
|
281
|
+
modalResult.current.toggleModal();
|
|
282
|
+
});
|
|
283
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
284
|
+
|
|
285
|
+
// useModals should keep class
|
|
286
|
+
act(() => {
|
|
287
|
+
modalsResult.current.openModal("modal1");
|
|
288
|
+
});
|
|
289
|
+
expect(document.body.classList.contains("modal-open")).toBe(true);
|
|
290
|
+
|
|
291
|
+
// Closing useModal should keep class (useModals still open)
|
|
292
|
+
act(() => {
|
|
293
|
+
modalResult.current.toggleModal();
|
|
294
|
+
});
|
|
295
|
+
// Note: This depends on DOM check in useModal
|
|
296
|
+
|
|
297
|
+
// Closing useModals should remove class
|
|
298
|
+
act(() => {
|
|
299
|
+
modalsResult.current.closeModal("modal1");
|
|
300
|
+
});
|
|
301
|
+
expect(document.body.classList.contains("modal-open")).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -63,6 +63,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
63
63
|
const isFormEditor = editorType === "form";
|
|
64
64
|
const isHeadingsEditor = editorType === "headings";
|
|
65
65
|
const isKeywordsEditor = editorType === "keywords";
|
|
66
|
+
const isZoomEditor = isPageEditor || isHeadingsEditor || isKeywordsEditor;
|
|
66
67
|
|
|
67
68
|
const frameWrapperRef = useRef<HTMLDivElement>(null);
|
|
68
69
|
|
|
@@ -82,6 +83,8 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
82
83
|
localStorage.setItem("selectedID", "0");
|
|
83
84
|
(window as any).browserRef = null;
|
|
84
85
|
|
|
86
|
+
if (!isZoomEditor) return;
|
|
87
|
+
|
|
85
88
|
const el = frameWrapperRef.current;
|
|
86
89
|
if (!el) return;
|
|
87
90
|
|
|
@@ -99,7 +102,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
99
102
|
observer.observe(el);
|
|
100
103
|
|
|
101
104
|
return () => observer.disconnect();
|
|
102
|
-
}, []);
|
|
105
|
+
}, [isZoomEditor]);
|
|
103
106
|
|
|
104
107
|
// Fetch share data when in preview mode
|
|
105
108
|
useEffect(() => {
|
|
@@ -188,7 +191,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
188
191
|
}
|
|
189
192
|
};
|
|
190
193
|
|
|
191
|
-
const scaledWidth =
|
|
194
|
+
const scaledWidth = isZoomEditor
|
|
192
195
|
? Math.floor(parseInt(dimensions.resolution) * (parseInt(dimensions.zoom) / 100))
|
|
193
196
|
: undefined;
|
|
194
197
|
|
|
@@ -201,8 +204,13 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
201
204
|
|
|
202
205
|
return (
|
|
203
206
|
<S.OuterContainer ref={frameWrapperRef}>
|
|
204
|
-
<S.BrowserWrapper
|
|
205
|
-
|
|
207
|
+
<S.BrowserWrapper
|
|
208
|
+
data-testid="browser-wrapper"
|
|
209
|
+
ref={browserRef}
|
|
210
|
+
scaledWidth={scaledWidth}
|
|
211
|
+
isPreview={!isZoomEditor}
|
|
212
|
+
>
|
|
213
|
+
{isZoomEditor && (
|
|
206
214
|
<S.NavBar>
|
|
207
215
|
<S.NavUrl>{url}</S.NavUrl>
|
|
208
216
|
{isPreview ? (
|
|
@@ -247,30 +255,34 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
247
255
|
</S.IconWrapper>
|
|
248
256
|
</S.NavActions>
|
|
249
257
|
) : (
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
<S.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
258
|
+
<>
|
|
259
|
+
{showIframe && (
|
|
260
|
+
<S.NavActions data-testid="nav-actions-wrapper">
|
|
261
|
+
<S.ResolutionWrapper>
|
|
262
|
+
<S.SelectLabel>Screen</S.SelectLabel>
|
|
263
|
+
<Select
|
|
264
|
+
name="resolution"
|
|
265
|
+
options={resolutionOptions}
|
|
266
|
+
value={dimensions.resolution}
|
|
267
|
+
onChange={handleResolutionChange}
|
|
268
|
+
type="round"
|
|
269
|
+
offSet="30%"
|
|
270
|
+
mandatory
|
|
271
|
+
/>
|
|
272
|
+
</S.ResolutionWrapper>
|
|
273
|
+
<S.ZoomWrapper>
|
|
274
|
+
<Select
|
|
275
|
+
name="zoom"
|
|
276
|
+
options={zoomOptionsWithDisabled}
|
|
277
|
+
value={dimensions.zoom}
|
|
278
|
+
onChange={handleZoomChange}
|
|
279
|
+
type="round"
|
|
280
|
+
mandatory
|
|
281
|
+
/>
|
|
282
|
+
</S.ZoomWrapper>
|
|
283
|
+
</S.NavActions>
|
|
284
|
+
)}
|
|
285
|
+
</>
|
|
274
286
|
)}
|
|
275
287
|
</S.NavBar>
|
|
276
288
|
)}
|
|
@@ -284,11 +296,7 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
284
296
|
$compact={isCompact}
|
|
285
297
|
/>
|
|
286
298
|
)}
|
|
287
|
-
<S.FrameWrapper
|
|
288
|
-
hasBorder={isPageEditor || isHeadingsEditor || isKeywordsEditor}
|
|
289
|
-
isFormEditor={isFormEditor}
|
|
290
|
-
data-testid="navbar-iframe-wrapper"
|
|
291
|
-
>
|
|
299
|
+
<S.FrameWrapper hasBorder={isZoomEditor} isFormEditor={isFormEditor} data-testid="navbar-iframe-wrapper">
|
|
292
300
|
{isPreview ? (
|
|
293
301
|
<iframe
|
|
294
302
|
title="Preview"
|
|
@@ -301,23 +309,23 @@ const Browser = (props: IBrowserProps): JSX.Element => {
|
|
|
301
309
|
) : (
|
|
302
310
|
<div
|
|
303
311
|
style={{
|
|
304
|
-
width: `${scaledWidth}px
|
|
312
|
+
width: scaledWidth !== undefined ? `${scaledWidth}px` : "100%",
|
|
305
313
|
height: "100%",
|
|
306
314
|
overflow: "hidden",
|
|
307
|
-
flexShrink: 0,
|
|
315
|
+
flexShrink: isZoomEditor ? 0 : "unset",
|
|
308
316
|
}}
|
|
309
317
|
>
|
|
310
318
|
<iframe
|
|
311
319
|
title="Preview"
|
|
312
|
-
width={dimensions.resolution}
|
|
320
|
+
width={isZoomEditor ? dimensions.resolution : "100%"}
|
|
313
321
|
src={urlPreview}
|
|
314
322
|
loading="lazy"
|
|
315
323
|
className="frame-content"
|
|
316
324
|
style={{
|
|
317
325
|
display: "block",
|
|
318
|
-
transform: `scale(${parseInt(dimensions.zoom) / 100})
|
|
326
|
+
transform: isZoomEditor ? `scale(${parseInt(dimensions.zoom) / 100})` : "scale(1)",
|
|
319
327
|
transformOrigin: "0 0",
|
|
320
|
-
height: `${Math.round(100 / (parseInt(dimensions.zoom) / 100))}
|
|
328
|
+
height: isZoomEditor ? `${Math.round(100 / (parseInt(dimensions.zoom) / 100))}%` : "100%",
|
|
321
329
|
}}
|
|
322
330
|
/>
|
|
323
331
|
</div>
|
|
@@ -70,7 +70,7 @@ const FrameWrapper = styled.div<{ hasBorder: boolean; isFormEditor: boolean }>`
|
|
|
70
70
|
justify-content: center;
|
|
71
71
|
flex: 1;
|
|
72
72
|
min-height: 0;
|
|
73
|
-
padding: ${(p) => (p.isFormEditor ? p.theme.spacing.m : "0")};
|
|
73
|
+
padding: ${(p) => (p.isFormEditor ? `${p.theme.spacing.m} 0 0 ${p.theme.spacing.m}` : "0")};
|
|
74
74
|
`;
|
|
75
75
|
|
|
76
76
|
const OuterContainer = styled.div`
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { memo, useRef } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
3
|
|
|
4
|
-
import { useHandleClickOutside } from "@ax/hooks";
|
|
5
4
|
import { IconAction } from "@ax/components";
|
|
5
|
+
import { useHandleClickOutside } from "@ax/hooks";
|
|
6
6
|
|
|
7
7
|
import * as S from "./style";
|
|
8
8
|
|
|
@@ -43,6 +43,7 @@ const FloatingPanel = (props: IFloatingPanelProps): JSX.Element | null => {
|
|
|
43
43
|
return createPortal(
|
|
44
44
|
<S.Wrapper
|
|
45
45
|
data-testid="floating-panel"
|
|
46
|
+
data-is-open={isOpen}
|
|
46
47
|
ref={node}
|
|
47
48
|
isOpen={isOpen}
|
|
48
49
|
isOpenedSecond={isOpenedSecond}
|
|
@@ -7,7 +7,7 @@ import type { IHeadingError } from "../../utils";
|
|
|
7
7
|
import * as S from "./style";
|
|
8
8
|
|
|
9
9
|
const ErrorsItem = (props: IErrorsItemProps) => {
|
|
10
|
-
const { error, onSelectHeading } = props;
|
|
10
|
+
const { error, onSelectHeading, onDelete } = props;
|
|
11
11
|
const { message, description, headingIds } = error;
|
|
12
12
|
|
|
13
13
|
const [isOpen, setIsOpen] = useState(false);
|
|
@@ -54,7 +54,12 @@ const ErrorsItem = (props: IErrorsItemProps) => {
|
|
|
54
54
|
<Icon name={isOpen ? "UpArrow" : "DownArrow"} size="16" />
|
|
55
55
|
</S.IconWrapper>
|
|
56
56
|
)}
|
|
57
|
-
<S.IconWrapper
|
|
57
|
+
<S.IconWrapper
|
|
58
|
+
onClick={() => {
|
|
59
|
+
setIsDeleted(true);
|
|
60
|
+
onDelete();
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
58
63
|
<Icon name="close" size="16" />
|
|
59
64
|
</S.IconWrapper>
|
|
60
65
|
</S.ErrorActions>
|
|
@@ -80,6 +85,7 @@ const ErrorsItem = (props: IErrorsItemProps) => {
|
|
|
80
85
|
interface IErrorsItemProps {
|
|
81
86
|
error: IHeadingError;
|
|
82
87
|
onSelectHeading: (id: number) => () => void;
|
|
88
|
+
onDelete: () => void;
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
export default ErrorsItem;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
2
|
|
|
3
3
|
import { Icon } from "@ax/components";
|
|
4
4
|
|
|
@@ -11,8 +11,18 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
|
|
|
11
11
|
const { errors, onSelectHeading, isOpen, setIsOpen, resetKey } = props;
|
|
12
12
|
|
|
13
13
|
const [isDeleted, setIsDeleted] = useState(false);
|
|
14
|
+
const [deletedErrorIndices, setDeletedErrorIndices] = useState<Set<number>>(new Set());
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
const allErrorsDeleted = useMemo(
|
|
17
|
+
() => deletedErrorIndices.size === errors.length && errors.length > 0,
|
|
18
|
+
[deletedErrorIndices.size, errors.length],
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const handleErrorDelete = (index: number) => {
|
|
22
|
+
setDeletedErrorIndices((prev) => new Set([...prev, index]));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (isDeleted || allErrorsDeleted) {
|
|
16
26
|
return <></>;
|
|
17
27
|
}
|
|
18
28
|
|
|
@@ -37,8 +47,13 @@ const ErrorsBanner = (props: IErrorsBannerProps) => {
|
|
|
37
47
|
Review <strong>suggestions and warnings</strong> to enhance your page's search engine optimization.
|
|
38
48
|
</S.Description>
|
|
39
49
|
<S.ErrorListWrapper>
|
|
40
|
-
{errors.map((error) => (
|
|
41
|
-
<ErrorItem
|
|
50
|
+
{errors.map((error, index) => (
|
|
51
|
+
<ErrorItem
|
|
52
|
+
key={`${error.message}-${resetKey}`}
|
|
53
|
+
error={error}
|
|
54
|
+
onSelectHeading={onSelectHeading}
|
|
55
|
+
onDelete={() => handleErrorDelete(index)}
|
|
56
|
+
/>
|
|
42
57
|
))}
|
|
43
58
|
</S.ErrorListWrapper>
|
|
44
59
|
</S.ErrorsContent>
|
|
@@ -67,12 +67,16 @@ const analyzeHeadings = (headings: HeadingNode[], isFiltering = false): IHeading
|
|
|
67
67
|
|
|
68
68
|
// 2. Check for incorrect nesting (skipped levels)
|
|
69
69
|
const nestingErrorIds: number[] = [];
|
|
70
|
+
let lastValidLevel = flatHeadings[0].level;
|
|
70
71
|
for (let i = 1; i < flatHeadings.length; i++) {
|
|
71
|
-
const prevLevel = flatHeadings[i - 1].level;
|
|
72
72
|
const currLevel = flatHeadings[i].level;
|
|
73
73
|
|
|
74
|
-
if (currLevel >
|
|
74
|
+
if (currLevel > lastValidLevel + 1) {
|
|
75
|
+
// Heading skips levels - mark as error
|
|
75
76
|
nestingErrorIds.push(flatHeadings[i].id);
|
|
77
|
+
} else {
|
|
78
|
+
// Correct nesting - update last valid level
|
|
79
|
+
lastValidLevel = currLevel;
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
if (nestingErrorIds.length > 0) {
|
|
@@ -163,8 +167,8 @@ const parseHeadingsTree = (html: HTMLDivElement): HeadingNode[] => {
|
|
|
163
167
|
|
|
164
168
|
const style = iframeWindow.getComputedStyle(el);
|
|
165
169
|
|
|
166
|
-
// 1. Check CSS properties directly on element
|
|
167
|
-
if (style.display === "none" || style.visibility === "hidden"
|
|
170
|
+
// 1. Check CSS properties directly on element (only definite cases)
|
|
171
|
+
if (style.display === "none" || style.visibility === "hidden") {
|
|
168
172
|
return true;
|
|
169
173
|
}
|
|
170
174
|
|
|
@@ -175,33 +179,10 @@ const parseHeadingsTree = (html: HTMLDivElement): HeadingNode[] => {
|
|
|
175
179
|
|
|
176
180
|
// 3. Check dimensions (width or height is 0)
|
|
177
181
|
const rect = el.getBoundingClientRect();
|
|
178
|
-
if (rect.width === 0
|
|
182
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
179
183
|
return true;
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
// 4. Check if completely out of viewport
|
|
183
|
-
const frameRect = frameObject?.getBoundingClientRect();
|
|
184
|
-
if (frameRect) {
|
|
185
|
-
if (
|
|
186
|
-
rect.bottom < frameRect.top ||
|
|
187
|
-
rect.top > frameRect.bottom ||
|
|
188
|
-
rect.right < frameRect.left ||
|
|
189
|
-
rect.left > frameRect.right
|
|
190
|
-
) {
|
|
191
|
-
return true;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// 5. Check if element is covered by another element (elementFromPoint)
|
|
196
|
-
const centerX = rect.left + rect.width / 2;
|
|
197
|
-
const centerY = rect.top + rect.height / 2;
|
|
198
|
-
if (frameObject && centerX >= 0 && centerY >= 0 && centerX <= window.innerWidth && centerY <= window.innerHeight) {
|
|
199
|
-
const topElement = document.elementFromPoint(centerX, centerY);
|
|
200
|
-
if (topElement && topElement !== el && !el.contains(topElement)) {
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
186
|
return false;
|
|
206
187
|
};
|
|
207
188
|
|
|
@@ -230,17 +211,14 @@ const parseHeadingsTree = (html: HTMLDivElement): HeadingNode[] => {
|
|
|
230
211
|
}
|
|
231
212
|
}
|
|
232
213
|
|
|
233
|
-
// Check if ancestor is in collapsed navigation
|
|
214
|
+
// Check if ancestor is in collapsed navigation (only when explicitly collapsed)
|
|
234
215
|
const navSelector = "nav, header, [role='navigation'], .nav, .menu, .navbar, .sidebar";
|
|
235
216
|
if (node.matches(navSelector)) {
|
|
236
|
-
const ancestorRect = node.getBoundingClientRect();
|
|
237
217
|
const nodeHeight = parseFloat(style.height);
|
|
238
218
|
const nodeMaxHeight = parseFloat(style.maxHeight);
|
|
239
219
|
|
|
240
|
-
if
|
|
241
|
-
|
|
242
|
-
(ancestorRect.height === 0 || ancestorRect.height < 10)
|
|
243
|
-
) {
|
|
220
|
+
// Only mark as hidden if height is explicitly 0
|
|
221
|
+
if ((nodeHeight === 0 || nodeMaxHeight === 0) && style.overflow === "hidden") {
|
|
244
222
|
return true;
|
|
245
223
|
}
|
|
246
224
|
}
|
|
@@ -39,11 +39,22 @@ const KeywordsPreviewModal = (props: IKeywordsPreviewProps) => {
|
|
|
39
39
|
const handleDeleteKeyword = (value: string) => {
|
|
40
40
|
setDeletedKeyword(value);
|
|
41
41
|
deleteKeyword(value);
|
|
42
|
+
setKeywordsFilter([]);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleCloseModal = () => {
|
|
46
|
+
setKeywordsFilter([]);
|
|
47
|
+
toggleModal();
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleAddKeywords = (value: string[]) => {
|
|
51
|
+
setKeywordsFilter([]);
|
|
52
|
+
addKeywords(value);
|
|
42
53
|
};
|
|
43
54
|
|
|
44
55
|
return (
|
|
45
56
|
<S.Wrapper>
|
|
46
|
-
<FloatingPanel title="Keywords" toggleModal={
|
|
57
|
+
<FloatingPanel title="Keywords" toggleModal={handleCloseModal} closeOnOutsideClick={false} isOpen={isOpen} width={358}>
|
|
47
58
|
{isOpen && (
|
|
48
59
|
<S.KeywordsWrapper>
|
|
49
60
|
{keywordsFilter.length > 0 && (
|
|
@@ -79,7 +90,7 @@ const KeywordsPreviewModal = (props: IKeywordsPreviewProps) => {
|
|
|
79
90
|
</S.KeywordsWrapper>
|
|
80
91
|
)}
|
|
81
92
|
</FloatingPanel>
|
|
82
|
-
<AddKeywordsModal isOpen={isAddOpen} toggleModal={toggleAddModal} addNewKeyword={
|
|
93
|
+
<AddKeywordsModal isOpen={isAddOpen} toggleModal={toggleAddModal} addNewKeyword={handleAddKeywords} />
|
|
83
94
|
{isVisible && <Toast message={toastState} setIsVisible={setIsVisible} />}
|
|
84
95
|
</S.Wrapper>
|
|
85
96
|
);
|
|
@@ -11,18 +11,27 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
|
|
|
11
11
|
|
|
12
12
|
const [rwidth, setRwidth] = useState(MIN_WIDTH);
|
|
13
13
|
const rightPanelRef = useRef<HTMLDivElement>(null);
|
|
14
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
14
15
|
|
|
15
16
|
const calculateFixedPanelMinWidth = (currentWidth: number) => {
|
|
16
|
-
const
|
|
17
|
-
|
|
17
|
+
const containerWidth = containerRef.current?.offsetWidth ?? 1280;
|
|
18
|
+
const minWidth = Math.max(500, containerWidth - currentWidth - 32);
|
|
19
|
+
return `${minWidth}px`;
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
useEffect(() => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const updateWidth = () => {
|
|
24
|
+
requestAnimationFrame(() => {
|
|
25
|
+
if (rightPanelRef.current) {
|
|
26
|
+
setRwidth(rightPanelRef.current.offsetWidth);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
updateWidth();
|
|
32
|
+
|
|
33
|
+
window.addEventListener("resize", updateWidth);
|
|
34
|
+
return () => window.removeEventListener("resize", updateWidth);
|
|
26
35
|
}, []);
|
|
27
36
|
|
|
28
37
|
const resize = (value: number) => {
|
|
@@ -30,7 +39,7 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
|
|
|
30
39
|
};
|
|
31
40
|
|
|
32
41
|
return (
|
|
33
|
-
|
|
42
|
+
<div ref={containerRef} style={{ display: "flex", width: "100%", height: "100%" }}>
|
|
34
43
|
<S.LeftPanel data-testid="left-panel">
|
|
35
44
|
{fixed ? (
|
|
36
45
|
<S.FixedPanel
|
|
@@ -48,7 +57,7 @@ const ResizePanel = (props: IResizePanelProps): JSX.Element => {
|
|
|
48
57
|
<S.RightPanel ref={rightPanelRef} data-testid="right-panel" style={{ width: rwidth ? `${rwidth}px` : "auto" }}>
|
|
49
58
|
{rightPanel}
|
|
50
59
|
</S.RightPanel>
|
|
51
|
-
|
|
60
|
+
</div>
|
|
52
61
|
);
|
|
53
62
|
};
|
|
54
63
|
|
|
@@ -3,7 +3,7 @@ import styled from "styled-components";
|
|
|
3
3
|
const LeftPanel = styled.section`
|
|
4
4
|
position: relative;
|
|
5
5
|
flex-grow: 1;
|
|
6
|
-
min-width:
|
|
6
|
+
min-width: 620px;
|
|
7
7
|
`;
|
|
8
8
|
|
|
9
9
|
const RightPanel = styled.section`
|
|
@@ -12,7 +12,7 @@ const RightPanel = styled.section`
|
|
|
12
12
|
padding: ${(p) => `0 ${p.theme.spacing.m} ${p.theme.spacing.m} ${p.theme.spacing.m}`};
|
|
13
13
|
flex-shrink: 0;
|
|
14
14
|
min-width: 368px;
|
|
15
|
-
max-width: ${(p) => `calc(100% -
|
|
15
|
+
max-width: ${(p) => `calc(100% - 620px - ${p.theme.spacing.m})`};
|
|
16
16
|
flex-direction: column;
|
|
17
17
|
`;
|
|
18
18
|
|
package/src/hooks/modals.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
|
|
3
3
|
const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
4
4
|
const [isOpen, setIsOpen] = useState(initialState || false);
|
|
@@ -11,17 +11,10 @@ const useModal = (initialState?: boolean, bodyBlock = true) => {
|
|
|
11
11
|
if (!document.body.classList.contains("modal-open")) {
|
|
12
12
|
document.body.classList.add("modal-open");
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
document.body.classList.remove("modal-open");
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
} else if (!bodyBlock || !isOpen) {
|
|
22
|
-
// Solo eliminar si no hay modales abiertos
|
|
23
|
-
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
24
|
-
if (modals.length === 0) {
|
|
14
|
+
} else if (!isOpen && bodyBlock) {
|
|
15
|
+
// Solo remover si no hay otros modales o FloatingPanels abiertos
|
|
16
|
+
const hasOpenModals = document.querySelectorAll('[data-is-open="true"]').length > 0;
|
|
17
|
+
if (!hasOpenModals) {
|
|
25
18
|
document.body.classList.remove("modal-open");
|
|
26
19
|
}
|
|
27
20
|
}
|
|
@@ -70,18 +63,12 @@ const useModals = <T extends string>(modalKeys: readonly T[], bodyBlock = true)
|
|
|
70
63
|
if (!document.body.classList.contains("modal-open")) {
|
|
71
64
|
document.body.classList.add("modal-open");
|
|
72
65
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
// Solo eliminar si no hay modales abiertos
|
|
82
|
-
const modals = document.querySelectorAll('[data-testid="modal-wrapper"]');
|
|
83
|
-
if (modals.length === 0) {
|
|
84
|
-
document.body.classList.remove("modal-open");
|
|
66
|
+
} else if (!hasOpenModals) {
|
|
67
|
+
// Solo remover si no hay otros modales o FloatingPanels abiertos
|
|
68
|
+
const hasAnyOpenModals = document.querySelectorAll('[data-is-open="true"]').length > 0;
|
|
69
|
+
if (!hasAnyOpenModals) {
|
|
70
|
+
document.body.classList.remove("modal-open");
|
|
71
|
+
}
|
|
85
72
|
}
|
|
86
73
|
}, [openModals, bodyBlock]);
|
|
87
74
|
|
|
@@ -23,6 +23,18 @@ const isEffectivelyVisible = (el: HTMLElement): boolean => {
|
|
|
23
23
|
return true;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
+
const isInStickyOrFixed = (el: HTMLElement): boolean => {
|
|
27
|
+
let parent: HTMLElement | null = el.parentElement;
|
|
28
|
+
while (parent && parent !== document.body) {
|
|
29
|
+
const style = window.getComputedStyle(parent);
|
|
30
|
+
if (style.position === "sticky" || style.position === "fixed") {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
parent = parent.parentElement;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
};
|
|
37
|
+
|
|
26
38
|
const HeadingsOverlay = ({ headingFilter }: IHeadingsOverlayProps) => {
|
|
27
39
|
const [boxes, setBoxes] = useState<HeadingBox[]>([]);
|
|
28
40
|
const rafRef = useRef<number>(0);
|
|
@@ -33,14 +45,18 @@ const HeadingsOverlay = ({ headingFilter }: IHeadingsOverlayProps) => {
|
|
|
33
45
|
rafRef.current = requestAnimationFrame(() => {
|
|
34
46
|
const selector = headingFilter || "h1, h2, h3, h4, h5, h6";
|
|
35
47
|
const headings = Array.from(document.querySelectorAll<HTMLElement>(selector));
|
|
36
|
-
const scrollX = window.scrollX;
|
|
37
|
-
const scrollY = window.scrollY;
|
|
38
48
|
const boxes: HeadingBox[] = [];
|
|
39
49
|
for (let i = 0; i < headings.length; i++) {
|
|
40
50
|
const el = headings[i];
|
|
41
51
|
const rect = el.getBoundingClientRect();
|
|
42
52
|
if (rect.width === 0 && rect.height === 0) continue;
|
|
43
53
|
if (!isEffectivelyVisible(el)) continue;
|
|
54
|
+
|
|
55
|
+
// Para headings dentro de sticky/fixed, no sumar scroll de página
|
|
56
|
+
const inStickyOrFixed = isInStickyOrFixed(el);
|
|
57
|
+
const scrollX = inStickyOrFixed ? 0 : window.scrollX;
|
|
58
|
+
const scrollY = inStickyOrFixed ? 0 : window.scrollY;
|
|
59
|
+
|
|
44
60
|
boxes.push({
|
|
45
61
|
id: el.dataset.griddoid || `heading-${i}`,
|
|
46
62
|
tag: el.tagName.toLowerCase(),
|
|
@@ -55,18 +71,18 @@ const HeadingsOverlay = ({ headingFilter }: IHeadingsOverlayProps) => {
|
|
|
55
71
|
|
|
56
72
|
update();
|
|
57
73
|
|
|
74
|
+
document.fonts.ready.then(update);
|
|
75
|
+
|
|
58
76
|
window.addEventListener("resize", update);
|
|
59
|
-
window.addEventListener("scroll", update, true);
|
|
60
77
|
document.addEventListener("animationend", update, true);
|
|
61
78
|
document.addEventListener("transitionend", update, true);
|
|
62
79
|
|
|
63
80
|
const observer = new MutationObserver(update);
|
|
64
|
-
observer.observe(document.body, { childList: true, subtree: true
|
|
81
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
65
82
|
|
|
66
83
|
return () => {
|
|
67
84
|
cancelAnimationFrame(rafRef.current);
|
|
68
85
|
window.removeEventListener("resize", update);
|
|
69
|
-
window.removeEventListener("scroll", update, true);
|
|
70
86
|
document.removeEventListener("animationend", update, true);
|
|
71
87
|
document.removeEventListener("transitionend", update, true);
|
|
72
88
|
observer.disconnect();
|
|
@@ -6,7 +6,10 @@ const addIdsToHeadings = (container: HTMLElement, headingFilter: string | null):
|
|
|
6
6
|
const text = heading.textContent?.trim();
|
|
7
7
|
if (!text) return;
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
const id = `heading-${index + 1}`;
|
|
10
|
+
if (heading.dataset.griddoid !== id) {
|
|
11
|
+
heading.dataset.griddoid = id;
|
|
12
|
+
}
|
|
10
13
|
});
|
|
11
14
|
};
|
|
12
15
|
|