@cmssy/react 12.6.0 → 12.7.0
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/dist/client.cjs +128 -1
- package/dist/client.js +128 -1
- package/package.json +2 -2
package/dist/client.cjs
CHANGED
|
@@ -65,6 +65,122 @@ function resolveShortcutAction(event, isMac, isTyping) {
|
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// src/bridge/invisible-blocks.ts
|
|
69
|
+
var TRANSPARENT = 0.01;
|
|
70
|
+
var PAINTS_WITHOUT_TEXT = "img,svg,video,canvas,picture,iframe";
|
|
71
|
+
function effectiveOpacity(node) {
|
|
72
|
+
let value = 1;
|
|
73
|
+
let current = node;
|
|
74
|
+
while (current) {
|
|
75
|
+
const style = getComputedStyle(current);
|
|
76
|
+
if (style.display === "none" || style.visibility === "hidden") return 0;
|
|
77
|
+
const own = Number.parseFloat(style.opacity);
|
|
78
|
+
if (Number.isFinite(own)) value *= own;
|
|
79
|
+
if (value <= TRANSPARENT) return 0;
|
|
80
|
+
if (current === document.documentElement) break;
|
|
81
|
+
current = current.parentElement;
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
function paintsSomething(el) {
|
|
86
|
+
if (el.matches(PAINTS_WITHOUT_TEXT)) return true;
|
|
87
|
+
for (const node of el.childNodes) {
|
|
88
|
+
if (node.nodeType === 3 && node.textContent?.trim()) return true;
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
function isBlockPainted(block) {
|
|
93
|
+
const candidates = [];
|
|
94
|
+
if (paintsSomething(block)) candidates.push(block);
|
|
95
|
+
for (const el of block.querySelectorAll("*")) {
|
|
96
|
+
if (paintsSomething(el)) candidates.push(el);
|
|
97
|
+
}
|
|
98
|
+
if (candidates.length === 0) return true;
|
|
99
|
+
return candidates.some((el) => effectiveOpacity(el) > TRANSPARENT);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/bridge/use-invisible-blocks.ts
|
|
103
|
+
var DWELL_MS = 1500;
|
|
104
|
+
var VISIBLE_FRACTION = 0.35;
|
|
105
|
+
var SWEEP_MS = 2e3;
|
|
106
|
+
function useInvisibleBlocks(enabled, blocksKey, report) {
|
|
107
|
+
const reportRef = react.useRef(report);
|
|
108
|
+
reportRef.current = report;
|
|
109
|
+
react.useEffect(() => {
|
|
110
|
+
if (!enabled) return;
|
|
111
|
+
if (typeof document === "undefined") return;
|
|
112
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
113
|
+
const timers = /* @__PURE__ */ new Map();
|
|
114
|
+
const observed = /* @__PURE__ */ new WeakSet();
|
|
115
|
+
const invisible = /* @__PURE__ */ new Map();
|
|
116
|
+
let reported = "";
|
|
117
|
+
const flush = () => {
|
|
118
|
+
const blocks = [...invisible.values()];
|
|
119
|
+
const key = blocks.map((block) => `${block.blockId}:${block.blockType}`).sort().join("|");
|
|
120
|
+
if (key === reported) return;
|
|
121
|
+
reported = key;
|
|
122
|
+
reportRef.current(blocks);
|
|
123
|
+
};
|
|
124
|
+
const judge = (el) => {
|
|
125
|
+
const blockId = el.getAttribute("data-block-id");
|
|
126
|
+
const blockType = el.getAttribute("data-block-type");
|
|
127
|
+
if (!blockId || !blockType) return;
|
|
128
|
+
if (isBlockPainted(el)) invisible.delete(blockId);
|
|
129
|
+
else invisible.set(blockId, { blockId, blockType });
|
|
130
|
+
flush();
|
|
131
|
+
};
|
|
132
|
+
const observer = new IntersectionObserver(
|
|
133
|
+
(entries) => {
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
const pending = timers.get(entry.target);
|
|
136
|
+
if (!entry.isIntersecting || entry.intersectionRatio < VISIBLE_FRACTION) {
|
|
137
|
+
if (pending) {
|
|
138
|
+
clearTimeout(pending);
|
|
139
|
+
timers.delete(entry.target);
|
|
140
|
+
}
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (pending) continue;
|
|
144
|
+
timers.set(
|
|
145
|
+
entry.target,
|
|
146
|
+
setTimeout(() => {
|
|
147
|
+
timers.delete(entry.target);
|
|
148
|
+
judge(entry.target);
|
|
149
|
+
}, DWELL_MS)
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
{ threshold: VISIBLE_FRACTION }
|
|
154
|
+
);
|
|
155
|
+
const sweep = () => {
|
|
156
|
+
const present = /* @__PURE__ */ new Set();
|
|
157
|
+
for (const el of document.querySelectorAll("[data-block-id]")) {
|
|
158
|
+
if (!observed.has(el)) {
|
|
159
|
+
observed.add(el);
|
|
160
|
+
observer.observe(el);
|
|
161
|
+
}
|
|
162
|
+
const blockId = el.getAttribute("data-block-id");
|
|
163
|
+
if (!blockId) continue;
|
|
164
|
+
present.add(blockId);
|
|
165
|
+
if (invisible.has(blockId) && isBlockPainted(el)) {
|
|
166
|
+
invisible.delete(blockId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const blockId of [...invisible.keys()]) {
|
|
170
|
+
if (!present.has(blockId)) invisible.delete(blockId);
|
|
171
|
+
}
|
|
172
|
+
flush();
|
|
173
|
+
};
|
|
174
|
+
sweep();
|
|
175
|
+
const interval = setInterval(sweep, SWEEP_MS);
|
|
176
|
+
return () => {
|
|
177
|
+
observer.disconnect();
|
|
178
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
179
|
+
clearInterval(interval);
|
|
180
|
+
};
|
|
181
|
+
}, [enabled, blocksKey]);
|
|
182
|
+
}
|
|
183
|
+
|
|
68
184
|
// src/bridge/use-edit-bridge.tsx
|
|
69
185
|
var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
|
|
70
186
|
function collectRects() {
|
|
@@ -88,6 +204,9 @@ function findBlockEl(blockId) {
|
|
|
88
204
|
return null;
|
|
89
205
|
}
|
|
90
206
|
}
|
|
207
|
+
function canDetectInvisibleBlocks() {
|
|
208
|
+
return typeof document !== "undefined" && typeof IntersectionObserver !== "undefined";
|
|
209
|
+
}
|
|
91
210
|
function prefersReducedMotion() {
|
|
92
211
|
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
93
212
|
}
|
|
@@ -126,6 +245,7 @@ function useEditBridge(page, config) {
|
|
|
126
245
|
});
|
|
127
246
|
const { id: pageId, blocks } = page;
|
|
128
247
|
const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
|
|
248
|
+
const framed = typeof window !== "undefined" && window.parent !== window;
|
|
129
249
|
react.useEffect(() => {
|
|
130
250
|
setPatches({});
|
|
131
251
|
setPatchesStyle({});
|
|
@@ -172,7 +292,7 @@ function useEditBridge(page, config) {
|
|
|
172
292
|
],
|
|
173
293
|
schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
|
|
174
294
|
blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null),
|
|
175
|
-
capabilities: ["shortcuts"]
|
|
295
|
+
capabilities: canDetectInvisibleBlocks() ? ["shortcuts", "invisible-blocks"] : ["shortcuts"]
|
|
176
296
|
});
|
|
177
297
|
} catch (error) {
|
|
178
298
|
if (typeof console !== "undefined") {
|
|
@@ -322,6 +442,13 @@ function useEditBridge(page, config) {
|
|
|
322
442
|
window.removeEventListener("resize", emitSelectedBounds);
|
|
323
443
|
};
|
|
324
444
|
}, [config.editorOrigin, pageId, blocksKey]);
|
|
445
|
+
useInvisibleBlocks(framed, blocksKey, (invisibleBlocks) => {
|
|
446
|
+
postSafeRef.current({
|
|
447
|
+
type: "cmssy:invisible-blocks",
|
|
448
|
+
protocolVersion: core.PROTOCOL_VERSION,
|
|
449
|
+
blocks: invisibleBlocks
|
|
450
|
+
});
|
|
451
|
+
});
|
|
325
452
|
react.useEffect(() => {
|
|
326
453
|
emitBoundsRef.current();
|
|
327
454
|
});
|
package/dist/client.js
CHANGED
|
@@ -63,6 +63,122 @@ function resolveShortcutAction(event, isMac, isTyping) {
|
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
// src/bridge/invisible-blocks.ts
|
|
67
|
+
var TRANSPARENT = 0.01;
|
|
68
|
+
var PAINTS_WITHOUT_TEXT = "img,svg,video,canvas,picture,iframe";
|
|
69
|
+
function effectiveOpacity(node) {
|
|
70
|
+
let value = 1;
|
|
71
|
+
let current = node;
|
|
72
|
+
while (current) {
|
|
73
|
+
const style = getComputedStyle(current);
|
|
74
|
+
if (style.display === "none" || style.visibility === "hidden") return 0;
|
|
75
|
+
const own = Number.parseFloat(style.opacity);
|
|
76
|
+
if (Number.isFinite(own)) value *= own;
|
|
77
|
+
if (value <= TRANSPARENT) return 0;
|
|
78
|
+
if (current === document.documentElement) break;
|
|
79
|
+
current = current.parentElement;
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
function paintsSomething(el) {
|
|
84
|
+
if (el.matches(PAINTS_WITHOUT_TEXT)) return true;
|
|
85
|
+
for (const node of el.childNodes) {
|
|
86
|
+
if (node.nodeType === 3 && node.textContent?.trim()) return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
function isBlockPainted(block) {
|
|
91
|
+
const candidates = [];
|
|
92
|
+
if (paintsSomething(block)) candidates.push(block);
|
|
93
|
+
for (const el of block.querySelectorAll("*")) {
|
|
94
|
+
if (paintsSomething(el)) candidates.push(el);
|
|
95
|
+
}
|
|
96
|
+
if (candidates.length === 0) return true;
|
|
97
|
+
return candidates.some((el) => effectiveOpacity(el) > TRANSPARENT);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/bridge/use-invisible-blocks.ts
|
|
101
|
+
var DWELL_MS = 1500;
|
|
102
|
+
var VISIBLE_FRACTION = 0.35;
|
|
103
|
+
var SWEEP_MS = 2e3;
|
|
104
|
+
function useInvisibleBlocks(enabled, blocksKey, report) {
|
|
105
|
+
const reportRef = useRef(report);
|
|
106
|
+
reportRef.current = report;
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!enabled) return;
|
|
109
|
+
if (typeof document === "undefined") return;
|
|
110
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
111
|
+
const timers = /* @__PURE__ */ new Map();
|
|
112
|
+
const observed = /* @__PURE__ */ new WeakSet();
|
|
113
|
+
const invisible = /* @__PURE__ */ new Map();
|
|
114
|
+
let reported = "";
|
|
115
|
+
const flush = () => {
|
|
116
|
+
const blocks = [...invisible.values()];
|
|
117
|
+
const key = blocks.map((block) => `${block.blockId}:${block.blockType}`).sort().join("|");
|
|
118
|
+
if (key === reported) return;
|
|
119
|
+
reported = key;
|
|
120
|
+
reportRef.current(blocks);
|
|
121
|
+
};
|
|
122
|
+
const judge = (el) => {
|
|
123
|
+
const blockId = el.getAttribute("data-block-id");
|
|
124
|
+
const blockType = el.getAttribute("data-block-type");
|
|
125
|
+
if (!blockId || !blockType) return;
|
|
126
|
+
if (isBlockPainted(el)) invisible.delete(blockId);
|
|
127
|
+
else invisible.set(blockId, { blockId, blockType });
|
|
128
|
+
flush();
|
|
129
|
+
};
|
|
130
|
+
const observer = new IntersectionObserver(
|
|
131
|
+
(entries) => {
|
|
132
|
+
for (const entry of entries) {
|
|
133
|
+
const pending = timers.get(entry.target);
|
|
134
|
+
if (!entry.isIntersecting || entry.intersectionRatio < VISIBLE_FRACTION) {
|
|
135
|
+
if (pending) {
|
|
136
|
+
clearTimeout(pending);
|
|
137
|
+
timers.delete(entry.target);
|
|
138
|
+
}
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (pending) continue;
|
|
142
|
+
timers.set(
|
|
143
|
+
entry.target,
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
timers.delete(entry.target);
|
|
146
|
+
judge(entry.target);
|
|
147
|
+
}, DWELL_MS)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{ threshold: VISIBLE_FRACTION }
|
|
152
|
+
);
|
|
153
|
+
const sweep = () => {
|
|
154
|
+
const present = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const el of document.querySelectorAll("[data-block-id]")) {
|
|
156
|
+
if (!observed.has(el)) {
|
|
157
|
+
observed.add(el);
|
|
158
|
+
observer.observe(el);
|
|
159
|
+
}
|
|
160
|
+
const blockId = el.getAttribute("data-block-id");
|
|
161
|
+
if (!blockId) continue;
|
|
162
|
+
present.add(blockId);
|
|
163
|
+
if (invisible.has(blockId) && isBlockPainted(el)) {
|
|
164
|
+
invisible.delete(blockId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
for (const blockId of [...invisible.keys()]) {
|
|
168
|
+
if (!present.has(blockId)) invisible.delete(blockId);
|
|
169
|
+
}
|
|
170
|
+
flush();
|
|
171
|
+
};
|
|
172
|
+
sweep();
|
|
173
|
+
const interval = setInterval(sweep, SWEEP_MS);
|
|
174
|
+
return () => {
|
|
175
|
+
observer.disconnect();
|
|
176
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
177
|
+
clearInterval(interval);
|
|
178
|
+
};
|
|
179
|
+
}, [enabled, blocksKey]);
|
|
180
|
+
}
|
|
181
|
+
|
|
66
182
|
// src/bridge/use-edit-bridge.tsx
|
|
67
183
|
var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
|
|
68
184
|
function collectRects() {
|
|
@@ -86,6 +202,9 @@ function findBlockEl(blockId) {
|
|
|
86
202
|
return null;
|
|
87
203
|
}
|
|
88
204
|
}
|
|
205
|
+
function canDetectInvisibleBlocks() {
|
|
206
|
+
return typeof document !== "undefined" && typeof IntersectionObserver !== "undefined";
|
|
207
|
+
}
|
|
89
208
|
function prefersReducedMotion() {
|
|
90
209
|
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
91
210
|
}
|
|
@@ -124,6 +243,7 @@ function useEditBridge(page, config) {
|
|
|
124
243
|
});
|
|
125
244
|
const { id: pageId, blocks } = page;
|
|
126
245
|
const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
|
|
246
|
+
const framed = typeof window !== "undefined" && window.parent !== window;
|
|
127
247
|
useEffect(() => {
|
|
128
248
|
setPatches({});
|
|
129
249
|
setPatchesStyle({});
|
|
@@ -170,7 +290,7 @@ function useEditBridge(page, config) {
|
|
|
170
290
|
],
|
|
171
291
|
schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
|
|
172
292
|
blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null),
|
|
173
|
-
capabilities: ["shortcuts"]
|
|
293
|
+
capabilities: canDetectInvisibleBlocks() ? ["shortcuts", "invisible-blocks"] : ["shortcuts"]
|
|
174
294
|
});
|
|
175
295
|
} catch (error) {
|
|
176
296
|
if (typeof console !== "undefined") {
|
|
@@ -320,6 +440,13 @@ function useEditBridge(page, config) {
|
|
|
320
440
|
window.removeEventListener("resize", emitSelectedBounds);
|
|
321
441
|
};
|
|
322
442
|
}, [config.editorOrigin, pageId, blocksKey]);
|
|
443
|
+
useInvisibleBlocks(framed, blocksKey, (invisibleBlocks) => {
|
|
444
|
+
postSafeRef.current({
|
|
445
|
+
type: "cmssy:invisible-blocks",
|
|
446
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
447
|
+
blocks: invisibleBlocks
|
|
448
|
+
});
|
|
449
|
+
});
|
|
323
450
|
useEffect(() => {
|
|
324
451
|
emitBoundsRef.current();
|
|
325
452
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/react",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.7.0",
|
|
4
4
|
"description": "React blocks, renderers, data client and editor bridge for cmssy headless sites",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cmssy",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
99
|
"@cmssy/types": "0.35.0",
|
|
100
|
-
"@cmssy/core": "12.
|
|
100
|
+
"@cmssy/core": "12.7.0"
|
|
101
101
|
},
|
|
102
102
|
"scripts": {
|
|
103
103
|
"build": "tsup",
|