@cmssy/react 12.2.0 → 12.4.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 CHANGED
@@ -38,6 +38,34 @@ function blocksToMeta(blocks, defaults = {}) {
38
38
  }
39
39
  return out;
40
40
  }
41
+
42
+ // src/bridge/shortcut-keys.ts
43
+ function isMacPlatform() {
44
+ const platform = typeof navigator !== "undefined" ? navigator.platform : void 0;
45
+ return typeof platform === "string" && platform.toUpperCase().includes("MAC");
46
+ }
47
+ function isTypingTarget(target) {
48
+ const el = target;
49
+ if (!el || typeof el.tagName !== "string") return false;
50
+ return el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable === true;
51
+ }
52
+ function resolveShortcutAction(event, isMac, isTyping) {
53
+ if (event.repeat || event.isComposing) return null;
54
+ const modifier = isMac ? event.metaKey : event.ctrlKey;
55
+ const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
56
+ if (modifier && key === "z") {
57
+ if (isTyping) return null;
58
+ return event.shiftKey ? "redo" : "undo";
59
+ }
60
+ if (modifier && key === "s") return "save";
61
+ if (modifier && key === "d") return isTyping ? null : "duplicate";
62
+ const isDeleteKey = key === "Delete" || isMac && key === "Backspace";
63
+ if (isDeleteKey) return isTyping ? null : "delete";
64
+ if (key === "Escape") return "escape";
65
+ return null;
66
+ }
67
+
68
+ // src/bridge/use-edit-bridge.tsx
41
69
  var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
42
70
  function collectRects() {
43
71
  const rects = /* @__PURE__ */ new Map();
@@ -94,6 +122,8 @@ function useEditBridge(page, config) {
94
122
  const selectedIdRef = react.useRef(null);
95
123
  const postSafeRef = react.useRef(() => {
96
124
  });
125
+ const emitBoundsRef = react.useRef(() => {
126
+ });
97
127
  const { id: pageId, blocks } = page;
98
128
  const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
99
129
  react.useEffect(() => {
@@ -141,7 +171,8 @@ function useEditBridge(page, config) {
141
171
  ...collectLayoutBlocks(rects, pageIds)
142
172
  ],
143
173
  schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
144
- blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null)
174
+ blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null),
175
+ capabilities: ["shortcuts"]
145
176
  });
146
177
  } catch (error) {
147
178
  if (typeof console !== "undefined") {
@@ -236,6 +267,20 @@ function useEditBridge(page, config) {
236
267
  ...layoutPosition !== null ? { layoutPosition } : {}
237
268
  });
238
269
  };
270
+ const onKeyDown = (event) => {
271
+ const action = resolveShortcutAction(
272
+ event,
273
+ isMacPlatform(),
274
+ isTypingTarget(event.target)
275
+ );
276
+ if (!action) return;
277
+ event.preventDefault();
278
+ postSafe({
279
+ type: "cmssy:shortcut",
280
+ protocolVersion: core.PROTOCOL_VERSION,
281
+ action
282
+ });
283
+ };
239
284
  let boundsRaf = 0;
240
285
  let boundsPending = false;
241
286
  const emitSelectedBounds = () => {
@@ -256,8 +301,10 @@ function useEditBridge(page, config) {
256
301
  });
257
302
  });
258
303
  };
304
+ emitBoundsRef.current = emitSelectedBounds;
259
305
  window.addEventListener("message", handler);
260
306
  document.addEventListener("click", onClick, { capture: true });
307
+ document.addEventListener("keydown", onKeyDown, { capture: true });
261
308
  window.addEventListener("scroll", emitSelectedBounds, {
262
309
  capture: true,
263
310
  passive: true
@@ -268,12 +315,16 @@ function useEditBridge(page, config) {
268
315
  if (boundsRaf) cancelAnimationFrame(boundsRaf);
269
316
  window.removeEventListener("message", handler);
270
317
  document.removeEventListener("click", onClick, { capture: true });
318
+ document.removeEventListener("keydown", onKeyDown, { capture: true });
271
319
  window.removeEventListener("scroll", emitSelectedBounds, {
272
320
  capture: true
273
321
  });
274
322
  window.removeEventListener("resize", emitSelectedBounds);
275
323
  };
276
324
  }, [config.editorOrigin, pageId, blocksKey]);
325
+ react.useEffect(() => {
326
+ emitBoundsRef.current();
327
+ });
277
328
  react.useEffect(() => {
278
329
  if (typeof window === "undefined" || window.parent === window) return;
279
330
  const notifySelectionGone = () => {
package/dist/client.js CHANGED
@@ -36,6 +36,34 @@ function blocksToMeta(blocks, defaults = {}) {
36
36
  }
37
37
  return out;
38
38
  }
39
+
40
+ // src/bridge/shortcut-keys.ts
41
+ function isMacPlatform() {
42
+ const platform = typeof navigator !== "undefined" ? navigator.platform : void 0;
43
+ return typeof platform === "string" && platform.toUpperCase().includes("MAC");
44
+ }
45
+ function isTypingTarget(target) {
46
+ const el = target;
47
+ if (!el || typeof el.tagName !== "string") return false;
48
+ return el.tagName === "INPUT" || el.tagName === "TEXTAREA" || el.isContentEditable === true;
49
+ }
50
+ function resolveShortcutAction(event, isMac, isTyping) {
51
+ if (event.repeat || event.isComposing) return null;
52
+ const modifier = isMac ? event.metaKey : event.ctrlKey;
53
+ const key = event.key.length === 1 ? event.key.toLowerCase() : event.key;
54
+ if (modifier && key === "z") {
55
+ if (isTyping) return null;
56
+ return event.shiftKey ? "redo" : "undo";
57
+ }
58
+ if (modifier && key === "s") return "save";
59
+ if (modifier && key === "d") return isTyping ? null : "duplicate";
60
+ const isDeleteKey = key === "Delete" || isMac && key === "Backspace";
61
+ if (isDeleteKey) return isTyping ? null : "delete";
62
+ if (key === "Escape") return "escape";
63
+ return null;
64
+ }
65
+
66
+ // src/bridge/use-edit-bridge.tsx
39
67
  var ZERO_RECT = { x: 0, y: 0, width: 0, height: 0 };
40
68
  function collectRects() {
41
69
  const rects = /* @__PURE__ */ new Map();
@@ -92,6 +120,8 @@ function useEditBridge(page, config) {
92
120
  const selectedIdRef = useRef(null);
93
121
  const postSafeRef = useRef(() => {
94
122
  });
123
+ const emitBoundsRef = useRef(() => {
124
+ });
95
125
  const { id: pageId, blocks } = page;
96
126
  const blocksKey = blocks.map((b) => `${b.id}:${b.type}`).join("|");
97
127
  useEffect(() => {
@@ -139,7 +169,8 @@ function useEditBridge(page, config) {
139
169
  ...collectLayoutBlocks(rects, pageIds)
140
170
  ],
141
171
  schemas: config.schemas ?? /* @__PURE__ */ Object.create(null),
142
- blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null)
172
+ blockMeta: config.blockMeta ?? /* @__PURE__ */ Object.create(null),
173
+ capabilities: ["shortcuts"]
143
174
  });
144
175
  } catch (error) {
145
176
  if (typeof console !== "undefined") {
@@ -234,6 +265,20 @@ function useEditBridge(page, config) {
234
265
  ...layoutPosition !== null ? { layoutPosition } : {}
235
266
  });
236
267
  };
268
+ const onKeyDown = (event) => {
269
+ const action = resolveShortcutAction(
270
+ event,
271
+ isMacPlatform(),
272
+ isTypingTarget(event.target)
273
+ );
274
+ if (!action) return;
275
+ event.preventDefault();
276
+ postSafe({
277
+ type: "cmssy:shortcut",
278
+ protocolVersion: PROTOCOL_VERSION,
279
+ action
280
+ });
281
+ };
237
282
  let boundsRaf = 0;
238
283
  let boundsPending = false;
239
284
  const emitSelectedBounds = () => {
@@ -254,8 +299,10 @@ function useEditBridge(page, config) {
254
299
  });
255
300
  });
256
301
  };
302
+ emitBoundsRef.current = emitSelectedBounds;
257
303
  window.addEventListener("message", handler);
258
304
  document.addEventListener("click", onClick, { capture: true });
305
+ document.addEventListener("keydown", onKeyDown, { capture: true });
259
306
  window.addEventListener("scroll", emitSelectedBounds, {
260
307
  capture: true,
261
308
  passive: true
@@ -266,12 +313,16 @@ function useEditBridge(page, config) {
266
313
  if (boundsRaf) cancelAnimationFrame(boundsRaf);
267
314
  window.removeEventListener("message", handler);
268
315
  document.removeEventListener("click", onClick, { capture: true });
316
+ document.removeEventListener("keydown", onKeyDown, { capture: true });
269
317
  window.removeEventListener("scroll", emitSelectedBounds, {
270
318
  capture: true
271
319
  });
272
320
  window.removeEventListener("resize", emitSelectedBounds);
273
321
  };
274
322
  }, [config.editorOrigin, pageId, blocksKey]);
323
+ useEffect(() => {
324
+ emitBoundsRef.current();
325
+ });
275
326
  useEffect(() => {
276
327
  if (typeof window === "undefined" || window.parent === window) return;
277
328
  const notifySelectionGone = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmssy/react",
3
- "version": "12.2.0",
3
+ "version": "12.4.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.2.0"
100
+ "@cmssy/core": "12.4.0"
101
101
  },
102
102
  "scripts": {
103
103
  "build": "tsup",