@storkspear/post-editor-react 0.1.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.
Files changed (192) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +12 -0
  3. package/dist/ColorPicker.d.ts +11 -0
  4. package/dist/ColorPicker.d.ts.map +1 -0
  5. package/dist/ColorPicker.js +30 -0
  6. package/dist/ColorPicker.js.map +1 -0
  7. package/dist/DrawioModal.d.ts +9 -0
  8. package/dist/DrawioModal.d.ts.map +1 -0
  9. package/dist/DrawioModal.js +97 -0
  10. package/dist/DrawioModal.js.map +1 -0
  11. package/dist/FontMenu.d.ts +14 -0
  12. package/dist/FontMenu.d.ts.map +1 -0
  13. package/dist/FontMenu.js +14 -0
  14. package/dist/FontMenu.js.map +1 -0
  15. package/dist/Icon.d.ts +69 -0
  16. package/dist/Icon.d.ts.map +1 -0
  17. package/dist/Icon.js +47 -0
  18. package/dist/Icon.js.map +1 -0
  19. package/dist/InsertHandle.d.ts +14 -0
  20. package/dist/InsertHandle.d.ts.map +1 -0
  21. package/dist/InsertHandle.js +60 -0
  22. package/dist/InsertHandle.js.map +1 -0
  23. package/dist/PickMenu.d.ts +61 -0
  24. package/dist/PickMenu.d.ts.map +1 -0
  25. package/dist/PickMenu.js +57 -0
  26. package/dist/PickMenu.js.map +1 -0
  27. package/dist/Popover.d.ts +20 -0
  28. package/dist/Popover.d.ts.map +1 -0
  29. package/dist/Popover.js +22 -0
  30. package/dist/Popover.js.map +1 -0
  31. package/dist/PostTitle.d.ts +33 -0
  32. package/dist/PostTitle.d.ts.map +1 -0
  33. package/dist/PostTitle.js +171 -0
  34. package/dist/PostTitle.js.map +1 -0
  35. package/dist/SelectionBubble.d.ts +6 -0
  36. package/dist/SelectionBubble.d.ts.map +1 -0
  37. package/dist/SelectionBubble.js +71 -0
  38. package/dist/SelectionBubble.js.map +1 -0
  39. package/dist/TableTools.d.ts +10 -0
  40. package/dist/TableTools.d.ts.map +1 -0
  41. package/dist/TableTools.js +200 -0
  42. package/dist/TableTools.js.map +1 -0
  43. package/dist/Toolbar.d.ts +17 -0
  44. package/dist/Toolbar.d.ts.map +1 -0
  45. package/dist/Toolbar.js +333 -0
  46. package/dist/Toolbar.js.map +1 -0
  47. package/dist/UrlPrompt.d.ts +24 -0
  48. package/dist/UrlPrompt.d.ts.map +1 -0
  49. package/dist/UrlPrompt.js +53 -0
  50. package/dist/UrlPrompt.js.map +1 -0
  51. package/dist/VariantMenu.d.ts +47 -0
  52. package/dist/VariantMenu.d.ts.map +1 -0
  53. package/dist/VariantMenu.js +21 -0
  54. package/dist/VariantMenu.js.map +1 -0
  55. package/dist/WidthToggle.d.ts +19 -0
  56. package/dist/WidthToggle.d.ts.map +1 -0
  57. package/dist/WidthToggle.js +17 -0
  58. package/dist/WidthToggle.js.map +1 -0
  59. package/dist/bubbleShows.d.ts +10 -0
  60. package/dist/bubbleShows.d.ts.map +1 -0
  61. package/dist/bubbleShows.js +29 -0
  62. package/dist/bubbleShows.js.map +1 -0
  63. package/dist/content-baked.d.ts +22 -0
  64. package/dist/content-baked.d.ts.map +1 -0
  65. package/dist/content-baked.js +21 -0
  66. package/dist/content-baked.js.map +1 -0
  67. package/dist/content.d.ts +29 -0
  68. package/dist/content.d.ts.map +1 -0
  69. package/dist/content.js +35 -0
  70. package/dist/content.js.map +1 -0
  71. package/dist/extensions/DiagramView.d.ts +26 -0
  72. package/dist/extensions/DiagramView.d.ts.map +1 -0
  73. package/dist/extensions/DiagramView.js +146 -0
  74. package/dist/extensions/DiagramView.js.map +1 -0
  75. package/dist/extensions/FigureView.d.ts +3 -0
  76. package/dist/extensions/FigureView.d.ts.map +1 -0
  77. package/dist/extensions/FigureView.js +108 -0
  78. package/dist/extensions/FigureView.js.map +1 -0
  79. package/dist/index.d.ts +100 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +166 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/objects/ObjectFloat.d.ts +11 -0
  84. package/dist/objects/ObjectFloat.d.ts.map +1 -0
  85. package/dist/objects/ObjectFloat.js +42 -0
  86. package/dist/objects/ObjectFloat.js.map +1 -0
  87. package/dist/objects/ObjectLayer.d.ts +10 -0
  88. package/dist/objects/ObjectLayer.d.ts.map +1 -0
  89. package/dist/objects/ObjectLayer.js +20 -0
  90. package/dist/objects/ObjectLayer.js.map +1 -0
  91. package/dist/objects/blockSize.d.ts +30 -0
  92. package/dist/objects/blockSize.d.ts.map +1 -0
  93. package/dist/objects/blockSize.js +56 -0
  94. package/dist/objects/blockSize.js.map +1 -0
  95. package/dist/objects/code.d.ts +3 -0
  96. package/dist/objects/code.d.ts.map +1 -0
  97. package/dist/objects/code.js +55 -0
  98. package/dist/objects/code.js.map +1 -0
  99. package/dist/objects/diagram.d.ts +21 -0
  100. package/dist/objects/diagram.d.ts.map +1 -0
  101. package/dist/objects/diagram.js +109 -0
  102. package/dist/objects/diagram.js.map +1 -0
  103. package/dist/objects/diagramGlyphs.d.ts +12 -0
  104. package/dist/objects/diagramGlyphs.d.ts.map +1 -0
  105. package/dist/objects/diagramGlyphs.js +49 -0
  106. package/dist/objects/diagramGlyphs.js.map +1 -0
  107. package/dist/objects/divider.d.ts +22 -0
  108. package/dist/objects/divider.d.ts.map +1 -0
  109. package/dist/objects/divider.js +60 -0
  110. package/dist/objects/divider.js.map +1 -0
  111. package/dist/objects/figure.d.ts +3 -0
  112. package/dist/objects/figure.d.ts.map +1 -0
  113. package/dist/objects/figure.js +62 -0
  114. package/dist/objects/figure.js.map +1 -0
  115. package/dist/objects/imageRow.d.ts +3 -0
  116. package/dist/objects/imageRow.d.ts.map +1 -0
  117. package/dist/objects/imageRow.js +29 -0
  118. package/dist/objects/imageRow.js.map +1 -0
  119. package/dist/objects/quote.d.ts +6 -0
  120. package/dist/objects/quote.d.ts.map +1 -0
  121. package/dist/objects/quote.js +13 -0
  122. package/dist/objects/quote.js.map +1 -0
  123. package/dist/objects/quoteGlyphs.d.ts +6 -0
  124. package/dist/objects/quoteGlyphs.d.ts.map +1 -0
  125. package/dist/objects/quoteGlyphs.js +28 -0
  126. package/dist/objects/quoteGlyphs.js.map +1 -0
  127. package/dist/objects/quoteRow.d.ts +3 -0
  128. package/dist/objects/quoteRow.d.ts.map +1 -0
  129. package/dist/objects/quoteRow.js +61 -0
  130. package/dist/objects/quoteRow.js.map +1 -0
  131. package/dist/objects/registry.d.ts +60 -0
  132. package/dist/objects/registry.d.ts.map +1 -0
  133. package/dist/objects/registry.js +45 -0
  134. package/dist/objects/registry.js.map +1 -0
  135. package/dist/objects/table.d.ts +11 -0
  136. package/dist/objects/table.d.ts.map +1 -0
  137. package/dist/objects/table.js +136 -0
  138. package/dist/objects/table.js.map +1 -0
  139. package/dist/objects/useDomRect.d.ts +14 -0
  140. package/dist/objects/useDomRect.d.ts.map +1 -0
  141. package/dist/objects/useDomRect.js +32 -0
  142. package/dist/objects/useDomRect.js.map +1 -0
  143. package/dist/objects/useEngaged.d.ts +14 -0
  144. package/dist/objects/useEngaged.d.ts.map +1 -0
  145. package/dist/objects/useEngaged.js +41 -0
  146. package/dist/objects/useEngaged.js.map +1 -0
  147. package/dist/pickItems.d.ts +36 -0
  148. package/dist/pickItems.d.ts.map +1 -0
  149. package/dist/pickItems.js +40 -0
  150. package/dist/pickItems.js.map +1 -0
  151. package/dist/recentColors.d.ts +7 -0
  152. package/dist/recentColors.d.ts.map +1 -0
  153. package/dist/recentColors.js +30 -0
  154. package/dist/recentColors.js.map +1 -0
  155. package/dist/seeds/drawio/aws.d.ts +15 -0
  156. package/dist/seeds/drawio/aws.d.ts.map +1 -0
  157. package/dist/seeds/drawio/aws.js +15 -0
  158. package/dist/seeds/drawio/aws.js.map +1 -0
  159. package/dist/seeds/drawio/azure.d.ts +14 -0
  160. package/dist/seeds/drawio/azure.d.ts.map +1 -0
  161. package/dist/seeds/drawio/azure.js +14 -0
  162. package/dist/seeds/drawio/azure.js.map +1 -0
  163. package/dist/seeds/drawio/ec2docker.d.ts +15 -0
  164. package/dist/seeds/drawio/ec2docker.d.ts.map +1 -0
  165. package/dist/seeds/drawio/ec2docker.js +15 -0
  166. package/dist/seeds/drawio/ec2docker.js.map +1 -0
  167. package/dist/seeds/drawio/userflow.d.ts +21 -0
  168. package/dist/seeds/drawio/userflow.d.ts.map +1 -0
  169. package/dist/seeds/drawio/userflow.js +21 -0
  170. package/dist/seeds/drawio/userflow.js.map +1 -0
  171. package/dist/seeds/mermaid.d.ts +19 -0
  172. package/dist/seeds/mermaid.d.ts.map +1 -0
  173. package/dist/seeds/mermaid.js +82 -0
  174. package/dist/seeds/mermaid.js.map +1 -0
  175. package/dist/useAnchoredPos.d.ts +22 -0
  176. package/dist/useAnchoredPos.d.ts.map +1 -0
  177. package/dist/useAnchoredPos.js +57 -0
  178. package/dist/useAnchoredPos.js.map +1 -0
  179. package/dist/useDismiss.d.ts +17 -0
  180. package/dist/useDismiss.d.ts.map +1 -0
  181. package/dist/useDismiss.js +36 -0
  182. package/dist/useDismiss.js.map +1 -0
  183. package/dist/useLastVariant.d.ts +9 -0
  184. package/dist/useLastVariant.d.ts.map +1 -0
  185. package/dist/useLastVariant.js +33 -0
  186. package/dist/useLastVariant.js.map +1 -0
  187. package/dist/useUploadCount.d.ts +3 -0
  188. package/dist/useUploadCount.d.ts.map +1 -0
  189. package/dist/useUploadCount.js +9 -0
  190. package/dist/useUploadCount.js.map +1 -0
  191. package/package.json +57 -0
  192. package/src/editor-ui.css +737 -0
@@ -0,0 +1,20 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * 떠 있는 것을 **문서 맨 끝**으로 옮겨 그린다.
4
+ *
5
+ * 왜 필요한가: 툴바(`.toolbar2`)가 `position: sticky; z-index: 20` 이라 **쌓임 맥락**을 만든다.
6
+ * 그 안에서 아무리 z-index 를 올려도 밖의 z-index 20 위로는 못 올라간다.
7
+ * 그래서 표 오버레이(`.tt-layer`, z-index 40)가 툴바에서 연 드롭다운을 덮었다
8
+ *.
9
+ *
10
+ * 좌표를 이미 `position: fixed` 로 계산해 두는 것들만 옮긴다(`useAnchoredPos`).
11
+ * 부모 기준 absolute 로 뜨는 것은 옮기면 자리를 잃으므로 제자리에 둔다.
12
+ *
13
+ * React 이벤트는 포털을 넘어 **부모 컴포넌트로 그대로 전파**되므로 호출부는 안 바뀐다.
14
+ * 다만 DOM 조상이 달라지니 `data-editor-ui` 는 뜨는 것 **자신**이 달아야 한다(useEngaged).
15
+ */
16
+ export declare function Portal({ active, children }: {
17
+ active: boolean;
18
+ children: ReactNode;
19
+ }): import("react").JSX.Element;
20
+ //# sourceMappingURL=Popover.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Popover.d.ts","sourceRoot":"","sources":["../src/Popover.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,+BAGpF"}
@@ -0,0 +1,22 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { createPortal } from 'react-dom';
3
+ /**
4
+ * 떠 있는 것을 **문서 맨 끝**으로 옮겨 그린다.
5
+ *
6
+ * 왜 필요한가: 툴바(`.toolbar2`)가 `position: sticky; z-index: 20` 이라 **쌓임 맥락**을 만든다.
7
+ * 그 안에서 아무리 z-index 를 올려도 밖의 z-index 20 위로는 못 올라간다.
8
+ * 그래서 표 오버레이(`.tt-layer`, z-index 40)가 툴바에서 연 드롭다운을 덮었다
9
+ *.
10
+ *
11
+ * 좌표를 이미 `position: fixed` 로 계산해 두는 것들만 옮긴다(`useAnchoredPos`).
12
+ * 부모 기준 absolute 로 뜨는 것은 옮기면 자리를 잃으므로 제자리에 둔다.
13
+ *
14
+ * React 이벤트는 포털을 넘어 **부모 컴포넌트로 그대로 전파**되므로 호출부는 안 바뀐다.
15
+ * 다만 DOM 조상이 달라지니 `data-editor-ui` 는 뜨는 것 **자신**이 달아야 한다(useEngaged).
16
+ */
17
+ export function Portal({ active, children }) {
18
+ if (!active || typeof document === 'undefined')
19
+ return _jsx(_Fragment, { children: children });
20
+ return createPortal(children, document.body);
21
+ }
22
+ //# sourceMappingURL=Popover.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Popover.js","sourceRoot":"","sources":["../src/Popover.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAGxC;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,EAA4C;IACnF,IAAI,CAAC,MAAM,IAAI,OAAO,QAAQ,KAAK,WAAW;QAAE,OAAO,4BAAG,QAAQ,GAAI,CAAA;IACtE,OAAO,YAAY,CAAC,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;AAC9C,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { type BannerBleed, type TitleValue } from '@storkspear/post-editor-core/title';
2
+ import { type ProseWidth } from '@storkspear/post-editor-core/proseWidth';
3
+ export { EMPTY_TITLE } from '@storkspear/post-editor-core/title';
4
+ export type { TitleBanner, TitleValue } from '@storkspear/post-editor-core/title';
5
+ /**
6
+ * 글 제목 — 본문과 **다른 물건**이다.
7
+ *
8
+ * 본문(ProseMirror)에 넣지 않은 이유: 제목은 DB 에서도 따로 사는 값이고(목록·permalink·`<title>`),
9
+ * 무엇보다 **한 덩어리로만 꾸민다.** 굵게·밑줄 같은 부분 서식이 붙을 자리가 아니다.
10
+ *
11
+ * `<input>` 이 아니라 `<h1 contenteditable>` 인 이유: input 은 **접히지 않는다.**
12
+ * 긴 제목이 편집 화면에서는 한 줄로 흘러가고 발행 페이지에서는 두 줄이 되어,
13
+ * 두 화면이 애초에 어긋나 있었다. `plaintext-only` 라 부분 서식은 여전히 불가능하다.
14
+ */
15
+ export declare function PostTitle({ value, onChange, onFocus, onBlur, placeholder, width, bleed }: {
16
+ value: TitleValue;
17
+ onChange: (v: TitleValue) => void;
18
+ onFocus?: () => void;
19
+ onBlur?: () => void;
20
+ placeholder?: string;
21
+ /**
22
+ * 본문 폭 — **본문과 같은 값을 받아야 한다.**
23
+ * 제목은 `.prose` 밖에 그려지므로 `.prose` 에 건 폭이 여기까지 내려오지 않는다.
24
+ * 안 넘기면 본문은 화면 끝까지 가는데 제목 밑줄만 38rem 에서 끊긴다.
25
+ */
26
+ width?: ProseWidth;
27
+ /**
28
+ * 배너를 어디까지 펴는가 — `container`(기본) 또는 `viewport`.
29
+ * 발행 쪽 `PostHead` 에 **같은 값**을 줘야 두 화면이 같아진다.
30
+ */
31
+ bleed?: BannerBleed;
32
+ }): import("react").JSX.Element;
33
+ //# sourceMappingURL=PostTitle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostTitle.d.ts","sourceRoot":"","sources":["../src/PostTitle.tsx"],"names":[],"mappings":"AACA,OAAO,EAAoC,KAAK,WAAW,EAAoB,KAAK,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAC1I,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,yCAAyC,CAAA;AAM1F,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAA;AAChE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,oCAAoC,CAAA;AAIjF;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,WAAkB,EAAE,KAAK,EAAE,KAAmB,EAAE,EAAE;IAC9G,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,UAAU,CAAA;IAClB;;;OAGG;IACH,KAAK,CAAC,EAAE,WAAW,CAAA;CACpB,+BAqMA"}
@@ -0,0 +1,171 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ import { bannerPosition, titleInlineStyle } from '@storkspear/post-editor-core/title';
4
+ import { proseWidthStyle } from '@storkspear/post-editor-core/proseWidth';
5
+ import { FOCUS_MAX, FOCUS_MIN } from '@storkspear/post-editor-core/vocab';
6
+ import { ref, resolveSrc, upload } from '@storkspear/post-editor-core/attachments';
7
+ import { pickImageFiles, prepareFailMessage, prepareImage } from '@storkspear/post-editor-core/image';
8
+ import { Icon } from './Icon.js';
9
+ export { EMPTY_TITLE } from '@storkspear/post-editor-core/title';
10
+ const clampFocus = (n) => Math.min(FOCUS_MAX, Math.max(FOCUS_MIN, n));
11
+ /**
12
+ * 글 제목 — 본문과 **다른 물건**이다.
13
+ *
14
+ * 본문(ProseMirror)에 넣지 않은 이유: 제목은 DB 에서도 따로 사는 값이고(목록·permalink·`<title>`),
15
+ * 무엇보다 **한 덩어리로만 꾸민다.** 굵게·밑줄 같은 부분 서식이 붙을 자리가 아니다.
16
+ *
17
+ * `<input>` 이 아니라 `<h1 contenteditable>` 인 이유: input 은 **접히지 않는다.**
18
+ * 긴 제목이 편집 화면에서는 한 줄로 흘러가고 발행 페이지에서는 두 줄이 되어,
19
+ * 두 화면이 애초에 어긋나 있었다. `plaintext-only` 라 부분 서식은 여전히 불가능하다.
20
+ */
21
+ export function PostTitle({ value, onChange, onFocus, onBlur, placeholder = '제목', width, bleed = 'container' }) {
22
+ const h1 = useRef(null);
23
+ const coverRef = useRef(null);
24
+ const [busy, setBusy] = useState(false);
25
+ /** 위치이동 중일 때의 임시 좌표. null 이면 평소 모드 — **저장값은 확인을 눌러야 바뀐다** */
26
+ const [moving, setMoving] = useState(null);
27
+ const banner = value.banner;
28
+ /**
29
+ * 밖에서 들어온 글자를 DOM 에 밀어 넣는다 — **손이 올라가 있을 때는 안 한다.**
30
+ * 조합 중(한글)에 DOM 을 다시 쓰면 조합이 끊겨 「ㅇㅎㅇㄴㅁㅎ」 가 된다.
31
+ */
32
+ useLayoutEffect(() => {
33
+ const el = h1.current;
34
+ if (!el || document.activeElement === el)
35
+ return;
36
+ if (el.textContent !== value.text)
37
+ el.textContent = value.text;
38
+ }, [value.text]);
39
+ const onInput = useCallback(() => {
40
+ const el = h1.current;
41
+ if (!el)
42
+ return;
43
+ // 줄바꿈은 공백으로 — 제목은 한 줄이다(접히는 것은 화면에서만)
44
+ const text = (el.textContent ?? '').replace(/[\r\n\t]+/g, ' ');
45
+ onChange({ ...value, text });
46
+ }, [onChange, value]);
47
+ const putBanner = useCallback(async () => {
48
+ setBusy(true);
49
+ try {
50
+ const files = await pickImageFiles();
51
+ const file = files[0];
52
+ if (!file)
53
+ return;
54
+ const prepared = await prepareImage(file);
55
+ // **조용히 끝내지 않는다.** 아무 일도 안 일어나면 왜 안 되는지 알 길이 없다
56
+ if (!prepared) {
57
+ window.alert(prepareFailMessage(file.type.startsWith('image/') ? 'undecodable' : 'not-image', file.name));
58
+ return;
59
+ }
60
+ // **먼저 올리고 끝난 뒤에 넣는다** — 본문 사진과 같은 계약이다.
61
+ // 올리는 중에 저장하면 브라우저에만 있는 임시 주소가 저장되기 때문.
62
+ const up = await upload(prepared.blob, prepared.name);
63
+ if (!up) {
64
+ window.alert(`사진을 올리지 못했습니다 — ${file.name}`);
65
+ return;
66
+ }
67
+ onChange({ ...value, banner: { src: ref(up.id), focusX: 50, focusY: 50, cover: true } });
68
+ }
69
+ finally {
70
+ setBusy(false);
71
+ }
72
+ }, [onChange, value]);
73
+ const patch = (b) => banner && onChange({ ...value, banner: { ...banner, ...b } });
74
+ /**
75
+ * 사진을 끌어 프레임 안에서 옮긴다.
76
+ *
77
+ * `object-fit: cover` 에서 `object-position: X%` 는 넘치는 양(ox)만큼을 X% 지점에 맞춘다.
78
+ * 오른쪽으로 dx 끌면 사진의 왼쪽 가장자리가 +dx 로 가야 하므로 `X' = X − dx·100/ox` 다.
79
+ * **0~100 클램프가 곧 「빈 곳이 안 생긴다」는 보장이다** — cover 에서 % 가 그 범위면
80
+ * 프레임은 언제나 사진으로 덮인다.
81
+ */
82
+ const onGrab = useCallback((e) => {
83
+ if (!moving)
84
+ return;
85
+ const host = coverRef.current;
86
+ const img = host?.querySelector('img');
87
+ if (!host || !img)
88
+ return;
89
+ const { width: W, height: H } = host.getBoundingClientRect();
90
+ const iw = img.naturalWidth, ih = img.naturalHeight;
91
+ if (!iw || !ih)
92
+ return; // 아직 안 실렸다 — 산식이 NaN 이 된다
93
+ const s = Math.max(W / iw, H / ih);
94
+ const ox = iw * s - W, oy = ih * s - H; // 넘치는 양. cover 라 한 축은 0 이다
95
+ const startX = e.clientX, startY = e.clientY;
96
+ const from = moving;
97
+ const target = e.currentTarget;
98
+ target.setPointerCapture(e.pointerId);
99
+ let latest = from;
100
+ const move = (ev) => {
101
+ latest = {
102
+ x: ox > 0.5 ? clampFocus(Math.round(from.x - ((ev.clientX - startX) / ox) * 100)) : from.x,
103
+ y: oy > 0.5 ? clampFocus(Math.round(from.y - ((ev.clientY - startY) / oy) * 100)) : from.y,
104
+ };
105
+ setMoving(latest);
106
+ };
107
+ const up = () => {
108
+ try {
109
+ target.releasePointerCapture(e.pointerId);
110
+ }
111
+ catch { /* 이미 풀렸으면 그만 */ }
112
+ target.removeEventListener('pointermove', move);
113
+ target.removeEventListener('pointerup', up);
114
+ // 브라우저가 포인터를 회수하면 pointerup 이 안 온다 — 리스너가 남아
115
+ // 버튼을 뗀 뒤에도 마우스만 움직이면 사진이 계속 움직인다
116
+ target.removeEventListener('pointercancel', up);
117
+ target.removeEventListener('lostpointercapture', up);
118
+ };
119
+ target.addEventListener('pointermove', move);
120
+ target.addEventListener('pointerup', up);
121
+ target.addEventListener('pointercancel', up);
122
+ target.addEventListener('lostpointercapture', up);
123
+ e.preventDefault();
124
+ }, [moving]);
125
+ // 위치이동 중 방향키 — 손이 떨리는 마지막 1% 는 키로 맞추는 것이 빠르다
126
+ useEffect(() => {
127
+ if (!moving)
128
+ return;
129
+ const onKey = (ev) => {
130
+ if (ev.key === 'Escape') {
131
+ setMoving(null);
132
+ return;
133
+ }
134
+ const step = ev.shiftKey ? 5 : 1;
135
+ const d = {
136
+ ArrowLeft: [step, 0], ArrowRight: [-step, 0], ArrowUp: [0, step], ArrowDown: [0, -step],
137
+ };
138
+ const v = d[ev.key];
139
+ if (!v)
140
+ return;
141
+ ev.preventDefault();
142
+ setMoving((m) => (m ? { x: clampFocus(m.x + v[0]), y: clampFocus(m.y + v[1]) } : m));
143
+ };
144
+ window.addEventListener('keydown', onKey);
145
+ return () => window.removeEventListener('keydown', onKey);
146
+ }, [moving]);
147
+ const shown = moving ?? (banner ? { x: banner.focusX, y: banner.focusY } : null);
148
+ return (_jsxs("header", { className: `post-head is-editing${moving ? ' is-moving' : ''}`, "data-banner": banner ? '1' : '0', "data-bleed": bleed, style: proseWidthStyle(width), children: [banner && (_jsxs("div", { className: "post-cover", ref: coverRef, onPointerDown: onGrab, children: [_jsx("img", { src: resolveSrc(banner.src), alt: "", style: { objectPosition: shown ? `${shown.x}% ${shown.y}%` : bannerPosition(banner) } }), moving && _jsx("p", { className: "ph-hint", "data-ui": "", children: "\uC774\uBBF8\uC9C0\uB97C \uC6C0\uC9C1\uC5EC \uC704\uCE58\uB97C \uC870\uC815\uD558\uC138\uC694." })] })), _jsx("h1", { ref: h1, className: "post-title",
149
+ /* plaintext-only 면 부분 서식이 **구조로** 불가능하다 — UI 가 막는 게 아니다.
150
+ 미지원 브라우저를 위해 붙여넣기에서 text/plain 만 받는다. */
151
+ contentEditable: "plaintext-only", suppressContentEditableWarning: true, role: "textbox", "aria-multiline": "false", "aria-label": "\uAE00 \uC81C\uBAA9", "data-placeholder": placeholder, onInput: onInput, onFocus: onFocus, onBlur: onBlur, onKeyDown: (e) => { if (e.key === 'Enter')
152
+ e.preventDefault(); }, onPaste: (e) => {
153
+ e.preventDefault();
154
+ const t = e.clipboardData.getData('text/plain').replace(/[\r\n\t]+/g, ' ');
155
+ document.execCommand('insertText', false, t);
156
+ }, style: { ...styleOf(value) } }), !banner && (_jsx("div", { className: "ph-tools", "data-ui": "", children: _jsx("button", { type: "button", title: "\uC81C\uBAA9\uC5D0 \uBC30\uACBD \uC0AC\uC9C4 \uB123\uAE30", "aria-label": "\uBC30\uACBD \uC0AC\uC9C4", disabled: busy, onMouseDown: (e) => e.preventDefault(), onClick: putBanner, children: _jsx(Icon, { name: "image", size: 15 }) }) })), banner && !moving && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "ph-badge", "data-ui": "", "aria-pressed": banner.cover, title: banner.cover ? '이 사진이 글 목록의 대표 이미지입니다' : '끄면 본문의 첫 사진이 대표가 됩니다', onMouseDown: (e) => e.preventDefault(), onClick: () => patch({ cover: !banner.cover }), children: banner.cover ? '✓ 대표' : '대표' }), _jsxs("div", { className: "ph-actions", "data-ui": "", children: [_jsxs("button", { type: "button", title: "\uC0AC\uC9C4 \uC704\uCE58 \uC870\uC815", onMouseDown: (e) => e.preventDefault(), onClick: () => setMoving({ x: banner.focusX, y: banner.focusY }), children: [_jsx(Icon, { name: "move", size: 14 }), "\uC704\uCE58\uC774\uB3D9"] }), _jsx("button", { type: "button", title: "\uBC30\uACBD \uC0AC\uC9C4 \uC9C0\uC6B0\uAE30", "aria-label": "\uBC30\uACBD \uC0AC\uC9C4 \uC9C0\uC6B0\uAE30", onMouseDown: (e) => e.preventDefault(), onClick: () => onChange({ ...value, banner: null }), children: _jsx(Icon, { name: "trash", size: 15 }) })] })] })), banner && moving && (_jsx(_Fragment, { children: _jsxs("div", { className: "ph-actions", "data-ui": "", children: [_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => setMoving(null), children: "\uCDE8\uC18C" }), _jsx("button", { type: "button", className: "is-go", onMouseDown: (e) => e.preventDefault(), onClick: () => { patch({ focusX: moving.x, focusY: moving.y }); setMoving(null); }, children: "\uD655\uC778" })] }) }))] }));
157
+ }
158
+ /** 발행 쪽과 **같은 문자열**을 쓴다 — `titleInlineStyle` 이 그 한 곳이다 */
159
+ function styleOf(t) {
160
+ const out = {};
161
+ for (const decl of titleInlineStyle(t).split(';')) {
162
+ const i = decl.indexOf(':');
163
+ if (i < 0)
164
+ continue;
165
+ // font-family → fontFamily
166
+ const k = decl.slice(0, i).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
167
+ out[k] = decl.slice(i + 1);
168
+ }
169
+ return out;
170
+ }
171
+ //# sourceMappingURL=PostTitle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostTitle.js","sourceRoot":"","sources":["../src/PostTitle.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { type Editor } from '@tiptap/react';
2
+ /** 본문에서 글자를 잡으면 그 자리에 뜨는 작은 툴바. 툴바까지 올라갈 필요를 없앤다. */
3
+ export declare function SelectionBubble({ editor }: {
4
+ editor: Editor;
5
+ }): import("react").JSX.Element;
6
+ //# sourceMappingURL=SelectionBubble.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionBubble.d.ts","sourceRoot":"","sources":["../src/SelectionBubble.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAkB,KAAK,MAAM,EAAE,MAAM,eAAe,CAAA;AAY3D,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,+BA2I7D"}
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useRef, useState } from 'react';
3
+ import { BubbleMenu } from '@tiptap/react/menus';
4
+ import { useEditorState } from '@tiptap/react';
5
+ import { Icon } from './Icon.js';
6
+ import { ColorPicker } from './ColorPicker.js';
7
+ import { useRecentColors } from './recentColors.js';
8
+ import { FontMenu } from './FontMenu.js';
9
+ import { FONTS, FONT_SIZE_DEFAULT } from '@storkspear/post-editor-core/vocab';
10
+ import { Picker } from './PickMenu.js';
11
+ import { SIZE_ITEMS } from './pickItems.js';
12
+ import { bubbleShows } from './bubbleShows.js';
13
+ import { UrlPrompt } from './UrlPrompt.js';
14
+ import { OBJECTS, findActiveObject } from './objects/registry.js';
15
+ /** 본문에서 글자를 잡으면 그 자리에 뜨는 작은 툴바. 툴바까지 올라갈 필요를 없앤다. */
16
+ export function SelectionBubble({ editor }) {
17
+ const [pick, setPick] = useState(null);
18
+ const linkAnchor = useRef(null);
19
+ const colorAnchor = useRef(null);
20
+ const hlAnchor = useRef(null);
21
+ const fontAnchor = useRef(null);
22
+ const { recent, remember } = useRecentColors();
23
+ const s = useEditorState({
24
+ editor,
25
+ selector: ({ editor: e }) => ({
26
+ bold: e.isActive('bold'), italic: e.isActive('italic'), underline: e.isActive('underline'),
27
+ strike: e.isActive('strike'), link: e.isActive('link'),
28
+ href: e.getAttributes('link').href ?? '',
29
+ size: e.getAttributes('textStyle').fontSize ?? '',
30
+ font: e.getAttributes('textStyle').fontFamily ?? '',
31
+ color: e.getAttributes('textStyle').color ?? '',
32
+ hlColor: e.getAttributes('highlight').color ?? '',
33
+ inTable: e.isActive('table'),
34
+ // 링크는 걸 글자가 있어야 건다 — 인용구는 커서만 있어도 버블이 뜨므로 여기서 가른다
35
+ hasRange: e.state.selection.from !== e.state.selection.to,
36
+ }),
37
+ });
38
+ // 지금 잡은 글자가 어떤 객체 안인가 — 그 객체가 버블 줄을 내놓으면 앞에 붙인다
39
+ const obj = useEditorState({ editor, selector: () => findActiveObject(editor) });
40
+ const kind = obj ? OBJECTS[obj.kind] : null;
41
+ const objBubble = obj && kind?.Bubble ? { C: kind.Bubble, m: obj } : null;
42
+ /**
43
+ * 바를 어디에 붙일까 — 인용구 안이면 **인용구 상자 위**에 고정한다.
44
+ *
45
+ * 기본은 「잡은 글자」가 기준이라, 같은 인용구를 눌러도 누른 자리마다 바가 옮겨 다녔다
46
+ *. 인용구는 블록 하나를 통째로 다루는 물건이라 기준도 블록이어야 한다.
47
+ * null 을 돌려주면 기본(선택 영역 기준)으로 돌아간다 — 인용 밖에서는 그게 맞다.
48
+ *
49
+ * 화면에서 직접 읽어 오는 콜백이다. 렌더 때 값을 담아 두면 플러그인이 처음 받은 것을
50
+ * 계속 쓰는 탓에 「늘 null」이 되어 조용히 아무 일도 안 한다.
51
+ */
52
+ const anchorRect = useCallback(() => {
53
+ const m = findActiveObject(editor);
54
+ const k = m ? OBJECTS[m.kind] : null;
55
+ if (!m || !k?.Bubble)
56
+ return null;
57
+ const el = (k.anchor ? k.anchor(m.dom) : m.dom);
58
+ if (!el)
59
+ return null;
60
+ return {
61
+ getBoundingClientRect: () => el.getBoundingClientRect(),
62
+ getClientRects: () => [el.getBoundingClientRect()],
63
+ contextElement: el,
64
+ };
65
+ }, [editor]);
66
+ const c = () => editor.chain().focus();
67
+ const applyColor = (hex) => { remember(hex); c().setColor(hex).run(); };
68
+ const applyHl = (hex) => { remember(hex); c().setHighlight({ color: hex }).run(); };
69
+ return (_jsx(BubbleMenu, { editor: editor, options: { placement: 'top', offset: 10 }, getReferencedVirtualElement: anchorRect, shouldShow: ({ editor: e }) => bubbleShows(e), children: _jsxs("div", { className: "bub", "data-editor-ui": "", children: [objBubble && (_jsxs(_Fragment, { children: [_jsx(objBubble.C, { editor: editor, m: objBubble.m }), _jsx("span", { className: "bub-div" })] })), _jsxs("div", { className: "bub-anchor", ref: fontAnchor, children: [_jsxs("button", { className: `bub-font${pick === 'font' ? ' on' : ''}`, title: "\uAE00\uAF34", style: { fontFamily: s.font || 'var(--font-body)' }, onMouseDown: (e) => e.preventDefault(), onClick: () => setPick(pick === 'font' ? null : 'font'), children: [FONTS.find((f) => f.value === s.font)?.label ?? '기본서체', _jsx("i", { className: "tb-caret" })] }), pick === 'font' && (_jsx(FontMenu, { value: s.font, anchorRef: fontAnchor, onClose: () => setPick(null), onPick: (v) => (v ? c().setFontFamily(v).run() : c().unsetFontFamily().run()) }))] }), _jsx(Picker, { title: "\uAE00\uC790 \uD06C\uAE30 (px)", className: "bub-size", width: 78, align: "center", items: SIZE_ITEMS, value: String(s.size ? parseInt(s.size, 10) : FONT_SIZE_DEFAULT), onPick: (v) => c().setFontSize(`${v}px`).run() }), _jsx("span", { className: "bub-div" }), _jsx("button", { className: s.bold ? 'on' : '', title: "\uAD75\uAC8C", onMouseDown: (e) => e.preventDefault(), onClick: () => c().toggleBold().run(), children: _jsx(Icon, { name: "bold", size: 16 }) }), _jsx("button", { className: s.italic ? 'on' : '', title: "\uAE30\uC6B8\uC784", onMouseDown: (e) => e.preventDefault(), onClick: () => c().toggleItalic().run(), children: _jsx(Icon, { name: "italic", size: 16 }) }), _jsx("button", { className: s.underline ? 'on' : '', title: "\uBC11\uC904", onMouseDown: (e) => e.preventDefault(), onClick: () => c().toggleUnderline().run(), children: _jsx(Icon, { name: "underline", size: 16 }) }), _jsx("button", { className: s.strike ? 'on' : '', title: "\uCDE8\uC18C\uC120", onMouseDown: (e) => e.preventDefault(), onClick: () => c().toggleStrike().run(), children: _jsx(Icon, { name: "strike", size: 16 }) }), _jsx("span", { className: "bub-div" }), _jsxs("div", { className: "bub-anchor", ref: colorAnchor, children: [_jsxs("button", { title: "\uAE00\uC790\uC0C9", onMouseDown: (e) => e.preventDefault(), onClick: () => setPick(pick === 'color' ? null : 'color'), children: [_jsx("span", { className: "bub-swatch", style: { background: s.color || '#1a1a1a' } }), _jsx("b", { className: "bub-letter", children: "\uAC00" })] }), pick === 'color' && (_jsx(ColorPicker, { label: "\uAE00\uC790\uC0C9", value: s.color, recent: recent, anchorRef: colorAnchor, onPick: applyColor, onClear: () => c().unsetColor().run(), onClose: () => setPick(null) }))] }), _jsxs("div", { className: "bub-anchor", ref: hlAnchor, children: [_jsx("button", { title: "\uAE00\uC790 \uBC30\uACBD\uC0C9", onMouseDown: (e) => e.preventDefault(), onClick: () => setPick(pick === 'highlight' ? null : 'highlight'), children: _jsx("b", { className: "bub-letter bub-hl-letter", style: { background: s.hlColor || '#fff2a8' }, children: "\uAC00" }) }), pick === 'highlight' && (_jsx(ColorPicker, { label: "\uAE00\uC790 \uBC30\uACBD\uC0C9", value: s.hlColor, recent: recent, anchorRef: hlAnchor, onPick: applyHl, onClear: () => c().unsetHighlight().run(), onClose: () => setPick(null) }))] }), s.hasRange && _jsx("span", { className: "bub-div" }), s.hasRange && (_jsxs("div", { className: "bub-anchor", ref: linkAnchor, children: [_jsx("button", { className: s.link || pick === 'link' ? 'on' : '', title: "\uB9C1\uD06C", onMouseDown: (e) => e.preventDefault(), onClick: () => setPick(pick === 'link' ? null : 'link'), children: _jsx(Icon, { name: "link", size: 16 }) }), pick === 'link' && (_jsx(UrlPrompt, { label: "\uB9C1\uD06C \uC8FC\uC18C", placeholder: "https://", initial: s.href, anchorRef: linkAnchor, hint: s.link ? '비우고 「걸기」를 누르면 링크가 풀립니다' : undefined, submitLabel: "\uAC78\uAE30", onClose: () => setPick(null), onSubmit: (url) => (url === '' ? c().unsetLink().run() : c().setLink({ href: url }).run()) }))] }))] }) }));
70
+ }
71
+ //# sourceMappingURL=SelectionBubble.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SelectionBubble.js","sourceRoot":"","sources":["../src/SelectionBubble.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAe,MAAM,eAAe,CAAA;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACrC,OAAO,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,oCAAoC,CAAA;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAoB,MAAM,oBAAoB,CAAA;AAEhF,qDAAqD;AACrD,MAAM,UAAU,eAAe,CAAC,EAAE,MAAM,EAAsB;IAC5D,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAiD,IAAI,CAAC,CAAA;IACtF,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC/C,MAAM,WAAW,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC7C,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAA;IAC/C,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,eAAe,EAAE,CAAA;IAE9C,MAAM,CAAC,GAAG,cAAc,CAAC;QACvB,MAAM;QACN,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;YAC5B,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;YAC1F,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;YACtD,IAAI,EAAG,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAA2B,IAAI,EAAE;YAChE,IAAI,EAAG,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,QAA+B,IAAI,EAAE;YACzE,IAAI,EAAG,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,UAAiC,IAAI,EAAE;YAC3E,KAAK,EAAG,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,KAA4B,IAAI,EAAE;YACvE,OAAO,EAAG,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,KAA4B,IAAI,EAAE;YACzE,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YAC5B,kDAAkD;YAClD,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE;SAC1D,CAAC;KACH,CAAC,CAAA;IACF,gDAAgD;IAChD,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAuB,CAAA;IACtG,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IAC3C,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;IAEzE;;;;;;;;;OASG;IACH,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;QAClC,MAAM,CAAC,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAA;QAClC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACpC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM;YAAE,OAAO,IAAI,CAAA;QACjC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAuB,CAAA;QACrE,IAAI,CAAC,EAAE;YAAE,OAAO,IAAI,CAAA;QACpB,OAAO;YACL,qBAAqB,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,qBAAqB,EAAE;YACvD,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,qBAAqB,EAAE,CAAC;YAClD,cAAc,EAAE,EAAE;SACV,CAAA;IACZ,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;IAEZ,MAAM,CAAC,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,CAAA;IAEtC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,EAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAA,CAAC,CAAC,CAAA;IACvF,MAAM,OAAO,GAAG,CAAC,GAAW,EAAE,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAA,CAAC,CAAC,CAAA;IAE1F,OAAO,CACL,KAAC,UAAU,IACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,EACzC,2BAA2B,EAAE,UAAU,EACvC,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,YAE7C,eAAK,SAAS,EAAC,KAAK,oBAAgB,EAAE,aAGnC,SAAS,IAAI,CACZ,8BACE,KAAC,SAAS,CAAC,CAAC,IAAC,MAAM,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,GAAI,EAC/C,eAAM,SAAS,EAAC,SAAS,GAAG,IAC3B,CACJ,EAED,eAAK,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,UAAU,aACzC,kBAAQ,SAAS,EAAE,WAAW,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAC,cAAI,EACtE,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,IAAI,IAAI,kBAAkB,EAAE,EACnD,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,aACtD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,MAAM,EACvD,YAAG,SAAS,EAAC,UAAU,GAAG,IACnB,EACR,IAAI,KAAK,MAAM,IAAI,CAClB,KAAC,QAAQ,IAAC,KAAK,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAC1E,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,EAAU,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAE,CAAC,EAAU,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,CAAC,GAAI,CACtG,IACG,EACN,KAAC,MAAM,IAAC,KAAK,EAAC,gCAAY,EAAC,SAAS,EAAC,UAAU,EAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAC,QAAQ,EAAC,KAAK,EAAE,UAAU,EACzF,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,EAChE,MAAM,EAAE,CAAC,CAAC,EAAE,EAAE,CAAE,CAAC,EAAU,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,GAAI,EAC7D,eAAM,SAAS,EAAC,SAAS,GAAG,EAC5B,iBAAQ,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAC,cAAI,EAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EACtF,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,YAAE,KAAC,IAAI,IAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAE,EAAE,GAAI,GAAS,EAChF,iBAAQ,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAC,oBAAK,EAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EACzF,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,YAAE,KAAC,IAAI,IAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,EAAE,GAAI,GAAS,EACpF,iBAAQ,SAAS,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAC,cAAI,EAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAC3F,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC,GAAG,EAAE,YAAE,KAAC,IAAI,IAAC,IAAI,EAAC,WAAW,EAAC,IAAI,EAAE,EAAE,GAAI,GAAS,EAC1F,iBAAQ,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAC,oBAAK,EAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EACzF,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,YAAE,KAAC,IAAI,IAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAE,EAAE,GAAI,GAAS,EAEpF,eAAM,SAAS,EAAC,SAAS,GAAG,EAC5B,eAAK,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,WAAW,aAC1C,kBAAQ,KAAK,EAAC,oBAAK,EAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EACxD,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,aACzD,eAAM,SAAS,EAAC,YAAY,EAAC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,KAAK,IAAI,SAAS,EAAE,GAAI,EAC5E,YAAG,SAAS,EAAC,YAAY,uBAAM,IACxB,EACR,IAAI,KAAK,OAAO,IAAI,CACnB,KAAC,WAAW,IAAC,KAAK,EAAC,oBAAK,EAAC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,EAC7E,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,CAAE,CAAC,EAAU,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAI,CACvG,IACG,EACN,eAAK,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,QAAQ,aACvC,iBAAQ,KAAK,EAAC,iCAAQ,EAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EAC3D,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,YACjE,YAAG,SAAS,EAAC,0BAA0B,EAAC,KAAK,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS,EAAE,uBAAO,GACrF,EACR,IAAI,KAAK,WAAW,IAAI,CACvB,KAAC,WAAW,IAAC,KAAK,EAAC,iCAAQ,EAAC,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAC/E,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAI,CAC/F,IACG,EAEL,CAAC,CAAC,QAAQ,IAAI,eAAM,SAAS,EAAC,SAAS,GAAG,EAC1C,CAAC,CAAC,QAAQ,IAAI,CACf,eAAK,SAAS,EAAC,YAAY,EAAC,GAAG,EAAE,UAAU,aACzC,iBAAQ,SAAS,EAAE,CAAC,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAC,cAAI,EAClE,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,EAAE,EACtC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,YAAE,KAAC,IAAI,IAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAE,EAAE,GAAI,GAAS,EACjG,IAAI,KAAK,MAAM,IAAI,CAClB,KAAC,SAAS,IAAC,KAAK,EAAC,2BAAO,EAAC,WAAW,EAAC,UAAU,EAAC,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EACpF,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,SAAS,EACnD,WAAW,EAAC,cAAI,EAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAC7C,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,GAAI,CACjG,IACG,CACL,IACG,GACK,CACd,CAAA;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { Editor } from '@tiptap/react';
2
+ /**
3
+ * 표 조작 오버레이 — 셀 경계의 +로 그 자리에 삽입, 머리 띠로 행/열 선택,
4
+ * 선택하면 문맥 메뉴(병합·분할·너비맞춤·삭제), 표 전체 선택이면 휴지통.
5
+ * 표 NodeView 를 만들지 않고 화면 좌표로 덧그린다 — ProseMirror 표 구현을 안 건드리려고.
6
+ */
7
+ export declare function TableTools({ editor }: {
8
+ editor: Editor;
9
+ }): import("react").JSX.Element | null;
10
+ //# sourceMappingURL=TableTools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TableTools.d.ts","sourceRoot":"","sources":["../src/TableTools.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAkB3C;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,sCAsOxD"}
@@ -0,0 +1,200 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useState } from 'react';
3
+ import { Icon } from './Icon.js';
4
+ import { ROW_MAX, ROW_MIN } from '@storkspear/post-editor-core/extensions/TableRowHeight';
5
+ const EMPTY = [];
6
+ /**
7
+ * 표 조작 오버레이 — 셀 경계의 +로 그 자리에 삽입, 머리 띠로 행/열 선택,
8
+ * 선택하면 문맥 메뉴(병합·분할·너비맞춤·삭제), 표 전체 선택이면 휴지통.
9
+ * 표 NodeView 를 만들지 않고 화면 좌표로 덧그린다 — ProseMirror 표 구현을 안 건드리려고.
10
+ */
11
+ export function TableTools({ editor }) {
12
+ const [geo, setGeo] = useState(null);
13
+ const [sel, setSel] = useState({ cells: 0, wholeTable: false, anchorRect: null });
14
+ const [menu, setMenu] = useState(false);
15
+ const measure = useCallback(() => {
16
+ if (!editor?.isEditable)
17
+ return setGeo(null);
18
+ const { state, view } = editor;
19
+ const { from } = state.selection;
20
+ let dom = null;
21
+ try {
22
+ dom = view.domAtPos(from).node;
23
+ }
24
+ catch {
25
+ return setGeo(null);
26
+ }
27
+ const el = (dom?.nodeType === 3 ? dom.parentElement : dom);
28
+ const table = el?.closest('table');
29
+ if (!table) {
30
+ setGeo(null);
31
+ setMenu(false);
32
+ return;
33
+ }
34
+ const tr = [...table.rows];
35
+ if (!tr.length)
36
+ return setGeo(null);
37
+ const tRect = table.getBoundingClientRect();
38
+ const cells0 = [...tr[0].cells];
39
+ const cols = cells0.map((c) => {
40
+ const r = c.getBoundingClientRect();
41
+ return { left: r.left, top: tRect.top - 14, width: r.width, height: 12 };
42
+ });
43
+ const rows = tr.map((row) => {
44
+ const r = row.getBoundingClientRect();
45
+ return { left: tRect.left - 14, top: r.top, width: 12, height: r.height };
46
+ });
47
+ const colEdges = cells0.map((c) => c.getBoundingClientRect().right);
48
+ const rowEdges = tr.map((r) => r.getBoundingClientRect().bottom);
49
+ const rowPos = tr.map((row) => { try {
50
+ return view.posAtDOM(row, 0) - 1;
51
+ }
52
+ catch {
53
+ return -1;
54
+ } });
55
+ const rowHeights = tr.map((row) => row.getBoundingClientRect().height);
56
+ const cellPos = tr.map((row) => [...row.cells].map((c) => {
57
+ try {
58
+ return view.posAtDOM(c, 0) - 1;
59
+ }
60
+ catch {
61
+ return -1;
62
+ }
63
+ }));
64
+ // 표 너비는 감싸개에 건다 — updateColumns() 가 table 의 인라인 width 를 지우기 때문.
65
+ const wrap = table.closest('.tableWrapper');
66
+ if (wrap) {
67
+ const st = editor.getAttributes('table').style ?? '';
68
+ const m = /width\s*:\s*([\d.]+)%/.exec(st);
69
+ wrap.style.width = m ? `${m[1]}%` : '';
70
+ wrap.style.marginInline = m ? 'auto' : '';
71
+ }
72
+ setGeo({
73
+ table: { left: tRect.left, top: tRect.top, width: tRect.width, height: tRect.height },
74
+ cols, rows, colEdges, rowEdges, cellPos, rowPos, rowHeights,
75
+ });
76
+ const selected = table.querySelectorAll('.selectedCell');
77
+ const total = table.querySelectorAll('td,th').length;
78
+ const first = selected[0]?.getBoundingClientRect();
79
+ const last = selected[selected.length - 1]?.getBoundingClientRect();
80
+ setSel({
81
+ cells: selected.length,
82
+ wholeTable: selected.length > 0 && selected.length === total,
83
+ anchorRect: first && last
84
+ ? { left: Math.min(first.left, last.left), top: Math.min(first.top, last.top),
85
+ width: Math.max(first.right, last.right) - Math.min(first.left, last.left),
86
+ height: Math.max(first.bottom, last.bottom) - Math.min(first.top, last.top) }
87
+ : null,
88
+ });
89
+ }, [editor]);
90
+ useEffect(() => {
91
+ measure();
92
+ editor.on('selectionUpdate', measure);
93
+ editor.on('transaction', measure);
94
+ window.addEventListener('scroll', measure, true);
95
+ window.addEventListener('resize', measure);
96
+ return () => {
97
+ editor.off('selectionUpdate', measure);
98
+ editor.off('transaction', measure);
99
+ window.removeEventListener('scroll', measure, true);
100
+ window.removeEventListener('resize', measure);
101
+ };
102
+ }, [editor, measure]);
103
+ if (!geo)
104
+ return null;
105
+ const c = () => editor.chain().focus();
106
+ const pos = geo.cellPos;
107
+ const insertColAfter = (i) => {
108
+ const a = pos[0]?.[i], h = pos[pos.length - 1]?.[i];
109
+ if (a >= 0 && h >= 0)
110
+ editor.chain().focus().setCellSelection({ anchorCell: a, headCell: h }).addColumnAfter().run();
111
+ };
112
+ const insertRowAfter = (i) => {
113
+ const row = pos[i] ?? EMPTY;
114
+ if (row.length)
115
+ editor.chain().focus().setCellSelection({ anchorCell: row[0], headCell: row[row.length - 1] }).addRowAfter().run();
116
+ };
117
+ const selectCol = (i) => {
118
+ const anchor = pos[0]?.[i], head = pos[pos.length - 1]?.[i];
119
+ if (anchor >= 0 && head >= 0)
120
+ editor.chain().focus().setCellSelection({ anchorCell: anchor, headCell: head }).run();
121
+ };
122
+ const selectRow = (i) => {
123
+ const row = pos[i] ?? EMPTY;
124
+ if (row.length)
125
+ editor.chain().focus().setCellSelection({ anchorCell: row[0], headCell: row[row.length - 1] }).run();
126
+ };
127
+ const selectAll = () => {
128
+ const a = pos[0]?.[0], h = pos[pos.length - 1]?.[(pos[pos.length - 1]?.length ?? 1) - 1];
129
+ if (a >= 0 && h >= 0)
130
+ editor.chain().focus().setCellSelection({ anchorCell: a, headCell: h }).run();
131
+ };
132
+ /** 행 높이를 심는다. null 이면 지운다(원래대로) */
133
+ const setRowHeight = (i, px) => {
134
+ const at = geo.rowPos[i];
135
+ if (at == null || at < 0)
136
+ return;
137
+ const node = editor.state.doc.nodeAt(at);
138
+ if (!node)
139
+ return;
140
+ const h = px == null ? null : Math.min(ROW_MAX, Math.max(ROW_MIN, Math.round(px)));
141
+ editor.view.dispatch(editor.state.tr.setNodeMarkup(at, undefined, { ...node.attrs, height: h }));
142
+ };
143
+ /**
144
+ * 행 경계 끌기.
145
+ *
146
+ * 포인터 이벤트로 받는다 — 마우스 전용으로 만들면 폰에서 못 쓴다.
147
+ * (라이브러리의 열 너비 조절이 마우스 전용이라 폰에서 안 되는 것과 같은 함정)
148
+ */
149
+ const startRowDrag = (ev, i) => {
150
+ ev.preventDefault();
151
+ ev.stopPropagation();
152
+ const startY = ev.clientY;
153
+ const startH = geo.rowHeights[i] ?? ROW_MIN;
154
+ const target = ev.currentTarget;
155
+ target.setPointerCapture(ev.pointerId);
156
+ const move = (e) => setRowHeight(i, startH + (e.clientY - startY));
157
+ const up = () => {
158
+ try {
159
+ target.releasePointerCapture(ev.pointerId);
160
+ }
161
+ catch { /* 이미 풀렸으면 그만 */ }
162
+ target.removeEventListener('pointermove', move);
163
+ target.removeEventListener('pointerup', up);
164
+ target.removeEventListener('pointercancel', up);
165
+ };
166
+ target.addEventListener('pointermove', move);
167
+ target.addEventListener('pointerup', up);
168
+ target.addEventListener('pointercancel', up);
169
+ };
170
+ /** 열 너비를 균등하게 — colwidth 를 지우면 표가 균등 배분으로 돌아간다 */
171
+ const evenWidths = () => {
172
+ const { state, view } = editor;
173
+ const tr = state.tr;
174
+ state.doc.descendants((node, p) => {
175
+ if (node.type.name === 'tableCell' || node.type.name === 'tableHeader') {
176
+ if (node.attrs.colwidth)
177
+ tr.setNodeMarkup(p, undefined, { ...node.attrs, colwidth: null });
178
+ }
179
+ });
180
+ view.dispatch(tr);
181
+ setMenu(false);
182
+ };
183
+ return (_jsxs("div", { className: "tt-layer", "data-editor-ui": "", children: [_jsx("button", { className: "tt-corner", style: { left: geo.table.left - 14, top: geo.table.top - 14 }, title: "\uD45C \uC804\uCCB4 \uC120\uD0DD", onMouseDown: (e) => e.preventDefault(), onClick: selectAll }), geo.cols.map((r, i) => (_jsx("button", { className: "tt-band", style: { left: r.left, top: r.top, width: r.width, height: r.height }, title: `${i + 1}번째 열 선택`, onMouseDown: (e) => e.preventDefault(), onClick: () => selectCol(i) }, 'c' + i))), geo.rows.map((r, i) => (_jsx("button", { className: "tt-band", style: { left: r.left, top: r.top, width: r.width, height: r.height }, title: `${i + 1}번째 행 선택`, onMouseDown: (e) => e.preventDefault(), onClick: () => selectRow(i) }, 'r' + i))), geo.colEdges.map((x, i) => (_jsx("button", { className: "tt-plus", style: { left: x - 9, top: geo.table.top - 23 }, title: `${i + 1}번째 열 뒤에 열 넣기`, onMouseDown: (e) => e.preventDefault(), onClick: () => insertColAfter(i), children: _jsx(PlusGlyph, {}) }, 'ce' + i))), geo.rowEdges.map((y, i) => (_jsx("button", { className: "tt-plus", style: { left: geo.table.left - 23, top: y - 9 }, title: `${i + 1}번째 행 아래에 행 넣기`, onMouseDown: (e) => e.preventDefault(), onClick: () => insertRowAfter(i), children: _jsx(PlusGlyph, {}) }, 're' + i))), sel.wholeTable && (_jsx("button", { className: "tt-trash", style: { left: geo.table.left + geo.table.width / 2 - 19, top: geo.table.top - 46 }, title: "\uD45C \uC0AD\uC81C", onMouseDown: (e) => e.preventDefault(), onClick: () => c().deleteTable().run(), children: _jsx(TrashGlyph, {}) })), geo.rowEdges.map((y, i) => (_jsx("div", { className: "tt-rowsize", style: { left: geo.table.left, top: y - 3, width: geo.table.width }, title: "\uB04C\uC5B4\uC11C \uD589 \uB192\uC774 \uC870\uC808 \u00B7 \uB450 \uBC88 \uB204\uB974\uBA74 \uC6D0\uB798\uB300\uB85C", onDoubleClick: () => setRowHeight(i, null), onPointerDown: (ev) => startRowDrag(ev, i) }, 'rs' + i))), sel.cells > 1 && !sel.wholeTable && sel.anchorRect && (_jsxs("div", { className: "tt-anchor", style: moreSpot(geo, sel.anchorRect), children: [_jsx("button", { className: "tt-more", title: "\uC140 \uC791\uC5C5", onMouseDown: (e) => e.preventDefault(), onClick: () => setMenu((v) => !v), children: "\u22EF" }), menu && (_jsxs("div", { className: "tt-menu", role: "menu", children: [_jsxs("button", { onClick: () => { c().mergeCells().run(); setMenu(false); }, children: [_jsx(Icon, { name: "rowPlus" }), " \uC140 \uBCD1\uD569"] }), _jsxs("button", { onClick: () => { c().splitCell().run(); setMenu(false); }, children: [_jsx(Icon, { name: "colPlus" }), " \uC140 \uBD84\uD560"] }), _jsxs("button", { onClick: evenWidths, children: [_jsx(Icon, { name: "table" }), " \uB108\uBE44 \uB9DE\uCDA4"] }), _jsx("span", { className: "tt-menu-div" }), _jsxs("button", { onClick: () => { c().deleteRow().run(); setMenu(false); }, children: [_jsx(TrashGlyph, {}), " \uD589 \uC0AD\uC81C"] }), _jsxs("button", { onClick: () => { c().deleteColumn().run(); setMenu(false); }, children: [_jsx(TrashGlyph, {}), " \uC5F4 \uC0AD\uC81C"] }), _jsxs("button", { className: "danger", onClick: () => { c().deleteTable().run(); setMenu(false); }, children: [_jsx(TrashGlyph, {}), " \uD45C \uC0AD\uC81C"] })] }))] }))] }));
184
+ }
185
+ /**
186
+ * `⋯` 버튼 자리 — **항상 표 위 바깥**, 선택 영역의 가로 한가운데.
187
+ *
188
+ * 규칙을 하나로 둔다. 행이면 왼쪽에 두는 식으로 갈랐더니 본문이 전체 폭일 때
189
+ * 표 왼쪽에 공간이 없어 결국 표와 겹쳤다. 위쪽은 어떤 경우에도 비어 있다.
190
+ * 경계 `+`(-23) 보다 더 바깥(-46)이라 서로 안 겹친다.
191
+ */
192
+ function moreSpot(geo, r) {
193
+ return {
194
+ left: Math.max(8, Math.min(r.left + r.width / 2 - 12, window.innerWidth - 210)),
195
+ top: Math.max(8, geo.table.top - 46),
196
+ };
197
+ }
198
+ const PlusGlyph = () => (_jsx("svg", { viewBox: "0 0 24 24", width: "12", height: "12", fill: "none", stroke: "currentColor", strokeWidth: "2.6", strokeLinecap: "round", children: _jsx("path", { d: "M12 5v14M5 12h14" }) }));
199
+ const TrashGlyph = () => (_jsx("svg", { viewBox: "0 0 24 24", width: "15", height: "15", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: "M4 7h16M9.5 7V5h5v2M6.5 7l.8 12.2A1.8 1.8 0 0 0 9.1 21h5.8a1.8 1.8 0 0 0 1.8-1.8L17.5 7" }) }));
200
+ //# sourceMappingURL=TableTools.js.map