@leviosa-ai/detail-page-editor 0.11.3 → 0.11.5

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.
@@ -68,7 +68,7 @@ export const SectionHeightHandle = observer(function SectionHeightHandle({
68
68
  // Konva 가 이 누름을 보면 캔버스 빈 곳 클릭으로 읽어 선택을 풀어 버린다.
69
69
  e.preventDefault();
70
70
  e.stopPropagation();
71
- e.currentTarget.setPointerCapture(e.pointerId);
71
+ e.currentTarget.setPointerCapture?.(e.pointerId);
72
72
  history?.startTransaction?.();
73
73
  drag.current = { pointerId: e.pointerId, y: e.clientY, base: height };
74
74
  setDragging(true);
@@ -286,7 +286,9 @@ export const CanvasSectionHeightHandle = observer(
286
286
  width: box.width,
287
287
  height: box.height,
288
288
  pointerEvents: "none",
289
- zIndex: 21,
289
+ // 아래 띠(페이지 목록)와 하단 독보다 위다. 장의 아래 끝이 화면 밑쪽에
290
+ // 오면 그것들이 손잡이를 덮어서 «없어진» 것처럼 보였다.
291
+ zIndex: 31,
290
292
  }}
291
293
  >
292
294
  <SectionHeightHandle page={page} scale={scale} history={s.history} />
@@ -2,6 +2,12 @@ import { useEffect, useRef, useState } from "react";
2
2
 
3
3
  export type SaveReason = "auto" | "manual" | "leave";
4
4
 
5
+ /**
6
+ * 첫 변경으로부터 여기까지는 기다려 준다. 디바운스만 두면 계속 그리는 동안 한 번도
7
+ * 안 나가므로, 편집 중이어도 이 간격마다 한 번은 저장된다.
8
+ */
9
+ const MAX_WAIT_MS = 15_000;
10
+
5
11
  /** 변경 알림만 받으면 되므로 스토어 전체를 요구하지 않는다. */
6
12
  interface ChangeSource {
7
13
  on(event: "change", listener: () => void): () => void;
@@ -10,7 +16,7 @@ interface ChangeSource {
10
16
  /**
11
17
  * 문서가 바뀌면 잠잠해진 뒤에 한 번 저장한다.
12
18
  *
13
- * 규칙 셋이 전부다.
19
+ * 규칙 넷이 전부다.
14
20
  *
15
21
  * 1. **한 번에 하나만 보낸다.** 상세페이지 서버는 저장을 멱등키로 직렬화해서, 겹쳐
16
22
  * 보내면 뒤엣것이 "처리 중"으로 거절당한다. 보내는 동안 또 바뀌었으면 끝난 뒤에
@@ -20,6 +26,9 @@ interface ChangeSource {
20
26
  * 3. **실패는 삼키되 잊지 않는다.** dirty 를 되돌려 두면 다음 변경이나 다음 이탈에
21
27
  * 다시 나간다. 자동저장이 실패했다고 편집을 막을 일은 아니고, 그렇다고 곧바로
22
28
  * 다시 보내지도 않는다 — 서버가 죽어 있는 동안 요청을 쉬지 않고 때리게 된다.
29
+ * 4. **기다림에 상한이 있다.** 편집 한 번마다 디바운스가 처음부터 다시 돌아가므로,
30
+ * 손을 안 떼고 계속 그리면 요청이 영영 안 나간다. 첫 변경으로부터 `MAX_WAIT_MS`
31
+ * 가 지나면 아직 그리는 중이어도 한 번 내보낸다.
23
32
  *
24
33
  * `delayMs` 가 없으면 아무것도 안 한다 — 자동저장을 아직 안 켠 화면이 그렇다.
25
34
  *
@@ -68,13 +77,21 @@ export function useAutoSave(options: {
68
77
  if (sent && dirtyRef.current) await flush(reason);
69
78
  };
70
79
 
80
+ // 이번 저장 묶음의 첫 변경 시각. 상한을 재는 기준이고, 내보낸 뒤 0 으로 돌아간다.
81
+ let firstChangeAt = 0;
82
+
71
83
  const off = store.on("change", () => {
72
84
  mark(true);
85
+ const now = Date.now();
86
+ if (!firstChangeAt) firstChangeAt = now;
73
87
  if (timer) clearTimeout(timer);
88
+ // 잠잠해지면 `delayMs` 뒤에, 계속 그리는 중이면 상한에 걸려 그보다 먼저 나간다.
89
+ const wait = Math.max(0, Math.min(delayMs, firstChangeAt + MAX_WAIT_MS - now));
74
90
  timer = setTimeout(() => {
75
91
  timer = null;
92
+ firstChangeAt = 0;
76
93
  void flush("auto");
77
- }, delayMs);
94
+ }, wait);
78
95
  });
79
96
 
80
97
  const leave = () => {
@@ -82,6 +99,7 @@ export function useAutoSave(options: {
82
99
  clearTimeout(timer);
83
100
  timer = null;
84
101
  }
102
+ firstChangeAt = 0;
85
103
  void flush("leave");
86
104
  };
87
105
  const onHidden = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leviosa-ai/detail-page-editor",
3
- "version": "0.11.3",
3
+ "version": "0.11.5",
4
4
  "description": "Leviosa detail-page editor shell. Source-only: consumers compile it through Next transpilePackages. Every app coupling (sourcing server, brand store, cache keys, toast) arrives through the DetailPageHost interface, and every URL it fetches comes from configureDetailPageEditor.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",