@cmssy/react 12.6.0 → 12.7.1
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 +147 -1
- package/dist/client.js +147 -1
- package/package.json +2 -2
package/dist/client.cjs
CHANGED
|
@@ -65,6 +65,141 @@ 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 VISIBLE_TEXT_FRACTION = 0.5;
|
|
71
|
+
var MEDIA = "img,svg,video,canvas,picture,iframe";
|
|
72
|
+
var UNPAINTED_TEXT = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEMPLATE", "NOSCRIPT"]);
|
|
73
|
+
function effectiveOpacity(node) {
|
|
74
|
+
let value = 1;
|
|
75
|
+
let current = node;
|
|
76
|
+
while (current) {
|
|
77
|
+
const style = getComputedStyle(current);
|
|
78
|
+
if (style.display === "none" || style.visibility === "hidden") return 0;
|
|
79
|
+
const own = Number.parseFloat(style.opacity);
|
|
80
|
+
if (Number.isFinite(own)) value *= own;
|
|
81
|
+
if (value <= TRANSPARENT) return 0;
|
|
82
|
+
if (current === document.documentElement) break;
|
|
83
|
+
current = current.parentElement;
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
function holdsText(el) {
|
|
88
|
+
for (const node of el.childNodes) {
|
|
89
|
+
if (node.nodeType === 3 && node.textContent?.trim()) return true;
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
function collectCopy(block) {
|
|
94
|
+
const copy = [];
|
|
95
|
+
const keep = (el) => {
|
|
96
|
+
if (UNPAINTED_TEXT.has(el.tagName)) return;
|
|
97
|
+
if (el.closest("svg")) return;
|
|
98
|
+
if (holdsText(el)) copy.push(el);
|
|
99
|
+
};
|
|
100
|
+
keep(block);
|
|
101
|
+
for (const el of block.querySelectorAll("*")) keep(el);
|
|
102
|
+
return copy;
|
|
103
|
+
}
|
|
104
|
+
function collectMedia(block) {
|
|
105
|
+
const media = [];
|
|
106
|
+
if (block.matches(MEDIA)) media.push(block);
|
|
107
|
+
for (const el of block.querySelectorAll(MEDIA)) media.push(el);
|
|
108
|
+
return media;
|
|
109
|
+
}
|
|
110
|
+
function isBlockPainted(block) {
|
|
111
|
+
const copy = collectCopy(block);
|
|
112
|
+
if (copy.length > 0) {
|
|
113
|
+
const visible2 = copy.filter((el) => effectiveOpacity(el) > TRANSPARENT);
|
|
114
|
+
return visible2.length >= copy.length * VISIBLE_TEXT_FRACTION;
|
|
115
|
+
}
|
|
116
|
+
const media = collectMedia(block);
|
|
117
|
+
if (media.length === 0) return true;
|
|
118
|
+
return media.some((el) => effectiveOpacity(el) > TRANSPARENT);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/bridge/use-invisible-blocks.ts
|
|
122
|
+
var DWELL_MS = 1500;
|
|
123
|
+
var VISIBLE_FRACTION = 0.35;
|
|
124
|
+
var SWEEP_MS = 2e3;
|
|
125
|
+
function useInvisibleBlocks(enabled, blocksKey, report) {
|
|
126
|
+
const reportRef = react.useRef(report);
|
|
127
|
+
reportRef.current = report;
|
|
128
|
+
react.useEffect(() => {
|
|
129
|
+
if (!enabled) return;
|
|
130
|
+
if (typeof document === "undefined") return;
|
|
131
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
132
|
+
const timers = /* @__PURE__ */ new Map();
|
|
133
|
+
const observed = /* @__PURE__ */ new WeakSet();
|
|
134
|
+
const invisible = /* @__PURE__ */ new Map();
|
|
135
|
+
let reported = "";
|
|
136
|
+
const flush = () => {
|
|
137
|
+
const blocks = [...invisible.values()];
|
|
138
|
+
const key = blocks.map((block) => `${block.blockId}:${block.blockType}`).sort().join("|");
|
|
139
|
+
if (key === reported) return;
|
|
140
|
+
reported = key;
|
|
141
|
+
reportRef.current(blocks);
|
|
142
|
+
};
|
|
143
|
+
const judge = (el) => {
|
|
144
|
+
const blockId = el.getAttribute("data-block-id");
|
|
145
|
+
const blockType = el.getAttribute("data-block-type");
|
|
146
|
+
if (!blockId || !blockType) return;
|
|
147
|
+
if (isBlockPainted(el)) invisible.delete(blockId);
|
|
148
|
+
else invisible.set(blockId, { blockId, blockType });
|
|
149
|
+
flush();
|
|
150
|
+
};
|
|
151
|
+
const observer = new IntersectionObserver(
|
|
152
|
+
(entries) => {
|
|
153
|
+
for (const entry of entries) {
|
|
154
|
+
const pending = timers.get(entry.target);
|
|
155
|
+
if (!entry.isIntersecting || entry.intersectionRatio < VISIBLE_FRACTION) {
|
|
156
|
+
if (pending) {
|
|
157
|
+
clearTimeout(pending);
|
|
158
|
+
timers.delete(entry.target);
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (pending) continue;
|
|
163
|
+
timers.set(
|
|
164
|
+
entry.target,
|
|
165
|
+
setTimeout(() => {
|
|
166
|
+
timers.delete(entry.target);
|
|
167
|
+
judge(entry.target);
|
|
168
|
+
}, DWELL_MS)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{ threshold: VISIBLE_FRACTION }
|
|
173
|
+
);
|
|
174
|
+
const sweep = () => {
|
|
175
|
+
const present = /* @__PURE__ */ new Set();
|
|
176
|
+
for (const el of document.querySelectorAll("[data-block-id]")) {
|
|
177
|
+
if (!observed.has(el)) {
|
|
178
|
+
observed.add(el);
|
|
179
|
+
observer.observe(el);
|
|
180
|
+
}
|
|
181
|
+
const blockId = el.getAttribute("data-block-id");
|
|
182
|
+
if (!blockId) continue;
|
|
183
|
+
present.add(blockId);
|
|
184
|
+
if (invisible.has(blockId) && isBlockPainted(el)) {
|
|
185
|
+
invisible.delete(blockId);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const blockId of [...invisible.keys()]) {
|
|
189
|
+
if (!present.has(blockId)) invisible.delete(blockId);
|
|
190
|
+
}
|
|
191
|
+
flush();
|
|
192
|
+
};
|
|
193
|
+
sweep();
|
|
194
|
+
const interval = setInterval(sweep, SWEEP_MS);
|
|
195
|
+
return () => {
|
|
196
|
+
observer.disconnect();
|
|
197
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
198
|
+
clearInterval(interval);
|
|
199
|
+
};
|
|
200
|
+
}, [enabled, blocksKey]);
|
|
201
|
+
}
|
|
202
|
+
|
|
68
203
|
// src/bridge/use-edit-bridge.tsx
|
|
69
204
|
var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
|
|
70
205
|
function collectRects() {
|
|
@@ -88,6 +223,9 @@ function findBlockEl(blockId) {
|
|
|
88
223
|
return null;
|
|
89
224
|
}
|
|
90
225
|
}
|
|
226
|
+
function canDetectInvisibleBlocks() {
|
|
227
|
+
return typeof document !== "undefined" && typeof IntersectionObserver !== "undefined";
|
|
228
|
+
}
|
|
91
229
|
function prefersReducedMotion() {
|
|
92
230
|
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
93
231
|
}
|
|
@@ -126,6 +264,7 @@ function useEditBridge(page, config) {
|
|
|
126
264
|
});
|
|
127
265
|
const { id: pageId, blocks } = page;
|
|
128
266
|
const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
|
|
267
|
+
const framed = typeof window !== "undefined" && window.parent !== window;
|
|
129
268
|
react.useEffect(() => {
|
|
130
269
|
setPatches({});
|
|
131
270
|
setPatchesStyle({});
|
|
@@ -172,7 +311,7 @@ function useEditBridge(page, config) {
|
|
|
172
311
|
],
|
|
173
312
|
schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
|
|
174
313
|
blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null),
|
|
175
|
-
capabilities: ["shortcuts"]
|
|
314
|
+
capabilities: canDetectInvisibleBlocks() ? ["shortcuts", "invisible-blocks"] : ["shortcuts"]
|
|
176
315
|
});
|
|
177
316
|
} catch (error) {
|
|
178
317
|
if (typeof console !== "undefined") {
|
|
@@ -322,6 +461,13 @@ function useEditBridge(page, config) {
|
|
|
322
461
|
window.removeEventListener("resize", emitSelectedBounds);
|
|
323
462
|
};
|
|
324
463
|
}, [config.editorOrigin, pageId, blocksKey]);
|
|
464
|
+
useInvisibleBlocks(framed, blocksKey, (invisibleBlocks) => {
|
|
465
|
+
postSafeRef.current({
|
|
466
|
+
type: "cmssy:invisible-blocks",
|
|
467
|
+
protocolVersion: core.PROTOCOL_VERSION,
|
|
468
|
+
blocks: invisibleBlocks
|
|
469
|
+
});
|
|
470
|
+
});
|
|
325
471
|
react.useEffect(() => {
|
|
326
472
|
emitBoundsRef.current();
|
|
327
473
|
});
|
package/dist/client.js
CHANGED
|
@@ -63,6 +63,141 @@ 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 VISIBLE_TEXT_FRACTION = 0.5;
|
|
69
|
+
var MEDIA = "img,svg,video,canvas,picture,iframe";
|
|
70
|
+
var UNPAINTED_TEXT = /* @__PURE__ */ new Set(["SCRIPT", "STYLE", "TEMPLATE", "NOSCRIPT"]);
|
|
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 holdsText(el) {
|
|
86
|
+
for (const node of el.childNodes) {
|
|
87
|
+
if (node.nodeType === 3 && node.textContent?.trim()) return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function collectCopy(block) {
|
|
92
|
+
const copy = [];
|
|
93
|
+
const keep = (el) => {
|
|
94
|
+
if (UNPAINTED_TEXT.has(el.tagName)) return;
|
|
95
|
+
if (el.closest("svg")) return;
|
|
96
|
+
if (holdsText(el)) copy.push(el);
|
|
97
|
+
};
|
|
98
|
+
keep(block);
|
|
99
|
+
for (const el of block.querySelectorAll("*")) keep(el);
|
|
100
|
+
return copy;
|
|
101
|
+
}
|
|
102
|
+
function collectMedia(block) {
|
|
103
|
+
const media = [];
|
|
104
|
+
if (block.matches(MEDIA)) media.push(block);
|
|
105
|
+
for (const el of block.querySelectorAll(MEDIA)) media.push(el);
|
|
106
|
+
return media;
|
|
107
|
+
}
|
|
108
|
+
function isBlockPainted(block) {
|
|
109
|
+
const copy = collectCopy(block);
|
|
110
|
+
if (copy.length > 0) {
|
|
111
|
+
const visible2 = copy.filter((el) => effectiveOpacity(el) > TRANSPARENT);
|
|
112
|
+
return visible2.length >= copy.length * VISIBLE_TEXT_FRACTION;
|
|
113
|
+
}
|
|
114
|
+
const media = collectMedia(block);
|
|
115
|
+
if (media.length === 0) return true;
|
|
116
|
+
return media.some((el) => effectiveOpacity(el) > TRANSPARENT);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// src/bridge/use-invisible-blocks.ts
|
|
120
|
+
var DWELL_MS = 1500;
|
|
121
|
+
var VISIBLE_FRACTION = 0.35;
|
|
122
|
+
var SWEEP_MS = 2e3;
|
|
123
|
+
function useInvisibleBlocks(enabled, blocksKey, report) {
|
|
124
|
+
const reportRef = useRef(report);
|
|
125
|
+
reportRef.current = report;
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!enabled) return;
|
|
128
|
+
if (typeof document === "undefined") return;
|
|
129
|
+
if (typeof IntersectionObserver === "undefined") return;
|
|
130
|
+
const timers = /* @__PURE__ */ new Map();
|
|
131
|
+
const observed = /* @__PURE__ */ new WeakSet();
|
|
132
|
+
const invisible = /* @__PURE__ */ new Map();
|
|
133
|
+
let reported = "";
|
|
134
|
+
const flush = () => {
|
|
135
|
+
const blocks = [...invisible.values()];
|
|
136
|
+
const key = blocks.map((block) => `${block.blockId}:${block.blockType}`).sort().join("|");
|
|
137
|
+
if (key === reported) return;
|
|
138
|
+
reported = key;
|
|
139
|
+
reportRef.current(blocks);
|
|
140
|
+
};
|
|
141
|
+
const judge = (el) => {
|
|
142
|
+
const blockId = el.getAttribute("data-block-id");
|
|
143
|
+
const blockType = el.getAttribute("data-block-type");
|
|
144
|
+
if (!blockId || !blockType) return;
|
|
145
|
+
if (isBlockPainted(el)) invisible.delete(blockId);
|
|
146
|
+
else invisible.set(blockId, { blockId, blockType });
|
|
147
|
+
flush();
|
|
148
|
+
};
|
|
149
|
+
const observer = new IntersectionObserver(
|
|
150
|
+
(entries) => {
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const pending = timers.get(entry.target);
|
|
153
|
+
if (!entry.isIntersecting || entry.intersectionRatio < VISIBLE_FRACTION) {
|
|
154
|
+
if (pending) {
|
|
155
|
+
clearTimeout(pending);
|
|
156
|
+
timers.delete(entry.target);
|
|
157
|
+
}
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (pending) continue;
|
|
161
|
+
timers.set(
|
|
162
|
+
entry.target,
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
timers.delete(entry.target);
|
|
165
|
+
judge(entry.target);
|
|
166
|
+
}, DWELL_MS)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
{ threshold: VISIBLE_FRACTION }
|
|
171
|
+
);
|
|
172
|
+
const sweep = () => {
|
|
173
|
+
const present = /* @__PURE__ */ new Set();
|
|
174
|
+
for (const el of document.querySelectorAll("[data-block-id]")) {
|
|
175
|
+
if (!observed.has(el)) {
|
|
176
|
+
observed.add(el);
|
|
177
|
+
observer.observe(el);
|
|
178
|
+
}
|
|
179
|
+
const blockId = el.getAttribute("data-block-id");
|
|
180
|
+
if (!blockId) continue;
|
|
181
|
+
present.add(blockId);
|
|
182
|
+
if (invisible.has(blockId) && isBlockPainted(el)) {
|
|
183
|
+
invisible.delete(blockId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
for (const blockId of [...invisible.keys()]) {
|
|
187
|
+
if (!present.has(blockId)) invisible.delete(blockId);
|
|
188
|
+
}
|
|
189
|
+
flush();
|
|
190
|
+
};
|
|
191
|
+
sweep();
|
|
192
|
+
const interval = setInterval(sweep, SWEEP_MS);
|
|
193
|
+
return () => {
|
|
194
|
+
observer.disconnect();
|
|
195
|
+
for (const timer of timers.values()) clearTimeout(timer);
|
|
196
|
+
clearInterval(interval);
|
|
197
|
+
};
|
|
198
|
+
}, [enabled, blocksKey]);
|
|
199
|
+
}
|
|
200
|
+
|
|
66
201
|
// src/bridge/use-edit-bridge.tsx
|
|
67
202
|
var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
|
|
68
203
|
function collectRects() {
|
|
@@ -86,6 +221,9 @@ function findBlockEl(blockId) {
|
|
|
86
221
|
return null;
|
|
87
222
|
}
|
|
88
223
|
}
|
|
224
|
+
function canDetectInvisibleBlocks() {
|
|
225
|
+
return typeof document !== "undefined" && typeof IntersectionObserver !== "undefined";
|
|
226
|
+
}
|
|
89
227
|
function prefersReducedMotion() {
|
|
90
228
|
return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
91
229
|
}
|
|
@@ -124,6 +262,7 @@ function useEditBridge(page, config) {
|
|
|
124
262
|
});
|
|
125
263
|
const { id: pageId, blocks } = page;
|
|
126
264
|
const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
|
|
265
|
+
const framed = typeof window !== "undefined" && window.parent !== window;
|
|
127
266
|
useEffect(() => {
|
|
128
267
|
setPatches({});
|
|
129
268
|
setPatchesStyle({});
|
|
@@ -170,7 +309,7 @@ function useEditBridge(page, config) {
|
|
|
170
309
|
],
|
|
171
310
|
schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
|
|
172
311
|
blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null),
|
|
173
|
-
capabilities: ["shortcuts"]
|
|
312
|
+
capabilities: canDetectInvisibleBlocks() ? ["shortcuts", "invisible-blocks"] : ["shortcuts"]
|
|
174
313
|
});
|
|
175
314
|
} catch (error) {
|
|
176
315
|
if (typeof console !== "undefined") {
|
|
@@ -320,6 +459,13 @@ function useEditBridge(page, config) {
|
|
|
320
459
|
window.removeEventListener("resize", emitSelectedBounds);
|
|
321
460
|
};
|
|
322
461
|
}, [config.editorOrigin, pageId, blocksKey]);
|
|
462
|
+
useInvisibleBlocks(framed, blocksKey, (invisibleBlocks) => {
|
|
463
|
+
postSafeRef.current({
|
|
464
|
+
type: "cmssy:invisible-blocks",
|
|
465
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
466
|
+
blocks: invisibleBlocks
|
|
467
|
+
});
|
|
468
|
+
});
|
|
323
469
|
useEffect(() => {
|
|
324
470
|
emitBoundsRef.current();
|
|
325
471
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cmssy/react",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.7.1",
|
|
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.1"
|
|
101
101
|
},
|
|
102
102
|
"scripts": {
|
|
103
103
|
"build": "tsup",
|