for-humanity 0.11.0 → 0.13.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 (226) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +16 -9
  3. package/dist/browser.js +16 -16
  4. package/dist/cli.css +2 -941
  5. package/dist/cli.js +92 -2290
  6. package/dist/config.js +1 -0
  7. package/dist/constant/copy.d.ts +100 -0
  8. package/dist/constant/site.d.ts +20 -0
  9. package/dist/constant/status.d.ts +19 -0
  10. package/dist/copy-C1uzvEnr.js +103 -0
  11. package/dist/group-path-XAThmzvC.js +10 -0
  12. package/dist/load-site-CbpLEhwu.js +2379 -0
  13. package/dist/logger-Dv1i9xSV.js +9 -0
  14. package/dist/mermaid/THIRD-PARTY-NOTICES.txt +3134 -0
  15. package/dist/mermaid/chunk--TN-qHgG.js +2 -0
  16. package/dist/mermaid/chunk-0kM1Lv4D.js +2 -0
  17. package/dist/mermaid/chunk-3FHei3pH.js +2001 -0
  18. package/dist/mermaid/chunk-3_2p2GqN.js +2 -0
  19. package/dist/mermaid/chunk-6HLei9i0.js +84 -0
  20. package/dist/mermaid/chunk-7Sz3kAwl.js +2214 -0
  21. package/dist/mermaid/chunk-7Tarqbpa.js +18047 -0
  22. package/dist/mermaid/chunk-9kkt8lzL.js +10 -0
  23. package/dist/mermaid/chunk-B--pNBQF.js +884 -0
  24. package/dist/mermaid/chunk-B4F-pysn.js +4241 -0
  25. package/dist/mermaid/chunk-B58sEIY_.js +14 -0
  26. package/dist/mermaid/chunk-B9_UWzSH.js +1517 -0
  27. package/dist/mermaid/chunk-BB8uZHwY.js +75 -0
  28. package/dist/mermaid/chunk-BFfk0LEc.js +717 -0
  29. package/dist/mermaid/chunk-BKri0qr7.js +89 -0
  30. package/dist/mermaid/chunk-BL404pH5.js +26 -0
  31. package/dist/mermaid/chunk-BLeiQSWo.js +354 -0
  32. package/dist/mermaid/chunk-BOmH_UFd.js +2 -0
  33. package/dist/mermaid/chunk-BSej9Nhy.js +1361 -0
  34. package/dist/mermaid/chunk-BYzrzvCI.js +12 -0
  35. package/dist/mermaid/chunk-BariMtWr.js +3020 -0
  36. package/dist/mermaid/chunk-BcrBoMwH.js +2352 -0
  37. package/dist/mermaid/chunk-Bok8z-Yo.js +67 -0
  38. package/dist/mermaid/chunk-BpTHLKQM.js +1970 -0
  39. package/dist/mermaid/chunk-BqjuxRsC.js +733 -0
  40. package/dist/mermaid/chunk-BuvPd-s5.js +1073 -0
  41. package/dist/mermaid/chunk-BvVehae3.js +5069 -0
  42. package/dist/mermaid/chunk-C-JABYpd.js +3075 -0
  43. package/dist/mermaid/chunk-C9XlTm4w.js +2 -0
  44. package/dist/mermaid/chunk-CApNbSU9.js +6 -0
  45. package/dist/mermaid/chunk-CCM2AH-C.js +9 -0
  46. package/dist/mermaid/chunk-CD1E0F_N.js +2 -0
  47. package/dist/mermaid/chunk-CDWvKG7D.js +2 -0
  48. package/dist/mermaid/chunk-CEVPpC1F.js +34 -0
  49. package/dist/mermaid/chunk-CLDr12lf.js +47504 -0
  50. package/dist/mermaid/chunk-CLVYqOBI.js +61 -0
  51. package/dist/mermaid/chunk-CLeOkFb5.js +15 -0
  52. package/dist/mermaid/chunk-CLgRAtOz.js +37 -0
  53. package/dist/mermaid/chunk-CMqFNUBl.js +134 -0
  54. package/dist/mermaid/chunk-CTPaBJXB.js +2 -0
  55. package/dist/mermaid/chunk-CVi6IGBL.js +2 -0
  56. package/dist/mermaid/chunk-CWV_EN-4.js +4745 -0
  57. package/dist/mermaid/chunk-CZ0dAs3g.js +2 -0
  58. package/dist/mermaid/chunk-C_YGy4Fn.js +236 -0
  59. package/dist/mermaid/chunk-CcVH22Yt.js +100 -0
  60. package/dist/mermaid/chunk-CgG6CD9_.js +7175 -0
  61. package/dist/mermaid/chunk-CgOP6IDB.js +3397 -0
  62. package/dist/mermaid/chunk-Cgg2UTSf.js +13 -0
  63. package/dist/mermaid/chunk-ChJfJJ1A.js +214 -0
  64. package/dist/mermaid/chunk-Ck5XRA46.js +447 -0
  65. package/dist/mermaid/chunk-Ckuw16cl.js +203 -0
  66. package/dist/mermaid/chunk-CpAbRCbF.js +1880 -0
  67. package/dist/mermaid/chunk-CuYekbSv.js +2 -0
  68. package/dist/mermaid/chunk-CwS4wZ5a.js +2 -0
  69. package/dist/mermaid/chunk-D-bcrBO3.js +82 -0
  70. package/dist/mermaid/chunk-D3IEAkCW.js +76 -0
  71. package/dist/mermaid/chunk-D8XfBJ_K.js +645 -0
  72. package/dist/mermaid/chunk-D9Ddd9y4.js +2228 -0
  73. package/dist/mermaid/chunk-DBh1mIbK.js +12 -0
  74. package/dist/mermaid/chunk-DD6Df6Ic.js +5 -0
  75. package/dist/mermaid/chunk-DGVDaXjx.js +8 -0
  76. package/dist/mermaid/chunk-DK9KqIXj.js +632 -0
  77. package/dist/mermaid/chunk-DORnIAsy.js +4487 -0
  78. package/dist/mermaid/chunk-DU69_VcF.js +363 -0
  79. package/dist/mermaid/chunk-DVNvSFoz.js +2144 -0
  80. package/dist/mermaid/chunk-DZe-4kde.js +20 -0
  81. package/dist/mermaid/chunk-D_a06Rz4.js +287 -0
  82. package/dist/mermaid/chunk-DfyFJJi2.js +972 -0
  83. package/dist/mermaid/chunk-DhKYPp1Z.js +2 -0
  84. package/dist/mermaid/chunk-DjFqgNTG.js +94 -0
  85. package/dist/mermaid/chunk-DmbDDrr-.js +65 -0
  86. package/dist/mermaid/chunk-DpuxlyL_.js +696 -0
  87. package/dist/mermaid/chunk-DqGhppWe.js +2 -0
  88. package/dist/mermaid/chunk-DqPZxHXa.js +5 -0
  89. package/dist/mermaid/chunk-Dt_1Jyif.js +293 -0
  90. package/dist/mermaid/chunk-DyQBE_NW.js +1935 -0
  91. package/dist/mermaid/chunk-JckyC4mW.js +1009 -0
  92. package/dist/mermaid/chunk-K7Myuj2-.js +1334 -0
  93. package/dist/mermaid/chunk-KuPYb48V.js +275 -0
  94. package/dist/mermaid/chunk-MFVRUoO0.js +4020 -0
  95. package/dist/mermaid/chunk-MbInCS_6.js +28 -0
  96. package/dist/mermaid/chunk-MexOLyKV.js +355 -0
  97. package/dist/mermaid/chunk-NnbDtJzg.js +21856 -0
  98. package/dist/mermaid/chunk-SGpOYZbI.js +2139 -0
  99. package/dist/mermaid/chunk-TbxLDlcU.js +198 -0
  100. package/dist/mermaid/chunk-TftHqGLg.js +490 -0
  101. package/dist/mermaid/chunk-UjQ8vA_Z.js +4138 -0
  102. package/dist/mermaid/chunk-YgmVTKqc.js +16 -0
  103. package/dist/mermaid/chunk-_a9EZE1v.js +1798 -0
  104. package/dist/mermaid/chunk-dTb8jqeP.js +2 -0
  105. package/dist/mermaid/chunk-fFdMfF8S.js +2240 -0
  106. package/dist/mermaid/chunk-feYbMzBj.js +1267 -0
  107. package/dist/mermaid/chunk-g3Pn8DkW.js +46 -0
  108. package/dist/mermaid/chunk-hM77LvCb.js +19832 -0
  109. package/dist/mermaid/chunk-hUDb-eAW.js +527 -0
  110. package/dist/mermaid/chunk-tpABtZ-3.js +4252 -0
  111. package/dist/mermaid/chunk-xIbz3oFK.js +59 -0
  112. package/dist/mermaid/chunk-yBz-vst0.js +976 -0
  113. package/dist/mermaid/mermaid-BhtR3wyB.js +1145 -0
  114. package/dist/run-site-command-C80YoNOe.js +129 -0
  115. package/dist/site-config-bllORGpm.js +90 -0
  116. package/dist/site.js +1 -0
  117. package/dist/start-dev-server-C4_Lu1nZ.js +176 -0
  118. package/dist/to-change-queue-DbEnZGNK.js +34 -0
  119. package/dist/to-error-message-pF6_7wGK.js +16 -0
  120. package/dist/type/group-path.d.ts +7 -0
  121. package/dist/type/site-config.d.ts +57 -0
  122. package/package.json +34 -17
  123. package/src/component/widget/cursor-face/_constant/cursor-face.ts +0 -14
  124. package/src/component/widget/cursor-face/wg-cursor-face.css +0 -15
  125. package/src/component/widget/cursor-face/wg-cursor-face.tsx +0 -153
  126. package/src/component/widget/navigation/_constant/locale.ts +0 -9
  127. package/src/component/widget/navigation/_constant/navigation-data-schema.ts +0 -33
  128. package/src/component/widget/navigation/_constant/navigation.ts +0 -9
  129. package/src/component/widget/navigation/_constant/reading-line.ts +0 -4
  130. package/src/component/widget/navigation/_function/restore-navigation-scroll.ts +0 -59
  131. package/src/component/widget/navigation/_function/to-doc-groups/_to-doc-branches.ts +0 -15
  132. package/src/component/widget/navigation/_function/to-doc-groups/_to-doc-group-branches.ts +0 -70
  133. package/src/component/widget/navigation/_function/to-doc-groups/to-doc-groups.ts +0 -15
  134. package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-document.ts +0 -13
  135. package/src/component/widget/navigation/_function/to-navigation-data/_to-navigation-group.ts +0 -13
  136. package/src/component/widget/navigation/_function/to-navigation-data/to-navigation-data.ts +0 -18
  137. package/src/component/widget/navigation/_function/to-navigation-json.ts +0 -8
  138. package/src/component/widget/navigation/_function/to-toc-groups.ts +0 -38
  139. package/src/component/widget/navigation/_type/doc-branch.ts +0 -11
  140. package/src/component/widget/navigation/_type/doc-group.ts +0 -24
  141. package/src/component/widget/navigation/_type/navigation-data.ts +0 -75
  142. package/src/component/widget/navigation/_type/navigation-focus-snapshot.ts +0 -17
  143. package/src/component/widget/navigation/_type/toc-group.ts +0 -39
  144. package/src/component/widget/navigation/_wg-navigation-nav-list.css +0 -48
  145. package/src/component/widget/navigation/_wg-navigation-nav-list.tsx +0 -61
  146. package/src/component/widget/navigation/_wg-navigation-nav.css +0 -21
  147. package/src/component/widget/navigation/_wg-navigation-nav.tsx +0 -33
  148. package/src/component/widget/navigation/_wg-navigation-scroll.tsx +0 -117
  149. package/src/component/widget/navigation/_wg-navigation-toc.css +0 -91
  150. package/src/component/widget/navigation/_wg-navigation-toc.tsx +0 -114
  151. package/src/component/widget/navigation/wg-navigation.css +0 -282
  152. package/src/component/widget/navigation/wg-navigation.tsx +0 -420
  153. package/src/component/widget/prose/_constant/heading-highlight.ts +0 -4
  154. package/src/component/widget/prose/_constant/section.ts +0 -7
  155. package/src/component/widget/prose/_constant/status.ts +0 -9
  156. package/src/component/widget/prose/_function/has-block-content.ts +0 -30
  157. package/src/component/widget/prose/_function/rehype-head.ts +0 -31
  158. package/src/component/widget/prose/_function/rehype-sections/_to-section-headings.ts +0 -20
  159. package/src/component/widget/prose/_function/rehype-sections/rehype-sections.ts +0 -21
  160. package/src/component/widget/prose/_function/rehype-tables.ts +0 -22
  161. package/src/component/widget/prose/_function/remark-blocks.ts +0 -112
  162. package/src/component/widget/prose/_function/remark-flow.ts +0 -26
  163. package/src/component/widget/prose/_function/remark-links.ts +0 -33
  164. package/src/component/widget/prose/_function/remark-parts.ts +0 -26
  165. package/src/component/widget/prose/_function/remark-status.ts +0 -59
  166. package/src/component/widget/prose/_function/remark-swatch.ts +0 -16
  167. package/src/component/widget/prose/_function/remark-unknown-directives.ts +0 -25
  168. package/src/component/widget/prose/wg-prose.css +0 -454
  169. package/src/component/widget/prose/wg-prose.tsx +0 -43
  170. package/src/component/widget/shell/wg-shell.css +0 -63
  171. package/src/component/widget/shell/wg-shell.tsx +0 -88
  172. package/src/component/widget/shell-controls/wg-shell-controls.css +0 -4
  173. package/src/component/widget/shell-controls/wg-shell-controls.tsx +0 -170
  174. package/src/constant/asset.ts +0 -63
  175. package/src/constant/copy.ts +0 -133
  176. package/src/constant/doc.ts +0 -13
  177. package/src/constant/font.ts +0 -34
  178. package/src/constant/site.ts +0 -24
  179. package/src/constant/status.ts +0 -15
  180. package/src/entry/browser/browser.tsx +0 -22
  181. package/src/entry/cli/_constant/cli.ts +0 -19
  182. package/src/entry/cli/_constant/crawling.ts +0 -14
  183. package/src/entry/cli/_constant/snippet.ts +0 -21
  184. package/src/entry/cli/_function/create-app.tsx +0 -57
  185. package/src/entry/cli/_function/create-dev-app.ts +0 -87
  186. package/src/entry/cli/_function/create-processor/_rehype-assets.ts +0 -70
  187. package/src/entry/cli/_function/create-processor/_rehype-heading-ids.ts +0 -34
  188. package/src/entry/cli/_function/create-processor/_remark-report.ts +0 -15
  189. package/src/entry/cli/_function/create-processor/_remark-snippets.ts +0 -117
  190. package/src/entry/cli/_function/create-processor/_shiki-transformers.ts +0 -55
  191. package/src/entry/cli/_function/create-processor/create-processor.ts +0 -64
  192. package/src/entry/cli/_function/read-docs.ts +0 -128
  193. package/src/entry/cli/_function/read-home.ts +0 -32
  194. package/src/entry/cli/_function/to-crawling-app.ts +0 -42
  195. package/src/entry/cli/cli.test.ts +0 -782
  196. package/src/entry/cli/cli.ts +0 -206
  197. package/src/env.d.ts +0 -6
  198. package/src/page/doc/pg-doc.tsx +0 -35
  199. package/src/page/home/pg-home.tsx +0 -40
  200. package/src/store/navigation/create-navigation-stores.test.ts +0 -74
  201. package/src/store/navigation/create-navigation-stores.ts +0 -59
  202. package/src/store/navigation/navigation-state.ts +0 -36
  203. package/src/store/navigation/navigation-storage.ts +0 -9
  204. package/src/store/navigation/to-navigation-scroll-state.ts +0 -42
  205. package/src/style/base.css +0 -28
  206. package/src/style/token.css +0 -186
  207. package/src/type/doc-content.ts +0 -15
  208. package/src/type/doc-data.ts +0 -38
  209. package/src/type/doc-file-data.ts +0 -21
  210. package/src/type/doc-heading.ts +0 -17
  211. package/src/type/doc-outline.ts +0 -16
  212. package/src/type/doc-section.ts +0 -9
  213. package/src/type/doc.ts +0 -20
  214. package/src/type/group-path.ts +0 -7
  215. package/src/type/site-assets.ts +0 -25
  216. package/src/type/site-config.ts +0 -73
  217. package/src/type/vfile-data.d.ts +0 -10
  218. package/src/util/async/to-change-queue.test.ts +0 -58
  219. package/src/util/async/to-change-queue.ts +0 -43
  220. package/src/util/dom/find-hash-target.ts +0 -18
  221. package/src/util/dom/reveal-hash-target.ts +0 -15
  222. package/src/util/error/to-error-message.ts +0 -10
  223. package/src/util/file/to-asset-path.ts +0 -26
  224. package/src/util/font/to-font-css.test.ts +0 -41
  225. package/src/util/font/to-font-css.ts +0 -28
  226. package/src/util/mermaid/render-flow.js +0 -321
@@ -1,282 +0,0 @@
1
- .wg_navigation__brandRow {
2
- display: flex;
3
-
4
- /* 두 스크롤 영역 바깥 · 목록과 목차 위에서 이름 고정 */
5
- position: sticky;
6
- top: 0;
7
- z-index: var(--app-z-index-sticky);
8
- align-self: start;
9
- min-width: 0;
10
- height: var(--app-size-brand);
11
- padding-block: var(--app-space-page-top) var(--app-space-group);
12
- background: var(--app-color-ground);
13
- gap: var(--app-space-inline);
14
- align-items: start;
15
- }
16
-
17
- .wg_navigation__brand {
18
- display: block;
19
- flex: 1;
20
- min-width: 0;
21
- overflow: hidden;
22
- color: var(--app-color-text-strong);
23
- font-family: var(--app-font-brand);
24
- font-size: var(--app-font-size-title);
25
- font-weight: var(--app-font-weight-mark);
26
- line-height: var(--app-line-height-title);
27
- letter-spacing: var(--app-letter-spacing-brand);
28
- text-decoration: none;
29
- text-overflow: ellipsis;
30
- white-space: nowrap;
31
-
32
- &:hover {
33
- text-decoration: underline;
34
- }
35
-
36
- &:focus-visible {
37
- /* 브랜드도 hover와 같은 밑줄로 키보드 위치 표시 */
38
- outline: none;
39
- text-decoration: underline;
40
- }
41
- }
42
-
43
- .wg_navigation__menuIcon {
44
- width: 20px;
45
- height: 20px;
46
- fill: none;
47
- stroke: currentcolor;
48
- stroke-linecap: round;
49
- stroke-width: 1.5;
50
- }
51
-
52
- .wg_navigation__menuToggle {
53
- display: none;
54
- align-items: center;
55
- justify-content: center;
56
- flex: none;
57
- width: var(--app-size-menu-button);
58
- height: var(--app-size-menu-button);
59
- padding: 0;
60
- border: 0;
61
- border-radius: var(--app-radius-box);
62
- background: transparent;
63
- color: var(--app-color-text-strong);
64
- cursor: pointer;
65
-
66
- &:hover {
67
- background: var(--app-color-accent-soft);
68
- }
69
-
70
- &:focus-visible {
71
- outline: var(--app-outline-focus);
72
- outline-offset: var(--app-outline-focus-offset);
73
- }
74
- }
75
-
76
- .wg_navigation__menuToggle--visible {
77
- display: flex;
78
- }
79
-
80
- /* sticky 틀 안에서 문서 목록과 목차의 스크롤 높이를 독립 유지 */
81
- .wg_navigation__sidebar {
82
- grid-area: nav;
83
- position: sticky;
84
- top: 0;
85
- align-self: start;
86
- min-width: 0;
87
- height: 100dvh;
88
- margin-inline-end: var(--app-space-sidebar-gap);
89
- }
90
-
91
- .wg_navigation__documents {
92
- /* 모든 배치에서 남은 탐색 높이를 문서 목록 전체에 사용 */
93
- flex: 1;
94
- min-height: 0;
95
- }
96
-
97
- .wg_navigation__outline {
98
- display: flex;
99
- flex-direction: column;
100
- flex: 1;
101
- min-height: 0;
102
-
103
- &:where(:has(.wg_navigation__headings:empty)) {
104
- display: none;
105
- }
106
- }
107
-
108
- .wg_navigation__headings {
109
- flex: 1;
110
- min-height: 0;
111
- }
112
-
113
- .wg_navigation__navigation {
114
- display: flex;
115
- flex-direction: column;
116
- height: calc(100dvh - var(--app-size-brand));
117
- min-width: 0;
118
- min-height: 0;
119
- }
120
-
121
- .wg_navigation__tocRail {
122
- /* body의 오른쪽 grid 열 · 문서 스크롤과 독립된 뷰포트 기준 sticky */
123
- grid-area: outline;
124
- position: sticky;
125
- top: 0;
126
- align-self: start;
127
- min-width: 0;
128
- height: 100dvh;
129
- margin-inline-start: var(--app-space-sidebar-gap);
130
- padding-top: var(--app-size-brand);
131
-
132
- & > .wg_navigation__outline {
133
- height: 100%;
134
- }
135
-
136
- &:where(:has(.wg_navigation__headings:empty)) {
137
- display: none;
138
- }
139
- }
140
-
141
- .wg_navigation__scroll {
142
- overflow-y: auto;
143
- overscroll-behavior-y: contain;
144
- scroll-padding-block: var(--app-size-navigation-fade);
145
- }
146
-
147
- .wg_navigation__scroll--ready {
148
- /* 흐림으로 넘침을 알릴 때만 손잡이 숨김 · JS 없으면 네이티브 손잡이 유지 */
149
- scrollbar-width: none;
150
- }
151
-
152
- .wg_navigation__scroll--above {
153
- mask-image: linear-gradient(to bottom, transparent, #000 var(--app-size-navigation-fade));
154
- }
155
-
156
- .wg_navigation__scroll--below {
157
- mask-image: linear-gradient(to bottom, #000 calc(100% - var(--app-size-navigation-fade)), transparent);
158
- }
159
-
160
- .wg_navigation__scroll--above.wg_navigation__scroll--below {
161
- mask-image: linear-gradient(to bottom, transparent, #000 var(--app-size-navigation-fade), #000 calc(100% - var(--app-size-navigation-fade)), transparent);
162
- }
163
-
164
- .wg_navigation__drawer {
165
- /* 네이티브 모달의 top layer · 뷰포트 왼쪽에 고정 */
166
- position: fixed;
167
- inset: 0 auto 0 0;
168
- width: min(var(--app-size-drawer), calc(100% - var(--app-space-gutter) * 2));
169
- max-width: none;
170
- height: 100dvh;
171
- max-height: 100dvh;
172
- margin: 0;
173
- padding: 0 var(--app-space-gutter) var(--app-space-group);
174
- border: 0;
175
- border-right: 1px solid var(--app-color-border-layout);
176
- background: var(--app-color-ground);
177
- color: var(--app-color-text);
178
- overflow: hidden;
179
-
180
- &::backdrop {
181
- background: var(--app-color-backdrop);
182
- }
183
- }
184
-
185
- .wg_navigation__drawerHeader {
186
- /* 두 스크롤 영역 바깥에서 닫기 버튼 유지 */
187
- display: flex;
188
- align-items: center;
189
- justify-content: space-between;
190
- gap: var(--app-space-inline);
191
- min-height: var(--app-size-mobile-header);
192
- margin-bottom: var(--app-space-block);
193
- background: var(--app-color-ground);
194
- }
195
-
196
- .wg_navigation__drawerTitle {
197
- margin: 0;
198
- font-size: var(--app-font-size-part);
199
- font-weight: var(--app-font-weight-strong);
200
- }
201
-
202
- .wg_navigation__drawerClose {
203
- display: flex;
204
- align-items: center;
205
- justify-content: center;
206
- flex: none;
207
- width: var(--app-size-menu-button);
208
- height: var(--app-size-menu-button);
209
- padding: 0;
210
- border: 0;
211
- border-radius: var(--app-radius-box);
212
- background: transparent;
213
- color: var(--app-color-text-strong);
214
- cursor: pointer;
215
-
216
- &:hover {
217
- background: var(--app-color-accent-soft);
218
- }
219
-
220
- &:focus-visible {
221
- outline: var(--app-outline-focus);
222
- outline-offset: var(--app-outline-focus-offset);
223
- }
224
- }
225
-
226
- .wg_navigation__drawerContent {
227
- min-width: 0;
228
- height: calc(100% - var(--app-size-mobile-header) - var(--app-space-block));
229
-
230
- & > .wg_navigation__navigation {
231
- height: 100%;
232
- }
233
- }
234
-
235
- /* 강제 색상 환경은 흐림 대신 시스템 손잡이로 넘침 표시 */
236
- @media (forced-colors: active) {
237
- .wg_navigation__scroll {
238
- mask-image: none;
239
- scrollbar-width: thin;
240
- scrollbar-color: auto;
241
- }
242
- }
243
-
244
- /* 와이드 화면에서만 현재 문서 목차 표시 */
245
- @media (width < 1360px) {
246
- .wg_navigation__tocRail {
247
- display: none;
248
- }
249
- }
250
-
251
- @media (width < 1024px) {
252
- .wg_navigation__sidebar {
253
- position: static;
254
- height: auto;
255
- margin-block-end: var(--app-space-block);
256
- margin-inline-end: 0;
257
- border-bottom: 1px solid var(--app-color-border-layout);
258
- }
259
-
260
- .wg_navigation__sidebar > .wg_navigation__navigation {
261
- height: auto;
262
- }
263
-
264
- .wg_navigation__sidebar > .wg_navigation__navigation > .wg_navigation__documents {
265
- max-height: none;
266
- }
267
-
268
- .wg_navigation__sidebar--navigationReady {
269
- /* 본문을 스크롤해도 메뉴 진입 유지 · 스크립트 없는 긴 목록에는 적용하지 않음 */
270
- position: sticky;
271
- top: 0;
272
- z-index: var(--app-z-index-sticky);
273
- background: var(--app-color-ground);
274
- }
275
-
276
- .wg_navigation__brandRow {
277
- position: static;
278
- height: var(--app-size-mobile-header);
279
- padding-block: 0;
280
- align-items: center;
281
- }
282
- }
@@ -1,420 +0,0 @@
1
- import clsx from "clsx";
2
- import {type DOMAttributes, Fragment, useLayoutEffect, useRef, useState} from "hono/jsx";
3
- import {flushSync} from "hono/jsx/dom";
4
- import {navigation_mobile_query, navigation_wide_query} from "@/component/widget/navigation/_constant/navigation";
5
- import {restoreNavigationScroll} from "@/component/widget/navigation/_function/restore-navigation-scroll";
6
- import type {NavigationData} from "@/component/widget/navigation/_type/navigation-data";
7
- import type {NavigationFocusSnapshot} from "@/component/widget/navigation/_type/navigation-focus-snapshot";
8
- import {WgNavigationNav} from "@/component/widget/navigation/_wg-navigation-nav";
9
- import {WgNavigationScroll} from "@/component/widget/navigation/_wg-navigation-scroll";
10
- import {WgNavigationToc} from "@/component/widget/navigation/_wg-navigation-toc";
11
- import {copy_nav_close, copy_nav_open, copy_nav_title} from "@/constant/copy";
12
- import type {createNavigationStores} from "@/store/navigation/create-navigation-stores";
13
- import type {NavigationLayout, NavigationPosition} from "@/store/navigation/navigation-state";
14
- import {navigation_scroll_storage_key, navigation_storage_version} from "@/store/navigation/navigation-storage";
15
- import {toNavigationScrollState} from "@/store/navigation/to-navigation-scroll-state";
16
- import {revealHashTarget} from "@/util/dom/reveal-hash-target";
17
- import "./wg-navigation.css";
18
-
19
- /**
20
- * 같은 Hono 탐색을 서버에서는 기본 링크로, 브라우저에서는 사용자 저장 상태로 렌더
21
- */
22
- export interface WgNavigationProps {
23
- /**
24
- * 서버가 정리한 문서 목록과 현재 페이지 목차
25
- */
26
- data: NavigationData;
27
- /**
28
- * 브라우저 진입점만 제공 · 없으면 저장소와 전역 DOM을 읽지 않음
29
- */
30
- stores?: ReturnType<typeof createNavigationStores>;
31
- }
32
-
33
- export const WgNavigation = (props: WgNavigationProps) => {
34
- const [layout, setLayout] = useState<NavigationLayout>(() => {
35
- if (props.stores === undefined) return "desktop";
36
- if (matchMedia(navigation_mobile_query).matches) return "drawer";
37
- return matchMedia(navigation_wide_query).matches ? "wide" : "desktop";
38
- });
39
- const [drawerOpen, setDrawerOpen] = useState(false);
40
- const navigationRef = useRef<HTMLDivElement | null>(null);
41
- const documentsRef = useRef<HTMLDivElement | null>(null);
42
- const drawerNavigationRef = useRef<HTMLDivElement | null>(null);
43
- const drawerDocumentsRef = useRef<HTMLDivElement | null>(null);
44
- const outlineRef = useRef<HTMLDivElement | null>(null);
45
- const dialogRef = useRef<HTMLDialogElement | null>(null);
46
- const openButtonRef = useRef<HTMLButtonElement | null>(null);
47
- const brandRef = useRef<HTMLAnchorElement | null>(null);
48
- const mobileRef = useRef<MediaQueryList | null>(null);
49
- const wideRef = useRef<MediaQueryList | null>(null);
50
- const layoutRef = useRef(layout);
51
- const focusRef = useRef<NavigationFocusSnapshot | null>(null);
52
- const restorePendingRef = useRef(true);
53
-
54
- /**
55
- * 저장된 배치와 현재 CSS 조건을 구분 · 조건 변경 직후 잘린 좌표를 이전 배치에 저장하지 않음
56
- */
57
- const getNavigationLayout = (): NavigationLayout => {
58
- if (mobileRef.current === null || wideRef.current === null) return layoutRef.current;
59
- if (mobileRef.current.matches) return "drawer";
60
- return wideRef.current.matches ? "wide" : "desktop";
61
- };
62
-
63
- /**
64
- * Hono의 이전 노드 정리가 새 배치 참조를 비우지 않도록 두 배치의 DOM 참조를 별도로 유지
65
- */
66
- const getNavigationPort = () => {
67
- return layoutRef.current === "drawer" ? drawerNavigationRef.current : navigationRef.current;
68
- };
69
-
70
- /**
71
- * 배치별 DOM은 분리하되 좌표 저장과 복원은 현재 표시되는 한 영역을 소비
72
- */
73
- const getDocumentsPort = () => {
74
- return layoutRef.current === "drawer" ? drawerDocumentsRef.current : documentsRef.current;
75
- };
76
-
77
- /**
78
- * 모든 링크의 URL·표시명으로 사이트 공통 문서 목록의 동일성 판정
79
- */
80
- const getNavigationSignature = () => {
81
- const navigation = getNavigationPort();
82
- if (navigation === null) return undefined;
83
- return [...navigation.querySelectorAll<HTMLAnchorElement>(".wg_navigationNav__root a[href]")].map((link) => `${link.pathname}:${link.textContent}`).join("\n");
84
- };
85
-
86
- /**
87
- * 닫기·이동보다 먼저 실제 좌표 보존 · 숨긴 모달과 아직 복원하지 않은 배치는 저장하지 않음
88
- */
89
- const saveScroll = () => {
90
- const documents = getDocumentsPort();
91
- if (
92
- props.stores === undefined ||
93
- documents === null ||
94
- outlineRef.current === null ||
95
- restorePendingRef.current ||
96
- layoutRef.current !== getNavigationLayout() ||
97
- (layoutRef.current === "drawer" && dialogRef.current?.open !== true)
98
- ) {
99
- return;
100
- }
101
- const signature = getNavigationSignature();
102
- if (signature === undefined) return;
103
- const navigationPosition: NavigationPosition = {
104
- navigation: signature,
105
- pathname: location.pathname,
106
- documents: documents.scrollTop,
107
- outline: outlineRef.current.scrollTop,
108
- };
109
- props.stores.scroll.setState((state) => ({
110
- positions: {
111
- ...state.positions,
112
- [layoutRef.current]: navigationPosition,
113
- },
114
- }));
115
- };
116
-
117
- /**
118
- * 연결된 영역에서 첫 paint 전 복원 · 문서는 페이지 간 공유하고 목차는 같은 경로에서만 재사용
119
- */
120
- const restoreScroll = () => {
121
- layoutRef.current = getNavigationLayout();
122
- const documents = getDocumentsPort();
123
- const navigation = getNavigationPort();
124
- if (props.stores === undefined || documents === null || outlineRef.current === null || navigation === null) return;
125
- if (layoutRef.current === "drawer" && dialogRef.current?.open !== true) return;
126
- const signature = getNavigationSignature();
127
- if (signature === undefined) return;
128
- const position = props.stores.scroll.getState().positions[layoutRef.current];
129
- documents.scrollTop = 0;
130
- outlineRef.current.scrollTop = 0;
131
- if (position === undefined) {
132
- restoreNavigationScroll({key: navigation_scroll_storage_key, version: navigation_storage_version, layout: layoutRef.current, toState: toNavigationScrollState});
133
- } else if (position.navigation === signature) {
134
- documents.scrollTop = position.documents;
135
- if (position.pathname === location.pathname) outlineRef.current.scrollTop = position.outline;
136
- }
137
- navigation.dataset.navigationLayout = layoutRef.current;
138
- restorePendingRef.current = false;
139
- saveScroll();
140
- // 좌표 설정의 비동기 scroll 이벤트를 기다리지 않고 첫 paint의 넘침 표시를 맞춘다
141
- documents.dispatchEvent(new Event("scroll"));
142
- outlineRef.current.dispatchEvent(new Event("scroll"));
143
- };
144
-
145
- /**
146
- * 네이티브 close가 이전 열기 이벤트보다 늦게 도착해도 새 모달을 잠금 해제하지 않음
147
- */
148
- const handleDrawerClose = (_event: Event) => {
149
- if (dialogRef.current?.open === true) return;
150
- document.body.classList.remove("wg_shell__root--drawerOpen");
151
- setDrawerOpen(false);
152
- };
153
-
154
- /**
155
- * 네이티브 포커스 복원을 유지하면서 링크의 기본 이동보다 먼저 배경 잠금 해제
156
- */
157
- const closeNavigation = () => {
158
- saveScroll();
159
- dialogRef.current?.close();
160
- document.body.classList.remove("wg_shell__root--drawerOpen");
161
- setDrawerOpen(false);
162
- };
163
-
164
- /**
165
- * 모바일에서만 네이티브 모달을 열고 독립된 드로어 좌표를 즉시 복원
166
- */
167
- const handleOpenButtonClick: DOMAttributes["onClick"] = (_event) => {
168
- if (getNavigationLayout() !== "drawer" || dialogRef.current === null || dialogRef.current.open) return;
169
- dialogRef.current.showModal();
170
- document.body.classList.add("wg_shell__root--drawerOpen");
171
- setDrawerOpen(true);
172
- restoreScroll();
173
- };
174
-
175
- const handleCloseButtonClick: DOMAttributes["onClick"] = (_event) => {
176
- closeNavigation();
177
- };
178
-
179
- /**
180
- * Escape의 네이티브 포커스 복원을 쓰되 좌표는 닫힌 모달이 되기 전에 보존
181
- */
182
- const handleDrawerCancel = (event: Event) => {
183
- event.preventDefault();
184
- closeNavigation();
185
- };
186
-
187
- /**
188
- * 대화상자 내부 빈 공간은 유지하고 경계 밖 배경만 닫기
189
- */
190
- const handleDrawerClick: DOMAttributes["onClick"] = (event) => {
191
- if (dialogRef.current === null || event.target !== dialogRef.current) return;
192
- const bounds = dialogRef.current.getBoundingClientRect();
193
- if (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom) closeNavigation();
194
- };
195
-
196
- /**
197
- * 보조키·새 탭 링크는 모달 유지 · 기본 문서 이동만 닫고 같은 문서 제목에 키보드 위치 전달
198
- */
199
- const handleNavigationClick: DOMAttributes["onClick"] = (event) => {
200
- saveScroll();
201
- if (
202
- dialogRef.current?.open !== true ||
203
- event.defaultPrevented ||
204
- event.button !== 0 ||
205
- event.metaKey ||
206
- event.ctrlKey ||
207
- event.shiftKey ||
208
- event.altKey ||
209
- !(event.target instanceof Element)
210
- ) {
211
- return;
212
- }
213
- const link = event.target.closest("a[href]");
214
- if (!(link instanceof HTMLAnchorElement) || (link.target !== "" && link.target !== "_self") || link.hasAttribute("download")) return;
215
- closeNavigation();
216
- if (link.origin === location.origin && link.pathname === location.pathname && link.search === location.search && link.hash) {
217
- const target = revealHashTarget(link.hash);
218
- if (target !== null) {
219
- target.tabIndex = -1;
220
- target.focus({preventScroll: true});
221
- }
222
- }
223
- };
224
-
225
- /**
226
- * 같은 목록을 한 배치에만 렌더 · 전환 전 포커스를 기억하고 새 배치가 연결된 뒤 좌표 복원
227
- */
228
- const handleLayoutChange = () => {
229
- const nextLayout = getNavigationLayout();
230
- if (nextLayout === layoutRef.current) return;
231
- if (document.activeElement instanceof HTMLElement) {
232
- focusRef.current = {
233
- navigation: document.activeElement.closest("[data-navigation]") !== null,
234
- drawer: document.activeElement === openButtonRef.current || document.activeElement.closest("[data-navigation-drawer]") !== null,
235
- href: document.activeElement.getAttribute("href"),
236
- };
237
- }
238
- if (nextLayout !== "drawer") closeNavigation();
239
- restorePendingRef.current = true;
240
- layoutRef.current = nextLayout;
241
- flushSync(() => setLayout(nextLayout));
242
- };
243
-
244
- /**
245
- * BFCache의 이전 메모리가 다음 문서의 좌표를 덮지 않도록 렌더까지 동기 갱신 후 복원
246
- */
247
- const handlePageShow = (event: PageTransitionEvent) => {
248
- if (!event.persisted || props.stores === undefined) return;
249
- restorePendingRef.current = true;
250
- flushSync(() => props.stores?.scroll.persist.rehydrate());
251
- handleLayoutChange();
252
- restoreScroll();
253
- };
254
-
255
- /**
256
- * Hono 탐색이 전역 구독의 설치·정리를 소유 · 서버 렌더에서는 실행하지 않음
257
- */
258
- useLayoutEffect(() => {
259
- if (props.stores === undefined) return;
260
- mobileRef.current = matchMedia(navigation_mobile_query);
261
- wideRef.current = matchMedia(navigation_wide_query);
262
- mobileRef.current.addEventListener("change", handleLayoutChange);
263
- wideRef.current.addEventListener("change", handleLayoutChange);
264
- addEventListener("pagehide", saveScroll);
265
- addEventListener("pageshow", handlePageShow);
266
- return () => {
267
- mobileRef.current?.removeEventListener("change", handleLayoutChange);
268
- wideRef.current?.removeEventListener("change", handleLayoutChange);
269
- mobileRef.current = null;
270
- wideRef.current = null;
271
- removeEventListener("pagehide", saveScroll);
272
- removeEventListener("pageshow", handlePageShow);
273
- document.body.classList.remove("wg_shell__root--drawerOpen");
274
- };
275
- }, [props.stores]);
276
-
277
- /**
278
- * 최초 mount와 반응형 재연결의 paint 전 좌표·포커스 복원 · Hono의 fragment 연결 시점 보정
279
- */
280
- useLayoutEffect(() => {
281
- if (props.stores === undefined) return;
282
- let active = true;
283
- const restoreNavigation = () => {
284
- if (!active) return;
285
- restoreScroll();
286
- if (focusRef.current === null) return;
287
- const navigation = getNavigationPort();
288
- if (layout === "drawer") {
289
- if (focusRef.current.navigation) openButtonRef.current?.focus({preventScroll: true});
290
- } else if (focusRef.current.navigation && navigation !== null) {
291
- const href = focusRef.current.href;
292
- const target = [...navigation.querySelectorAll<HTMLElement>("a[href]")].find((element) => element.getAttribute("href") === href);
293
- target?.focus({preventScroll: true});
294
- } else if (focusRef.current.drawer) {
295
- brandRef.current?.focus({preventScroll: true});
296
- }
297
- focusRef.current = null;
298
- };
299
- if (getDocumentsPort()?.isConnected) {
300
- restoreNavigation();
301
- } else {
302
- // Hono는 최초 layout effect에서 아직 fragment를 사용하므로 연결 후 microtask도 같은 첫 paint 앞에 둔다
303
- queueMicrotask(restoreNavigation);
304
- }
305
- return () => {
306
- active = false;
307
- };
308
- }, [layout, props.stores]);
309
-
310
- return (
311
- <Fragment>
312
- {/**
313
- * 데스크톱 문서 사이드바 · 모바일에서는 고정 브랜드와 연결된 메뉴 버튼
314
- */}
315
- <div className={clsx("wg_navigation__sidebar", props.stores !== undefined && "wg_navigation__sidebar--navigationReady")}>
316
- {/**
317
- * 어느 문서에서도 홈으로 이동 · 모달 진입은 브라우저 동작이 연결된 뒤 표시
318
- */}
319
- <div className={clsx("wg_navigation__brandRow")}>
320
- <a
321
- ref={props.stores === undefined ? undefined : brandRef}
322
- className={clsx("wg_navigation__brand")}
323
- href="/"
324
- aria-current={props.data.home ? "page" : undefined}
325
- >
326
- {props.data.title}
327
- </a>
328
- {/**
329
- * 네이티브 모달의 열림 상태와 접근 가능한 진입 이름
330
- */}
331
- <button
332
- ref={props.stores === undefined ? undefined : openButtonRef}
333
- className={clsx("wg_navigation__menuToggle", props.stores !== undefined && layout === "drawer" && "wg_navigation__menuToggle--visible")}
334
- type="button"
335
- aria-label={copy_nav_open}
336
- aria-controls="fh-navigation-drawer"
337
- aria-expanded={String(drawerOpen)}
338
- data-navigation-open=""
339
- onClick={handleOpenButtonClick}
340
- >
341
- <svg className={clsx("wg_navigation__menuIcon")} viewBox="0 0 24 24" aria-hidden="true" focusable="false">
342
- <path d="M4 6h16M4 12h16M4 18h16" />
343
- </svg>
344
- </button>
345
- </div>
346
- {/**
347
- * JavaScript 없이도 전체 문서 링크를 표시 · 브라우저 모바일에서는 드로어가 같은 목록을 소유
348
- */}
349
- {layout !== "drawer" && (
350
- <div ref={props.stores === undefined ? undefined : navigationRef} className={clsx("wg_navigation__navigation")} data-navigation="">
351
- <WgNavigationScroll portRef={documentsRef} kind="documents" ready={props.stores !== undefined} onScroll={saveScroll} onClick={handleNavigationClick}>
352
- <WgNavigationNav groups={props.data.groups} />
353
- </WgNavigationScroll>
354
- </div>
355
- )}
356
- </div>
357
- {/**
358
- * 와이드 화면 전용 목차 · 드로어에는 문서 목록만 표시
359
- */}
360
- <aside className={clsx("wg_navigation__tocRail")} data-navigation-rail="">
361
- <div className={clsx("wg_navigation__outline")} data-navigation-outline="">
362
- <WgNavigationScroll portRef={outlineRef} kind="outline" ready={props.stores !== undefined} onScroll={saveScroll} onClick={handleNavigationClick}>
363
- {props.data.outline.length > 0 && <WgNavigationToc groups={props.data.outline} />}
364
- </WgNavigationScroll>
365
- </div>
366
- </aside>
367
- {/**
368
- * 브라우저 top layer의 포커스·Escape·배경 클릭 동작과 모바일 문서 목록
369
- */}
370
- {/**
371
- * biome-ignore lint/a11y/useKeyWithClickEvents: 배경 클릭의 키보드 대응은 네이티브 dialog의 cancel과 별도 닫기 버튼이 제공한다
372
- */}
373
- <dialog
374
- ref={props.stores === undefined ? undefined : dialogRef}
375
- className={clsx("wg_navigation__drawer")}
376
- id="fh-navigation-drawer"
377
- aria-labelledby="fh-navigation-title"
378
- data-navigation-drawer=""
379
- onClose={handleDrawerClose}
380
- onCancel={handleDrawerCancel}
381
- onClick={handleDrawerClick}
382
- >
383
- {/**
384
- * 목록 스크롤과 독립된 대화상자 이름·닫기 동작
385
- */}
386
- <div className={clsx("wg_navigation__drawerHeader")}>
387
- <h2 className={clsx("wg_navigation__drawerTitle")} id="fh-navigation-title">
388
- {copy_nav_title}
389
- </h2>
390
- {/**
391
- * 네이티브 close로 열기 버튼에 포커스 복원
392
- */}
393
- <button className={clsx("wg_navigation__drawerClose")} type="button" aria-label={copy_nav_close} data-navigation-close="" onClick={handleCloseButtonClick}>
394
- <svg className={clsx("wg_navigation__menuIcon")} viewBox="0 0 24 24" aria-hidden="true" focusable="false">
395
- <path d="m6 6 12 12M18 6 6 18" />
396
- </svg>
397
- </button>
398
- </div>
399
- {/**
400
- * 모바일에만 한 번 렌더하는 문서 탐색 · 다른 배치와 저장 좌표 분리
401
- */}
402
- <div className={clsx("wg_navigation__drawerContent")} data-navigation-content="">
403
- {layout === "drawer" && (
404
- <div ref={props.stores === undefined ? undefined : drawerNavigationRef} className={clsx("wg_navigation__navigation")} data-navigation="">
405
- <WgNavigationScroll
406
- portRef={drawerDocumentsRef}
407
- kind="documents"
408
- ready={props.stores !== undefined}
409
- onScroll={saveScroll}
410
- onClick={handleNavigationClick}
411
- >
412
- <WgNavigationNav groups={props.data.groups} />
413
- </WgNavigationScroll>
414
- </div>
415
- )}
416
- </div>
417
- </dialog>
418
- </Fragment>
419
- );
420
- };