@aiquants/resize-panels 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/GlobalDebugOverlay.d.ts +4 -0
  2. package/dist/Panel.d.ts +13 -0
  3. package/dist/{src/PanelDebugInfo.d.ts → PanelDebugInfo.d.ts} +2 -14
  4. package/dist/PanelGroup.d.ts +2 -0
  5. package/dist/PanelResizeHandle.d.ts +2 -0
  6. package/dist/allocateLayout.d.ts +12 -0
  7. package/dist/context.d.ts +3 -0
  8. package/dist/debugOverlayStore.d.ts +32 -0
  9. package/dist/{src/hooks.d.ts → hooks.d.ts} +8 -23
  10. package/dist/index.d.ts +13 -2
  11. package/dist/reducer.d.ts +8 -0
  12. package/dist/roundHalfToEven.d.ts +1 -0
  13. package/dist/types.d.ts +183 -0
  14. package/dist/utils/simple-logger.d.ts +37 -0
  15. package/dist/utils.d.ts +16 -0
  16. package/package.json +2 -3
  17. package/dist/src/GlobalDebugOverlay.d.ts +0 -21
  18. package/dist/src/GlobalDebugOverlay.d.ts.map +0 -1
  19. package/dist/src/Panel.d.ts +0 -46
  20. package/dist/src/Panel.d.ts.map +0 -1
  21. package/dist/src/PanelDebugInfo.d.ts.map +0 -1
  22. package/dist/src/PanelGroup.d.ts +0 -7
  23. package/dist/src/PanelGroup.d.ts.map +0 -1
  24. package/dist/src/PanelResizeHandle.d.ts +0 -7
  25. package/dist/src/PanelResizeHandle.d.ts.map +0 -1
  26. package/dist/src/allocateLayout.d.ts +0 -60
  27. package/dist/src/allocateLayout.d.ts.map +0 -1
  28. package/dist/src/context.d.ts +0 -12
  29. package/dist/src/context.d.ts.map +0 -1
  30. package/dist/src/debugOverlayStore.d.ts +0 -69
  31. package/dist/src/debugOverlayStore.d.ts.map +0 -1
  32. package/dist/src/hooks.d.ts.map +0 -1
  33. package/dist/src/index.d.ts +0 -32
  34. package/dist/src/index.d.ts.map +0 -1
  35. package/dist/src/reducer.d.ts +0 -49
  36. package/dist/src/reducer.d.ts.map +0 -1
  37. package/dist/src/roundHalfToEven.d.ts +0 -10
  38. package/dist/src/roundHalfToEven.d.ts.map +0 -1
  39. package/dist/src/types.d.ts +0 -481
  40. package/dist/src/types.d.ts.map +0 -1
  41. package/dist/src/utils/simple-logger.d.ts +0 -111
  42. package/dist/src/utils/simple-logger.d.ts.map +0 -1
  43. package/dist/src/utils.d.ts +0 -164
  44. package/dist/src/utils.d.ts.map +0 -1
  45. package/dist/tests/support/dom-harness.d.ts +0 -89
  46. package/dist/tests/support/dom-harness.d.ts.map +0 -1
  47. package/src/GlobalDebugOverlay.tsx +0 -284
  48. package/src/Panel.tsx +0 -297
  49. package/src/PanelDebugInfo.tsx +0 -94
  50. package/src/PanelGroup.tsx +0 -245
  51. package/src/PanelResizeHandle.tsx +0 -758
  52. package/src/allocateLayout.ts +0 -412
  53. package/src/context.ts +0 -25
  54. package/src/debugOverlayStore.ts +0 -355
  55. package/src/hooks.ts +0 -376
  56. package/src/index.ts +0 -93
  57. package/src/reducer.ts +0 -472
  58. package/src/roundHalfToEven.ts +0 -40
  59. package/src/styles/components.entry.css +0 -10
  60. package/src/styles/resize-panels.css +0 -1
  61. package/src/styles/standalone.entry.css +0 -12
  62. package/src/types.ts +0 -505
  63. package/src/utils/simple-logger.ts +0 -186
  64. package/src/utils.ts +0 -320
@@ -1,284 +0,0 @@
1
- /**
2
- * @file Global debug overlay for resizable panel groups.
3
- * リサイズパネルグループ用のグローバルデバッグオーバーレイ。
4
- *
5
- * PanelGroup 本体から dynamic import で遅延読み込みされる。
6
- * showDebugInfo を使わないアプリのバンドルにオーバーレイ UI とストア購読を含めないための分離。
7
- */
8
-
9
- import { useState } from "react"
10
- import { createPortal } from "react-dom"
11
- import { useDebugOverlayOwnerId, useDebugOverlaySnapshot } from "./debugOverlayStore"
12
- import { PANEL_SNAP_THRESHOLD } from "./types"
13
-
14
- const formatNumericValue = (value: number | null | undefined, suffix: string) => {
15
- if (typeof value !== "number" || !Number.isFinite(value)) {
16
- return "—"
17
- }
18
- const clamped = value < 0 ? 0 : value
19
- return `${clamped.toFixed(8)}${suffix}`
20
- }
21
-
22
- const OVERLAY_CONTAINER_BASE_STYLE = {
23
- position: "fixed",
24
- left: "50%",
25
- bottom: "20px",
26
- transform: "translate(-50%, 0)",
27
- zIndex: 100,
28
- maxWidth: "min(90vw, 1120px)",
29
- width: "100%",
30
- borderRadius: "14px",
31
- backgroundColor: "rgba(15, 23, 42, 0.72)",
32
- color: "#e2e8f0",
33
- fontFamily: "monospace",
34
- fontSize: "11px",
35
- lineHeight: 1.6,
36
- boxShadow: "0 22px 45px rgba(15,23,42,0.45)",
37
- border: "1px solid rgba(148, 163, 184, 0.22)",
38
- backdropFilter: "blur(10px)",
39
- WebkitBackdropFilter: "blur(10px)",
40
- pointerEvents: "auto",
41
- overflow: "auto",
42
- } as const
43
-
44
- const OVERLAY_HEADER_STYLE = {
45
- display: "flex",
46
- justifyContent: "space-between",
47
- alignItems: "center",
48
- flexWrap: "wrap",
49
- gap: "12px",
50
- } as const
51
-
52
- const OVERLAY_SUMMARY_STYLE = {
53
- display: "flex",
54
- flexDirection: "column",
55
- gap: "4px",
56
- } as const
57
-
58
- const OVERLAY_BUTTON_BASE_STYLE = {
59
- padding: "6px 10px",
60
- borderRadius: "9999px",
61
- border: "1px solid rgba(148, 163, 184, 0.35)",
62
- color: "#e2e8f0",
63
- fontWeight: 600,
64
- fontSize: "10px",
65
- letterSpacing: "0.03em",
66
- textTransform: "uppercase" as const,
67
- cursor: "pointer",
68
- transition: "background-color 0.18s ease, border-color 0.18s ease",
69
- } as const
70
-
71
- const OVERLAY_GROUP_CARD_STYLE = {
72
- padding: "10px 12px",
73
- borderRadius: "10px",
74
- backgroundColor: "rgba(30, 41, 59, 0.55)",
75
- border: "1px solid rgba(148, 163, 184, 0.18)",
76
- display: "flex",
77
- flexDirection: "column",
78
- gap: "10px",
79
- } as const
80
-
81
- const OVERLAY_PANEL_CARD_STYLE = {
82
- padding: "8px 10px",
83
- borderRadius: "8px",
84
- backgroundColor: "rgba(15, 23, 42, 0.55)",
85
- border: "1px solid rgba(148, 163, 184, 0.16)",
86
- display: "flex",
87
- flexDirection: "column",
88
- gap: "4px",
89
- } as const
90
-
91
- const OVERLAY_PANEL_HEADER_STYLE = {
92
- display: "flex",
93
- justifyContent: "space-between",
94
- alignItems: "center",
95
- gap: "6px",
96
- } as const
97
-
98
- const OVERLAY_PANEL_GRID_STYLE = {
99
- display: "grid",
100
- gridTemplateColumns: "auto 1fr",
101
- gap: "2px 10px",
102
- color: "#e2e8f0",
103
- } as const
104
-
105
- const OVERLAY_STATUS_BADGE_STYLE = {
106
- padding: "2px 6px",
107
- borderRadius: "9999px",
108
- fontSize: "10px",
109
- textTransform: "uppercase" as const,
110
- } as const
111
-
112
- /**
113
- * Render the overlay only for the group that currently owns it.
114
- * オーバーレイの所有権を持つグループの場合のみ描画するゲート。
115
- * 所有権ストアの購読をこの遅延モジュール内へ閉じ込め、非デバッグ利用時の購読を発生させない。
116
- */
117
- export const GlobalDebugOverlayGate = ({ groupId }: { groupId: string }) => {
118
- const ownerId = useDebugOverlayOwnerId()
119
- if (ownerId !== groupId) {
120
- return null
121
- }
122
- return <GlobalDebugOverlay />
123
- }
124
-
125
- /**
126
- * Render global debug overlay displaying state for all active panel groups.
127
- * すべてのアクティブなパネルグループの状態を表示するグローバルデバッグオーバーレイを描画。
128
- */
129
- export const GlobalDebugOverlay = () => {
130
- const debugOverlaySnapshot = useDebugOverlaySnapshot()
131
- const debugOverlayGroups = debugOverlaySnapshot.groups
132
- const [isCollapsed, setIsCollapsed] = useState(true)
133
-
134
- if (debugOverlayGroups.length === 0) {
135
- return null
136
- }
137
-
138
- if (typeof document === "undefined") {
139
- return null
140
- }
141
-
142
- const totalPanels = debugOverlayGroups.reduce((sum, group) => sum + group.panels.length, 0)
143
- const totalHandles = debugOverlayGroups.reduce((sum, group) => sum + (group.handles?.length ?? 0), 0)
144
-
145
- const toggleCollapsed = () => setIsCollapsed((previous) => !previous)
146
-
147
- const debugOverlayContent = (
148
- <div
149
- style={{
150
- ...OVERLAY_CONTAINER_BASE_STYLE,
151
- padding: isCollapsed ? "10px 18px" : "14px 18px",
152
- maxHeight: isCollapsed ? "52px" : "min(65vh, 520px)",
153
- transition: "max-height 0.24s ease, padding 0.24s ease",
154
- }}>
155
- <div
156
- style={{
157
- ...OVERLAY_HEADER_STYLE,
158
- marginBottom: isCollapsed ? 0 : "12px",
159
- }}>
160
- <div style={OVERLAY_SUMMARY_STYLE}>
161
- <span
162
- style={{
163
- fontWeight: 700,
164
- fontSize: "12px",
165
- letterSpacing: "0.03em",
166
- }}>
167
- Panel Overview
168
- </span>
169
- <span style={{ color: "#cbd5f5" }}>
170
- groups: {debugOverlayGroups.length} / panels: {totalPanels} / resizers: {totalHandles}
171
- </span>
172
- </div>
173
- <button
174
- type="button"
175
- onClick={toggleCollapsed}
176
- style={{
177
- ...OVERLAY_BUTTON_BASE_STYLE,
178
- backgroundColor: isCollapsed ? "rgba(59, 130, 246, 0.2)" : "rgba(71, 85, 105, 0.45)",
179
- }}
180
- title={isCollapsed ? "Panel Overview を開く" : "Panel Overview を閉じる"}>
181
- {isCollapsed ? "開く" : "閉じる"}
182
- </button>
183
- </div>
184
- {!isCollapsed && (
185
- <div
186
- style={{
187
- display: "flex",
188
- flexDirection: "column",
189
- gap: "12px",
190
- maxHeight: "45vh",
191
- overflowY: "auto",
192
- paddingRight: "6px",
193
- }}>
194
- {debugOverlayGroups.map((group, groupIndex) => {
195
- const axisSize = group.direction === "horizontal" ? group.containerSize.width : group.containerSize.height
196
- return (
197
- <div key={group.groupId} style={OVERLAY_GROUP_CARD_STYLE}>
198
- <div
199
- style={{
200
- ...OVERLAY_PANEL_HEADER_STYLE,
201
- flexWrap: "wrap",
202
- gap: "10px",
203
- color: "#dbeafe",
204
- }}>
205
- <span
206
- style={{
207
- fontWeight: 600,
208
- fontSize: "11px",
209
- letterSpacing: "0.02em",
210
- textTransform: "uppercase",
211
- }}>
212
- group {groupIndex + 1}: {group.displayName}
213
- </span>
214
- <span>
215
- direction: {group.direction} / container: {formatNumericValue(group.containerSize.width, "px")} × {formatNumericValue(group.containerSize.height, "px")}
216
- </span>
217
- </div>
218
- <div
219
- style={{
220
- display: "grid",
221
- gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
222
- gap: "10px",
223
- }}>
224
- {group.panels.map((panel, panelIndex) => {
225
- const measuredPixel = panel.measuredPixelSize ?? panel.size
226
- const statePixel = panel.size
227
- const statePixelLabel = formatNumericValue(statePixel, "px")
228
- const statePercentage = axisSize > 0 ? (panel.size / axisSize) * 100 : undefined
229
- const measuredPercentage = panel.measuredPercentageSize !== undefined ? panel.measuredPercentageSize : axisSize > 0 ? (measuredPixel / axisSize) * 100 : undefined
230
- const isHidden = panel.collapsed || measuredPixel <= PANEL_SNAP_THRESHOLD
231
- const statusLabel = panel.collapsed ? "collapsed" : isHidden ? "hidden" : "visible"
232
- const percentageLabel = formatNumericValue(statePercentage, "%")
233
- const measuredPercentageLabel = formatNumericValue(measuredPercentage, "%")
234
- const measuredPixelLabel = formatNumericValue(measuredPixel, "px")
235
-
236
- return (
237
- <div key={panel.id || panelIndex} style={OVERLAY_PANEL_CARD_STYLE}>
238
- <div style={OVERLAY_PANEL_HEADER_STYLE}>
239
- <span style={{ flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis" }}>{panel.id}</span>
240
- <span
241
- style={{
242
- ...OVERLAY_STATUS_BADGE_STYLE,
243
- backgroundColor: panel.collapsed ? "rgba(251, 113, 133, 0.25)" : isHidden ? "rgba(250, 204, 21, 0.25)" : "rgba(74, 222, 128, 0.25)",
244
- color: panel.collapsed ? "#fecdd3" : isHidden ? "#fef08a" : "#bbf7d0",
245
- }}>
246
- {statusLabel}
247
- </span>
248
- </div>
249
- <div style={OVERLAY_PANEL_GRID_STYLE}>
250
- <span>size (state):</span>
251
- <span>{statePixelLabel}</span>
252
- <span>size (measured):</span>
253
- <span>{measuredPixelLabel}</span>
254
- <span>percentage (state):</span>
255
- <span>{percentageLabel}</span>
256
- <span>preference:</span>
257
- <span>{panel.sizeUnit === "pixels" ? formatNumericValue(panel.preferredPixelSize, "px") : formatNumericValue(panel.preferredPercentageSize, "%")}</span>
258
- <span>percentage (measured):</span>
259
- <span>{measuredPercentageLabel}</span>
260
- <span>unit:</span>
261
- <span>{panel.sizeUnit}</span>
262
- <span>min:</span>
263
- <span>{panel.minSize !== undefined ? JSON.stringify(panel.minSize) : "—"}</span>
264
- <span>max:</span>
265
- <span>{panel.maxSize !== undefined ? JSON.stringify(panel.maxSize) : "—"}</span>
266
- <span>priority:</span>
267
- <span>{panel.pixelAdjustPriority ?? "—"}</span>
268
- <span>flex priority:</span>
269
- <span>{panel.flexAdjustPriority ?? "—"}</span>
270
- </div>
271
- </div>
272
- )
273
- })}
274
- </div>
275
- </div>
276
- )
277
- })}
278
- </div>
279
- )}
280
- </div>
281
- )
282
-
283
- return createPortal(debugOverlayContent, document.body)
284
- }
package/src/Panel.tsx DELETED
@@ -1,297 +0,0 @@
1
- /**
2
- * @file Panel component for custom resizable panels
3
- * カスタムリサイズ可能パネルのための Panel コンポーネント
4
- */
5
-
6
- import { type CSSProperties, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef } from "react"
7
- import { twMerge } from "tailwind-merge"
8
- import { resolvePanelBounds } from "./allocateLayout"
9
- import { usePanelGroup } from "./context"
10
- import { PanelDebugInfo } from "./PanelDebugInfo"
11
- import { type CollapsibleConfig, PANEL_ALLOCATION_EPSILON, type PanelLayoutData, type PanelProps, type PanelRegistration, type SizeConfig } from "./types"
12
- import { getContainerSize, normalizeSizeConfig, toRoundedPercentage } from "./utils"
13
- import { Logger, LogLevel } from "./utils/simple-logger"
14
-
15
- const logger = new Logger(LogLevel.INFO, "[resize-panels]")
16
-
17
- /**
18
- * Directional collapse flags derived from the collapsible configuration.
19
- * 折りたたみ設定から導出した方向フラグ。
20
- */
21
- type CollapseFlags = {
22
- collapseFromStart: boolean
23
- collapseFromEnd: boolean
24
- }
25
-
26
- const COLLAPSE_FLAGS: Record<CollapsibleConfig["from"], CollapseFlags> = {
27
- both: { collapseFromStart: true, collapseFromEnd: true },
28
- start: { collapseFromStart: true, collapseFromEnd: false },
29
- end: { collapseFromStart: false, collapseFromEnd: true },
30
- }
31
-
32
- /**
33
- * Normalize collapsible configuration into directional flags.
34
- * 折りたたみ設定を方向フラグへ正規化する処理。
35
- *
36
- * @param config - Collapsible configuration from props / props で指定された折りたたみ設定
37
- * @returns Directional collapse flags / 方向ごとの折りたたみ可否
38
- * @throws When the configuration names an unsupported direction / 未対応の方向が指定された場合
39
- */
40
- const normalizeCollapsibleConfig = (config: CollapsibleConfig | undefined): CollapseFlags => {
41
- if (!config) {
42
- return { collapseFromStart: false, collapseFromEnd: false }
43
- }
44
- const flags = COLLAPSE_FLAGS[config.from]
45
- if (!flags) {
46
- throw new Error("Invalid collapsible configuration: unsupported 'from' value")
47
- }
48
- return flags
49
- }
50
-
51
- /**
52
- * Split a normalized default size into the preference fields of the layout state.
53
- * 正規化済みの既定サイズを、レイアウト状態の希望値フィールドへ分解する処理。
54
- *
55
- * Exactly one of the two preferences carries meaning, selected by the unit, so the state never holds the
56
- * same intent twice.
57
- * 意味を持つ希望値は単位によって選ばれる 1 つだけであり、状態が同じ意図を二重に持つことはない。
58
- *
59
- * @param defaultSize - Normalized default size / 正規化済みの既定サイズ
60
- * @returns Size unit and both preference fields / サイズ単位と 2 つの希望値フィールド
61
- */
62
- const toPreference = (defaultSize: SizeConfig): Pick<PanelRegistration, "sizeUnit" | "preferredPercentageSize" | "preferredPixelSize"> => {
63
- const value = Math.max(0, defaultSize.value)
64
- return defaultSize.unit === "pixels" ? { sizeUnit: "pixels", preferredPixelSize: value, preferredPercentageSize: 0 } : { sizeUnit: "percentage", preferredPercentageSize: value, preferredPixelSize: 0 }
65
- }
66
-
67
- /**
68
- * Flex triple a panel renders: the allocated length plus the weights that absorb a container change.
69
- * パネルが描画する flex の三つ組: 配分された長さと、コンテナ変化を吸収する重み。
70
- */
71
- type PanelFlexStyle = Required<Pick<CSSProperties, "flexBasis" | "flexGrow" | "flexShrink">>
72
-
73
- type ResolvePanelFlexArgs = {
74
- size: number
75
- panel: PanelLayoutData | undefined
76
- registration: PanelRegistration
77
- containerSize: number
78
- isCollapsed: boolean
79
- }
80
-
81
- /**
82
- * Resolve the flex triple that renders a panel's current state.
83
- * パネルの現在の状態を描画する flex の三つ組を解決する処理。
84
- *
85
- * The basis is always the allocated length, so the rendered geometry is the allocator's answer and nothing
86
- * else. The weights are the first-order continuation of that answer: a panel grows with its ratio weight
87
- * until it reaches its maximum and shrinks only while it is strictly between its bounds, which is exactly
88
- * how the allocator would move it for a small container change.
89
- * basis は常に配分された長さであり、描画される幾何は配分器の答えそのものとなる。重みはその答えの
90
- * 一次近似であり、上限に達するまでは比率の重みで伸び、上下限の内側にいる間だけ縮む。これは小さな
91
- * コンテナ変化に対して配分器が動かす量と一致する。
92
- *
93
- * @param args - Allocated size, panel state, registration and container length / 配分サイズ・パネル状態・登録情報・コンテナ長
94
- * @returns Flex basis, grow and shrink to render / 描画する flex basis・grow・shrink
95
- */
96
- export const resolvePanelFlex = ({ size, panel, registration, containerSize, isCollapsed }: ResolvePanelFlexArgs): PanelFlexStyle => {
97
- if (isCollapsed) {
98
- return { flexBasis: "0px", flexGrow: 0, flexShrink: 0 }
99
- }
100
-
101
- if (!panel || containerSize <= 0) {
102
- // まだ計測できていない間は宣言された既定サイズで描く。サーバー描画や初回フレームで
103
- // 幅ゼロのパネルが一瞬見えるのを防ぎ、計測後の配分結果と連続した見た目になる
104
- const isPixelRegistration = registration.sizeUnit === "pixels"
105
- return {
106
- flexBasis: isPixelRegistration ? `${registration.preferredPixelSize}px` : `${registration.preferredPercentageSize}%`,
107
- flexGrow: isPixelRegistration ? 0 : registration.preferredPercentageSize,
108
- flexShrink: isPixelRegistration ? 0 : 1,
109
- }
110
- }
111
-
112
- if (panel.sizeUnit === "pixels") {
113
- // 固定長パネルはコンテナ変化を引き受けない。引き受けると配分器の答えと描画がずれる
114
- return { flexBasis: `${size}px`, flexGrow: 0, flexShrink: 0 }
115
- }
116
-
117
- // 内側にいるパネルの縮小重みが一律 1 で正しいのは、CSS の縮小配分が「重み × basis」であり
118
- // basis が配分結果 (= λ × 重み) だからで、結果として重みの比で縮むため
119
- const bounds = resolvePanelBounds(panel, containerSize)
120
- const canGrow = size < bounds.max - PANEL_ALLOCATION_EPSILON
121
- const isInterior = canGrow && size > bounds.min + PANEL_ALLOCATION_EPSILON
122
- return { flexBasis: `${size}px`, flexGrow: canGrow ? panel.preferredPercentageSize : 0, flexShrink: isInterior ? 1 : 0 }
123
- }
124
-
125
- /**
126
- * Render a resizable panel whose size is decided by the group's allocator.
127
- * グループの配分器が決めたサイズで描画されるリサイズ可能パネル。
128
- *
129
- * The panel publishes what it wants (its preference and constraints) and renders what it was given. Its
130
- * flex basis is the allocated length itself, while `flex-grow` / `flex-shrink` describe how it should
131
- * absorb a container change during the frames before the allocator runs again — a growth weight that
132
- * vanishes at the panel's maximum and a shrink weight that vanishes at either bound, which is exactly how
133
- * the allocator would move this panel for a small change.
134
- * パネルは希望と制約を公開し、与えられたサイズで描画する。flex basis は配分結果そのもので、
135
- * `flex-grow` / `flex-shrink` は配分器が再実行されるまでの数フレームでコンテナ変化をどう吸収するかを表す
136
- * (上限で消える成長重みと、上下限のいずれかで消える縮小重み)。これは小さな変化に対して配分器が動かす量と一致する。
137
- */
138
- export const Panel = memo(
139
- ({ id: providedId, defaultSize = { value: 50, unit: "percentage" }, minSize, maxSize, className, style, children, collapsible, defaultCollapsed = false, pixelAdjustPriority, flexAdjustPriority, contentOverflow = "auto", ...elementProps }: PanelProps) => {
140
- const { direction, registerPanel, unregisterPanel, getPanel, containerSize, showDebugInfo, reportPanelMeasurement } = usePanelGroup()
141
- const panelRef = useRef<HTMLDivElement>(null)
142
- const generatedId = useId()
143
- const panelIdRef = useRef(providedId || `panel-${generatedId}`)
144
- const panelId = panelIdRef.current
145
-
146
- useEffect(() => {
147
- // id はマウント後に変更できない (登録・DOM 属性・計測が初期 id に固定されるため)
148
- if (providedId && providedId !== panelId) {
149
- logger.warn("Panel id prop change after mount is ignored.", { currentId: panelId, attemptedId: providedId })
150
- }
151
- }, [providedId, panelId])
152
-
153
- const collapseFlags = useMemo(() => normalizeCollapsibleConfig(collapsible), [collapsible])
154
- const isCollapsible = collapseFlags.collapseFromStart || collapseFlags.collapseFromEnd
155
-
156
- useEffect(() => {
157
- if (defaultCollapsed && !isCollapsible) {
158
- logger.warn("Panel defaultCollapsed is ignored because collapsible configuration is not provided.", { panelId })
159
- }
160
- }, [defaultCollapsed, isCollapsible, panelId])
161
-
162
- const registration = useMemo<PanelRegistration>(() => {
163
- const normalized = normalizeSizeConfig(defaultSize) ?? { value: 50, unit: "percentage" as const }
164
- return {
165
- id: panelId,
166
- ...toPreference(normalized),
167
- minSize,
168
- maxSize,
169
- ...collapseFlags,
170
- collapsed: isCollapsible && defaultCollapsed,
171
- pixelAdjustPriority,
172
- flexAdjustPriority,
173
- }
174
- }, [panelId, defaultSize, minSize, maxSize, collapseFlags, isCollapsible, defaultCollapsed, pixelAdjustPriority, flexAdjustPriority])
175
-
176
- useLayoutEffect(() => {
177
- registerPanel(registration)
178
- }, [registerPanel, registration])
179
-
180
- useLayoutEffect(() => {
181
- // 登録と同じフェーズで解除する。パッシブ効果だと、同一コミット内で同じ id が
182
- // 貼り替わったとき「新しい登録 → 古い解除」の順になり、パネルが状態から消えたまま戻らない
183
- return () => {
184
- unregisterPanel(panelId)
185
- }
186
- }, [unregisterPanel, panelId])
187
-
188
- const panel = getPanel(panelId)
189
- const size = panel?.size ?? 0
190
- const isCollapsed = panel?.collapsed ?? false
191
-
192
- /**
193
- * Measure the rendered length and report it for the debug overlay.
194
- * 描画後の実寸を計測し、デバッグオーバーレイへ報告する処理。
195
- */
196
- const containerSizeRef = useRef(containerSize)
197
- containerSizeRef.current = containerSize
198
- const measure = useCallback(() => {
199
- const element = panelRef.current
200
- if (!element) {
201
- return
202
- }
203
- const pixelSize = getContainerSize(element, direction).border
204
- reportPanelMeasurement(panelId, { pixelSize, percentageSize: toRoundedPercentage(pixelSize, containerSizeRef.current) })
205
- }, [direction, panelId, reportPanelMeasurement])
206
-
207
- useEffect(() => {
208
- // 実測値はデバッグ表示専用であり、レイアウトへは戻さない。
209
- // 表示していないときに監視すると、全パネルで無用な強制レイアウトを起こす
210
- if (!showDebugInfo) {
211
- reportPanelMeasurement(panelId, null)
212
- return
213
- }
214
- const element = panelRef.current
215
- if (!element || typeof ResizeObserver === "undefined") {
216
- measure()
217
- return
218
- }
219
- const observer = new ResizeObserver(measure)
220
- observer.observe(element)
221
- measure()
222
- return () => {
223
- observer.disconnect()
224
- reportPanelMeasurement(panelId, null)
225
- }
226
- }, [showDebugInfo, measure, panelId, reportPanelMeasurement])
227
-
228
- const panelStyle = useMemo<CSSProperties>(() => {
229
- // 見た目に関わる既定値は利用者が上書きできる。
230
- // `isolation` はパネル内部の z-index がハンドルより手前に出ないようにするための既定値だが、
231
- // パネルの外まで覆う overlay (position: fixed の最大化表示など) を置きたい利用者は
232
- // `isolation: "auto"` で降りられる (その場合、内容がハンドルを覆いうることを引き受ける)
233
- const presentation: CSSProperties = { overflow: "hidden", isolation: "isolate" }
234
- const collapsed: CSSProperties = isCollapsed ? { ...(direction === "horizontal" ? { width: "0px" } : { height: "0px" }), pointerEvents: "none", visibility: "hidden", display: "none" } : {}
235
-
236
- return {
237
- ...presentation,
238
- ...style,
239
- // ここから下はレイアウトの前提そのもの。利用者のスタイルやクラスで変わると配分結果と
240
- // 描画が一致しなくなるため、最後に適用して上書きを受け付けない:
241
- // flex の三つ組は配分器が決めた長さそのもの、margin はパネルの合計に含まれない隙間を作り、
242
- // order は DOM 順と視覚順をずらす
243
- ...resolvePanelFlex({ size, panel, registration, containerSize, isCollapsed }),
244
- order: 0,
245
- margin: 0,
246
- // 内容を通常フローで内容ボックスいっぱいに伸ばすための入れ物 (パディングを効かせる唯一の形)
247
- display: "flex",
248
- flexDirection: "column",
249
- position: "relative",
250
- boxSizing: "border-box",
251
- // 主軸の縮小をコンテンツの固有幅に妨げられないようにし、
252
- // 上限側のクランプ (max-w-* のようなユーティリティクラスを含む) も受け付けない。
253
- // 配分器が配った長さより短く描くと、状態と描画が食い違ったままハンドルが動かなくなる
254
- minWidth: 0,
255
- minHeight: 0,
256
- maxWidth: "none",
257
- maxHeight: "none",
258
- ...(direction === "horizontal" ? { height: "100%" } : { width: "100%" }),
259
- ...collapsed,
260
- }
261
- }, [size, direction, isCollapsed, panel, registration, containerSize, style])
262
-
263
- const contentWrapperStyle = useMemo<CSSProperties>(
264
- () => ({
265
- // 内容は通常フローの伸縮子として置く。絶対配置にするとパディングボックスを基準にしてしまい、
266
- // 利用者がパネルへ与えたパディングが何の効果も持たなくなる
267
- flex: "1 1 auto",
268
- position: "relative",
269
- overflow: contentOverflow,
270
- display: "flex",
271
- flexDirection: "column",
272
- minWidth: "0px",
273
- minHeight: "0px",
274
- }),
275
- [contentOverflow],
276
- )
277
-
278
- return (
279
- <div
280
- {...elementProps}
281
- ref={panelRef}
282
- className={twMerge("panel", className)}
283
- style={panelStyle}
284
- data-panel-id={panelId}
285
- data-panel-size={size}
286
- data-panel-size-unit={panel?.sizeUnit ?? "percentage"}
287
- data-panel-collapsible={isCollapsible}
288
- data-panel-collapse-start={collapseFlags.collapseFromStart}
289
- data-panel-collapse-end={collapseFlags.collapseFromEnd}
290
- data-panel-collapsed={isCollapsed}
291
- aria-hidden={isCollapsed}>
292
- <div style={contentWrapperStyle}>{children}</div>
293
- {showDebugInfo && <PanelDebugInfo panel={panel} containerAxisSize={containerSize} direction={direction} />}
294
- </div>
295
- )
296
- },
297
- )
@@ -1,94 +0,0 @@
1
- /**
2
- * @file Debug info overlay component for panels
3
- * パネル用デバッグ情報オーバーレイコンポーネント
4
- */
5
-
6
- import { type CSSProperties, memo, useMemo } from "react"
7
- import { resolvePanelBounds } from "./allocateLayout"
8
- import type { PanelDirection, PanelLayoutData } from "./types"
9
- import { toRoundedPercentage } from "./utils"
10
-
11
- type PanelDebugInfoProps = {
12
- panel: PanelLayoutData | undefined
13
- containerAxisSize: number
14
- direction: PanelDirection
15
- }
16
-
17
- type FormatPanelDebugInfoArgs = {
18
- panel: PanelLayoutData | undefined
19
- containerAxisSize: number
20
- containerLabel: string
21
- }
22
-
23
- type FormattedPanelDebugInfo = {
24
- sizeDisplay: string
25
- preferenceDisplay: string
26
- boundsDisplay: string
27
- containerDisplay: string
28
- headlineLabel: string
29
- titleText: string
30
- }
31
-
32
- /**
33
- * Format one panel's allocation for the debug overlay.
34
- * デバッグ表示用に 1 枚のパネルの配分結果を整形する処理。
35
- *
36
- * @param args - Panel, container length and the axis label / パネル・コンテナ長・軸ラベル
37
- * @returns Display strings for the overlay / オーバーレイ表示用の文字列群
38
- */
39
- export const formatPanelDebugInfo = ({ panel, containerAxisSize, containerLabel }: FormatPanelDebugInfoArgs): FormattedPanelDebugInfo => {
40
- if (!panel) {
41
- return { sizeDisplay: "-", preferenceDisplay: "-", boundsDisplay: "-", containerDisplay: "-", headlineLabel: "Unregistered", titleText: "Unregistered" }
42
- }
43
-
44
- const isPixel = panel.sizeUnit === "pixels"
45
- const sizeDisplay = `${panel.size.toFixed(3)}px / ${toRoundedPercentage(panel.size, containerAxisSize).toFixed(3)}%`
46
- const preferenceDisplay = isPixel ? `${panel.preferredPixelSize.toFixed(3)}px` : `${panel.preferredPercentageSize.toFixed(3)}%`
47
- const bounds = resolvePanelBounds(panel, containerAxisSize)
48
- const boundsDisplay = `${bounds.min.toFixed(1)} - ${bounds.max.toFixed(1)}px`
49
- const containerDisplay = Number.isFinite(containerAxisSize) ? `${containerAxisSize.toFixed(3)}px` : "-"
50
- const headlineLabel = isPixel ? "Fixed" : "Flexible"
51
- const titleText = [headlineLabel, `${containerLabel}: ${containerDisplay}`, `size: ${sizeDisplay}`, `preference: ${preferenceDisplay}`, `bounds: ${boundsDisplay}`].join("\n")
52
-
53
- return { sizeDisplay, preferenceDisplay, boundsDisplay, containerDisplay, headlineLabel, titleText }
54
- }
55
-
56
- /**
57
- * Render the per-panel debug badge inside a panel.
58
- * パネル内にパネル単位のデバッグバッジを描画するコンポーネント。
59
- */
60
- export const PanelDebugInfo = memo(({ panel, containerAxisSize, direction }: PanelDebugInfoProps) => {
61
- const containerLabel = direction === "horizontal" ? "container width" : "container height"
62
- const { sizeDisplay, preferenceDisplay, boundsDisplay, containerDisplay, headlineLabel, titleText } = useMemo(() => formatPanelDebugInfo({ panel, containerAxisSize, containerLabel }), [panel, containerAxisSize, containerLabel])
63
-
64
- const badgeStyle: CSSProperties = {
65
- position: "absolute",
66
- top: 2,
67
- left: 2,
68
- zIndex: 30,
69
- pointerEvents: "none",
70
- maxWidth: "calc(100% - 4px)",
71
- padding: "2px 4px",
72
- borderRadius: 4,
73
- border: "1px solid rgba(255,255,255,0.1)",
74
- backgroundColor: "rgba(15, 23, 42, 0.78)",
75
- color: "#e2e8f0",
76
- fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
77
- fontSize: 10,
78
- lineHeight: 1.35,
79
- whiteSpace: "pre",
80
- overflow: "hidden",
81
- textOverflow: "ellipsis",
82
- }
83
-
84
- return (
85
- <div style={badgeStyle} title={titleText} data-panel-debug-info="">
86
- <div>
87
- {headlineLabel} · {containerLabel}: {containerDisplay}
88
- </div>
89
- <div>size: {sizeDisplay}</div>
90
- <div>preference: {preferenceDisplay}</div>
91
- <div>bounds: {boundsDisplay}</div>
92
- </div>
93
- )
94
- })