@aiquants/resize-panels 1.8.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +213 -108
- package/dist/GlobalDebugOverlay-Cf3GOn2A.cjs +1 -0
- package/dist/{GlobalDebugOverlay-DYCR0FIu.js → GlobalDebugOverlay-DQtUdNeF.js} +28 -28
- package/dist/debugOverlayStore-D7FT6zPE.js +135 -0
- package/dist/debugOverlayStore-DY09saU8.cjs +1 -0
- package/dist/index-DZ0itiBD.js +1299 -0
- package/dist/index-DqATP_BA.cjs +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +2 -29
- package/dist/index.js +25 -13
- package/dist/src/GlobalDebugOverlay.d.ts.map +1 -0
- package/dist/src/Panel.d.ts +46 -0
- package/dist/src/Panel.d.ts.map +1 -0
- package/dist/src/PanelDebugInfo.d.ts +34 -0
- package/dist/src/PanelDebugInfo.d.ts.map +1 -0
- package/dist/src/PanelGroup.d.ts.map +1 -0
- package/dist/{PanelResizeHandle.d.ts → src/PanelResizeHandle.d.ts} +1 -1
- package/dist/src/PanelResizeHandle.d.ts.map +1 -0
- package/dist/src/allocateLayout.d.ts +60 -0
- package/dist/src/allocateLayout.d.ts.map +1 -0
- package/dist/src/context.d.ts.map +1 -0
- package/dist/{debugOverlayStore.d.ts → src/debugOverlayStore.d.ts} +10 -2
- package/dist/src/debugOverlayStore.d.ts.map +1 -0
- package/dist/src/hooks.d.ts +48 -0
- package/dist/src/hooks.d.ts.map +1 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/reducer.d.ts +49 -0
- package/dist/src/reducer.d.ts.map +1 -0
- package/dist/src/roundHalfToEven.d.ts.map +1 -0
- package/dist/src/types.d.ts +481 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/utils/simple-logger.d.ts.map +1 -0
- package/dist/src/utils.d.ts +164 -0
- package/dist/src/utils.d.ts.map +1 -0
- package/dist/styles/resize-panels.standalone.css +1 -1
- package/dist/tests/support/dom-harness.d.ts +89 -0
- package/dist/tests/support/dom-harness.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/GlobalDebugOverlay.tsx +3 -3
- package/src/Panel.tsx +203 -541
- package/src/PanelDebugInfo.tsx +52 -106
- package/src/PanelGroup.tsx +27 -42
- package/src/PanelResizeHandle.tsx +544 -504
- package/src/allocateLayout.ts +412 -0
- package/src/debugOverlayStore.ts +19 -15
- package/src/hooks.ts +187 -779
- package/src/index.ts +37 -6
- package/src/reducer.ts +364 -1126
- package/src/types.ts +282 -65
- package/src/utils.ts +205 -164
- package/dist/GlobalDebugOverlay-afs0vF-Q.cjs +0 -1
- package/dist/GlobalDebugOverlay.d.ts.map +0 -1
- package/dist/Panel.d.ts +0 -7
- package/dist/Panel.d.ts.map +0 -1
- package/dist/PanelDebugInfo.d.ts +0 -26
- package/dist/PanelDebugInfo.d.ts.map +0 -1
- package/dist/PanelGroup.d.ts.map +0 -1
- package/dist/PanelResizeHandle.d.ts.map +0 -1
- package/dist/context.d.ts.map +0 -1
- package/dist/debugOverlayStore-Cntyxboe.js +0 -141
- package/dist/debugOverlayStore-Dkl-fHoa.cjs +0 -1
- package/dist/debugOverlayStore.d.ts.map +0 -1
- package/dist/hooks.d.ts +0 -45
- package/dist/hooks.d.ts.map +0 -1
- package/dist/index-BzgId8aQ.js +0 -1972
- package/dist/index-xTV-c0PN.cjs +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/reducer.d.ts +0 -39
- package/dist/reducer.d.ts.map +0 -1
- package/dist/roundHalfToEven.d.ts.map +0 -1
- package/dist/types.d.ts +0 -253
- package/dist/types.d.ts.map +0 -1
- package/dist/utils/simple-logger.d.ts.map +0 -1
- package/dist/utils.d.ts +0 -61
- package/dist/utils.d.ts.map +0 -1
- /package/dist/{GlobalDebugOverlay.d.ts → src/GlobalDebugOverlay.d.ts} +0 -0
- /package/dist/{PanelGroup.d.ts → src/PanelGroup.d.ts} +0 -0
- /package/dist/{context.d.ts → src/context.d.ts} +0 -0
- /package/dist/{roundHalfToEven.d.ts → src/roundHalfToEven.d.ts} +0 -0
- /package/dist/{utils → src/utils}/simple-logger.d.ts +0 -0
package/src/Panel.tsx
CHANGED
|
@@ -3,609 +3,272 @@
|
|
|
3
3
|
* カスタムリサイズ可能パネルのための Panel コンポーネント
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { type CSSProperties,
|
|
6
|
+
import { type CSSProperties, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef } from "react"
|
|
7
7
|
import { twMerge } from "tailwind-merge"
|
|
8
|
+
import { resolvePanelBounds } from "./allocateLayout"
|
|
8
9
|
import { usePanelGroup } from "./context"
|
|
9
10
|
import { PanelDebugInfo } from "./PanelDebugInfo"
|
|
10
|
-
import type
|
|
11
|
-
import {
|
|
11
|
+
import { type CollapsibleConfig, PANEL_ALLOCATION_EPSILON, type PanelLayoutData, type PanelProps, type PanelRegistration, type SizeConfig } from "./types"
|
|
12
|
+
import { getContainerSize, normalizeSizeConfig, toRoundedPercentage } from "./utils"
|
|
12
13
|
import { Logger, LogLevel } from "./utils/simple-logger"
|
|
13
14
|
|
|
14
15
|
const logger = new Logger(LogLevel.INFO, "[resize-panels]")
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Directional collapse flags derived from the collapsible configuration.
|
|
19
|
+
* 折りたたみ設定から導出した方向フラグ。
|
|
20
|
+
*/
|
|
21
|
+
type CollapseFlags = {
|
|
17
22
|
collapseFromStart: boolean
|
|
18
23
|
collapseFromEnd: boolean
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
const
|
|
22
|
-
both: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
start: {
|
|
27
|
-
collapseFromStart: true,
|
|
28
|
-
collapseFromEnd: false,
|
|
29
|
-
},
|
|
30
|
-
end: {
|
|
31
|
-
collapseFromStart: false,
|
|
32
|
-
collapseFromEnd: true,
|
|
33
|
-
},
|
|
34
|
-
} as const
|
|
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
|
+
}
|
|
35
31
|
|
|
36
32
|
/**
|
|
37
33
|
* Normalize collapsible configuration into directional flags.
|
|
38
|
-
*
|
|
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
39
|
*/
|
|
40
|
-
const normalizeCollapsibleConfig = (config: CollapsibleConfig | undefined):
|
|
40
|
+
const normalizeCollapsibleConfig = (config: CollapsibleConfig | undefined): CollapseFlags => {
|
|
41
41
|
if (!config) {
|
|
42
|
-
return {
|
|
43
|
-
collapseFromStart: false,
|
|
44
|
-
collapseFromEnd: false,
|
|
45
|
-
}
|
|
42
|
+
return { collapseFromStart: false, collapseFromEnd: false }
|
|
46
43
|
}
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
44
|
+
const flags = COLLAPSE_FLAGS[config.from]
|
|
45
|
+
if (!flags) {
|
|
49
46
|
throw new Error("Invalid collapsible configuration: unsupported 'from' value")
|
|
50
47
|
}
|
|
51
|
-
return
|
|
48
|
+
return flags
|
|
52
49
|
}
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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 }
|
|
60
65
|
}
|
|
61
66
|
|
|
62
67
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
68
|
+
* Flex triple a panel renders: the allocated length plus the weights that absorb a container change.
|
|
69
|
+
* パネルが描画する flex の三つ組: 配分された長さと、コンテナ変化を吸収する重み。
|
|
65
70
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
}
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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,
|
|
92
109
|
}
|
|
110
|
+
}
|
|
93
111
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
[label]: sanitized,
|
|
98
|
-
})
|
|
99
|
-
setState(sanitized)
|
|
112
|
+
if (panel.sizeUnit === "pixels") {
|
|
113
|
+
// 固定長パネルはコンテナ変化を引き受けない。引き受けると配分器の答えと描画がずれる
|
|
114
|
+
return { flexBasis: `${size}px`, flexGrow: 0, flexShrink: 0 }
|
|
100
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 }
|
|
101
123
|
}
|
|
102
124
|
|
|
103
125
|
/**
|
|
104
|
-
* Render a resizable panel
|
|
105
|
-
*
|
|
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
|
+
* (上限で消える成長重みと、上下限のいずれかで消える縮小重み)。これは小さな変化に対して配分器が動かす量と一致する。
|
|
106
137
|
*/
|
|
107
138
|
export const Panel = memo(
|
|
108
|
-
({ id: providedId, defaultSize = { value: 50, unit: "percentage" }, minSize, maxSize,
|
|
109
|
-
const { direction, registerPanel, unregisterPanel, getPanel,
|
|
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()
|
|
110
141
|
const panelRef = useRef<HTMLDivElement>(null)
|
|
111
142
|
const generatedId = useId()
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
useEffect(() => {
|
|
115
|
-
// id prop はマウント後に変更できない (登録・DOM 属性・測定が初期 id に固定されるため)
|
|
116
|
-
// 黙殺すると気付けないため開発者向けに警告を出す
|
|
117
|
-
if (providedId && providedId !== panelId.current) {
|
|
118
|
-
logger.warn("Panel id prop change after mount is ignored.", {
|
|
119
|
-
currentId: panelId.current,
|
|
120
|
-
attemptedId: providedId,
|
|
121
|
-
})
|
|
122
|
-
}
|
|
123
|
-
}, [providedId])
|
|
124
|
-
|
|
125
|
-
const collapsibleConfig = useMemo(() => normalizeCollapsibleConfig(collapsible), [collapsible])
|
|
126
|
-
const isCollapsible = collapsibleConfig.collapseFromStart || collapsibleConfig.collapseFromEnd
|
|
127
|
-
const preferredCollapsedDirection: PanelCollapseDirection | null = useMemo(() => {
|
|
128
|
-
if (!isCollapsible) {
|
|
129
|
-
return null
|
|
130
|
-
}
|
|
131
|
-
if (collapsibleConfig.collapseFromStart) {
|
|
132
|
-
return "start"
|
|
133
|
-
}
|
|
134
|
-
if (collapsibleConfig.collapseFromEnd) {
|
|
135
|
-
return "end"
|
|
136
|
-
}
|
|
137
|
-
return null
|
|
138
|
-
}, [isCollapsible, collapsibleConfig.collapseFromStart, collapsibleConfig.collapseFromEnd])
|
|
139
|
-
const shouldStartCollapsed = isCollapsible && defaultCollapsed
|
|
140
|
-
const defaultSizeConfig = useMemo(() => normalizeSizeConfig(defaultSize), [defaultSize])
|
|
141
|
-
const isPixelUnit = defaultSizeConfig?.unit === "pixels"
|
|
142
|
-
const preferredPercentage = !isPixelUnit ? defaultSizeConfig?.value : undefined
|
|
143
|
-
|
|
144
|
-
// リデューサーが保持する最新パネル状態を取得
|
|
145
|
-
const currentPanel = getPanel(panelId.current)
|
|
146
|
-
// 折りたたみフラグが明示的に有効か判定
|
|
147
|
-
const isExplicitlyCollapsed = Boolean(currentPanel?.collapsed)
|
|
148
|
-
// サイズがゼロ以下で見かけ上折りたたまれているか判定
|
|
149
|
-
const hasZeroState = (currentPanel?.size ?? 0) <= 0
|
|
150
|
-
// 明示折りたたみかゼロ状態かを統合した折りたたみ判定
|
|
151
|
-
const isEffectivelyCollapsed = isExplicitlyCollapsed || hasZeroState
|
|
152
|
-
|
|
153
|
-
// observer コールバックから最新状態を参照するための ref。
|
|
154
|
-
// effect の依存に currentPanel (リデューサー更新のたびに identity が変わる) を入れると
|
|
155
|
-
// ドラッグ毎フレーム ResizeObserver / MutationObserver が再生成されるため、ref 経由で渡す。
|
|
156
|
-
const currentPanelRef = useRef(currentPanel)
|
|
157
|
-
currentPanelRef.current = currentPanel
|
|
158
|
-
const isEffectivelyCollapsedRef = useRef(isEffectivelyCollapsed)
|
|
159
|
-
isEffectivelyCollapsedRef.current = isEffectivelyCollapsed
|
|
160
|
-
|
|
161
|
-
const [pixelSize, setPixelSize] = useState(() => {
|
|
162
|
-
// 初期ピクセルサイズを defaultSize もしくは最新のリデューサー値から取得
|
|
163
|
-
if (isPixelUnit && defaultSizeConfig) {
|
|
164
|
-
return defaultSizeConfig.value
|
|
165
|
-
}
|
|
166
|
-
if (currentPanel?.size !== undefined) {
|
|
167
|
-
return currentPanel.size
|
|
168
|
-
}
|
|
169
|
-
return 0
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
const [percentageSize, setPercentageSize] = useState(() => {
|
|
173
|
-
// 初期パーセンテージサイズを defaultSize もしくは最新のリデューサー値から取得
|
|
174
|
-
if (!isPixelUnit && defaultSizeConfig) {
|
|
175
|
-
return defaultSizeConfig.value
|
|
176
|
-
}
|
|
177
|
-
if (currentPanel?.percentageSize !== undefined) {
|
|
178
|
-
return currentPanel.percentageSize
|
|
179
|
-
}
|
|
180
|
-
return 0
|
|
181
|
-
})
|
|
182
|
-
const pixelSizeRef = useRef(pixelSize)
|
|
183
|
-
const percentageSizeRef = useRef(percentageSize)
|
|
184
|
-
const pixelSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
185
|
-
const percentageSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
|
|
143
|
+
const panelIdRef = useRef(providedId || `panel-${generatedId}`)
|
|
144
|
+
const panelId = panelIdRef.current
|
|
186
145
|
|
|
187
146
|
useEffect(() => {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
147
|
+
// id はマウント後に変更できない (登録・DOM 属性・計測が初期 id に固定されるため)
|
|
148
|
+
if (providedId && providedId !== panelId) {
|
|
149
|
+
logger.warn("Panel id prop change after mount is ignored.", { currentId: panelId, attemptedId: providedId })
|
|
191
150
|
}
|
|
192
|
-
}, [])
|
|
151
|
+
}, [providedId, panelId])
|
|
193
152
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
createCommittedSetter({
|
|
197
|
-
ref: pixelSizeRef,
|
|
198
|
-
setState: setPixelSize,
|
|
199
|
-
panelIdRef: panelId,
|
|
200
|
-
label: "pixel",
|
|
201
|
-
timerRef: pixelSyncTimerRef,
|
|
202
|
-
}),
|
|
203
|
-
[],
|
|
204
|
-
)
|
|
205
|
-
const commitPercentageSize = useMemo(
|
|
206
|
-
() =>
|
|
207
|
-
createCommittedSetter({
|
|
208
|
-
ref: percentageSizeRef,
|
|
209
|
-
setState: setPercentageSize,
|
|
210
|
-
panelIdRef: panelId,
|
|
211
|
-
label: "percentage",
|
|
212
|
-
timerRef: percentageSyncTimerRef,
|
|
213
|
-
}),
|
|
214
|
-
[],
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Reset both pixel and percentage caches to zero without debug logging.
|
|
219
|
-
* デバッグ出力なしでピクセルと割合のキャッシュをゼロへリセット。
|
|
220
|
-
*/
|
|
221
|
-
const resetPanelSizes = useCallback(() => {
|
|
222
|
-
// 保留中の最終同期タイマーを破棄する (折りたたみ後に古い実測値が復活するのを防ぐ)
|
|
223
|
-
if (pixelSyncTimerRef.current) {
|
|
224
|
-
clearTimeout(pixelSyncTimerRef.current)
|
|
225
|
-
pixelSyncTimerRef.current = null
|
|
226
|
-
}
|
|
227
|
-
if (percentageSyncTimerRef.current) {
|
|
228
|
-
clearTimeout(percentageSyncTimerRef.current)
|
|
229
|
-
percentageSyncTimerRef.current = null
|
|
230
|
-
}
|
|
231
|
-
if (pixelSizeRef.current !== 0) {
|
|
232
|
-
pixelSizeRef.current = 0
|
|
233
|
-
setPixelSize(0)
|
|
234
|
-
}
|
|
235
|
-
if (percentageSizeRef.current !== 0) {
|
|
236
|
-
percentageSizeRef.current = 0
|
|
237
|
-
setPercentageSize(0)
|
|
238
|
-
}
|
|
239
|
-
}, [])
|
|
153
|
+
const collapseFlags = useMemo(() => normalizeCollapsibleConfig(collapsible), [collapsible])
|
|
154
|
+
const isCollapsible = collapseFlags.collapseFromStart || collapseFlags.collapseFromEnd
|
|
240
155
|
|
|
241
156
|
useEffect(() => {
|
|
242
|
-
// ResizeObserver がコンテナサイズを取得するまでパネル登録を遅延
|
|
243
|
-
if (!isContainerReady) {
|
|
244
|
-
return
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// ピクセル単位の場合は制約範囲内に収めた初期値を算出
|
|
248
|
-
let resolvedPixelSize = 0
|
|
249
|
-
if (isPixelUnit && defaultSizeConfig) {
|
|
250
|
-
const targetSize = defaultSizeConfig.value
|
|
251
|
-
|
|
252
|
-
// ピクセル制約があれば初期値へ反映
|
|
253
|
-
let constrainedSize = targetSize
|
|
254
|
-
if (minSize && typeof minSize === "object" && minSize.unit === "pixels") {
|
|
255
|
-
constrainedSize = Math.max(constrainedSize, minSize.value)
|
|
256
|
-
}
|
|
257
|
-
if (maxSize && typeof maxSize === "object" && maxSize.unit === "pixels") {
|
|
258
|
-
constrainedSize = Math.min(constrainedSize, maxSize.value)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// 制約を適用した初期ピクセルサイズ
|
|
262
|
-
resolvedPixelSize = constrainedSize
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const isInitiallyCollapsed = shouldStartCollapsed
|
|
266
|
-
const initialCollapseDirection: PanelCollapseDirection | null = isInitiallyCollapsed ? preferredCollapsedDirection : null
|
|
267
|
-
|
|
268
157
|
if (defaultCollapsed && !isCollapsible) {
|
|
269
|
-
logger.warn("Panel defaultCollapsed is ignored because collapsible configuration is not provided.")
|
|
158
|
+
logger.warn("Panel defaultCollapsed is ignored because collapsible configuration is not provided.", { panelId })
|
|
270
159
|
}
|
|
160
|
+
}, [defaultCollapsed, isCollapsible, panelId])
|
|
271
161
|
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const preferredPixelSize = isPixelUnit ? resolvedPixelSize : undefined
|
|
278
|
-
|
|
279
|
-
const panel = {
|
|
280
|
-
id: panelId.current,
|
|
281
|
-
size: isInitiallyCollapsed ? 0 : resolvedPixelSize,
|
|
282
|
-
percentageSize: isInitiallyCollapsed ? 0 : preferredPercentage,
|
|
283
|
-
preferredPercentageSize: preferredPercentage,
|
|
284
|
-
preferredPixelSize,
|
|
285
|
-
sizeUnit: defaultSizeConfig?.unit || "percentage",
|
|
286
|
-
originalPixelSize: isPixelUnit ? resolvedPixelSize : undefined,
|
|
162
|
+
const registration = useMemo<PanelRegistration>(() => {
|
|
163
|
+
const normalized = normalizeSizeConfig(defaultSize) ?? { value: 50, unit: "percentage" as const }
|
|
164
|
+
return {
|
|
165
|
+
id: panelId,
|
|
166
|
+
...toPreference(normalized),
|
|
287
167
|
minSize,
|
|
288
168
|
maxSize,
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
collapsedByDirection: initialCollapseDirection,
|
|
292
|
-
collapsed: isInitiallyCollapsed,
|
|
293
|
-
sizeBeforeCollapse: initialSizeBeforeCollapse,
|
|
294
|
-
percentageSizeBeforeCollapse: initialPercentageBeforeCollapse,
|
|
295
|
-
preferredPercentageSizeBeforeCollapse: isCollapsible ? preferredPercentage : undefined,
|
|
296
|
-
preferredPixelSizeBeforeCollapse: isCollapsible ? preferredPixelSize : undefined,
|
|
169
|
+
...collapseFlags,
|
|
170
|
+
collapsed: isCollapsible && defaultCollapsed,
|
|
297
171
|
pixelAdjustPriority,
|
|
298
172
|
flexAdjustPriority,
|
|
299
|
-
autoMinSize,
|
|
300
173
|
}
|
|
174
|
+
}, [panelId, defaultSize, minSize, maxSize, collapseFlags, isCollapsible, defaultCollapsed, pixelAdjustPriority, flexAdjustPriority])
|
|
301
175
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
collapsibleConfig.collapseFromEnd,
|
|
306
|
-
collapsibleConfig.collapseFromStart,
|
|
307
|
-
defaultCollapsed,
|
|
308
|
-
defaultSizeConfig,
|
|
309
|
-
flexAdjustPriority,
|
|
310
|
-
isCollapsible,
|
|
311
|
-
isContainerReady,
|
|
312
|
-
isPixelUnit,
|
|
313
|
-
maxSize,
|
|
314
|
-
minSize,
|
|
315
|
-
pixelAdjustPriority,
|
|
316
|
-
preferredCollapsedDirection,
|
|
317
|
-
preferredPercentage,
|
|
318
|
-
registerPanel,
|
|
319
|
-
shouldStartCollapsed,
|
|
320
|
-
])
|
|
176
|
+
useLayoutEffect(() => {
|
|
177
|
+
registerPanel(registration)
|
|
178
|
+
}, [registerPanel, registration])
|
|
321
179
|
|
|
322
|
-
|
|
323
|
-
//
|
|
180
|
+
useLayoutEffect(() => {
|
|
181
|
+
// 登録と同じフェーズで解除する。パッシブ効果だと、同一コミット内で同じ id が
|
|
182
|
+
// 貼り替わったとき「新しい登録 → 古い解除」の順になり、パネルが状態から消えたまま戻らない
|
|
324
183
|
return () => {
|
|
325
|
-
unregisterPanel(panelId
|
|
326
|
-
reportPanelMeasurement(panelId.current, null)
|
|
184
|
+
unregisterPanel(panelId)
|
|
327
185
|
}
|
|
328
|
-
}, [unregisterPanel,
|
|
186
|
+
}, [unregisterPanel, panelId])
|
|
329
187
|
|
|
330
|
-
const
|
|
188
|
+
const panel = getPanel(panelId)
|
|
189
|
+
const size = panel?.size ?? 0
|
|
190
|
+
const isCollapsed = panel?.collapsed ?? false
|
|
331
191
|
|
|
332
192
|
/**
|
|
333
|
-
* Measure the
|
|
334
|
-
*
|
|
335
|
-
* observer から毎フレーム呼ばれるため、リデューサー状態は ref 経由で読み依存を安定させる。
|
|
193
|
+
* Measure the rendered length and report it for the debug overlay.
|
|
194
|
+
* 描画後の実寸を計測し、デバッグオーバーレイへ報告する処理。
|
|
336
195
|
*/
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (!latestPanel) {
|
|
196
|
+
const containerSizeRef = useRef(containerSize)
|
|
197
|
+
containerSizeRef.current = containerSize
|
|
198
|
+
const measure = useCallback(() => {
|
|
199
|
+
const element = panelRef.current
|
|
200
|
+
if (!element) {
|
|
343
201
|
return
|
|
344
202
|
}
|
|
203
|
+
const pixelSize = getContainerSize(element, direction).border
|
|
204
|
+
reportPanelMeasurement(panelId, { pixelSize, percentageSize: toRoundedPercentage(pixelSize, containerSizeRef.current) })
|
|
205
|
+
}, [direction, panelId, reportPanelMeasurement])
|
|
345
206
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const parentVertical = getContainerSize(parentElement, "vertical")
|
|
352
|
-
|
|
353
|
-
const currentPixelSize = direction === "horizontal" ? rect.width : rect.height
|
|
354
|
-
const parentSize = direction === "horizontal" ? parentHorizontal.inner : parentVertical.inner
|
|
355
|
-
const snapThreshold = calculateSnapThreshold(parentSize)
|
|
356
|
-
|
|
357
|
-
// 折りたたみ時はサイズをゼロに固定
|
|
358
|
-
if (isEffectivelyCollapsedRef.current) {
|
|
359
|
-
resetPanelSizes()
|
|
360
|
-
return
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// 実測値がノイズ範囲と判定される場合はスキップ
|
|
364
|
-
// snapThreshold から導いた閾値より大きなサイズをリデューサーが保持しているのに、ResizeObserver が一瞬だけ極端に小さい値を返すケースがある
|
|
365
|
-
// その揺らぎを反映すると表示がチラついたり、意図せず折りたたみ判定へ繋がるのでここで検出して破棄する
|
|
366
|
-
const expectedPanelSize = latestPanel.size ?? 0
|
|
367
|
-
const noiseThreshold = Math.max(2, snapThreshold * 0.5)
|
|
368
|
-
const looksLikeNoise = expectedPanelSize > snapThreshold && currentPixelSize < noiseThreshold
|
|
369
|
-
|
|
370
|
-
if (looksLikeNoise) {
|
|
371
|
-
logger.debug("skip noisy measurement", {
|
|
372
|
-
panelId: panelId.current,
|
|
373
|
-
expectedPanelSize,
|
|
374
|
-
measured: currentPixelSize,
|
|
375
|
-
noiseThreshold,
|
|
376
|
-
})
|
|
207
|
+
useEffect(() => {
|
|
208
|
+
// 実測値はデバッグ表示専用であり、レイアウトへは戻さない。
|
|
209
|
+
// 表示していないときに監視すると、全パネルで無用な強制レイアウトを起こす
|
|
210
|
+
if (!showDebugInfo) {
|
|
211
|
+
reportPanelMeasurement(panelId, null)
|
|
377
212
|
return
|
|
378
213
|
}
|
|
379
|
-
|
|
380
|
-
if (
|
|
381
|
-
|
|
382
|
-
commitPixelSize(currentPixelSize)
|
|
383
|
-
|
|
384
|
-
if (parentSize > 0) {
|
|
385
|
-
// 親コンテナ比率から割合を再計算
|
|
386
|
-
commitPercentageSize((currentPixelSize / parentSize) * 100)
|
|
387
|
-
}
|
|
214
|
+
const element = panelRef.current
|
|
215
|
+
if (!element || typeof ResizeObserver === "undefined") {
|
|
216
|
+
measure()
|
|
388
217
|
return
|
|
389
218
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
if (latestPanel.percentageSize !== undefined) {
|
|
395
|
-
// 状態が保持する割合をそのまま採用
|
|
396
|
-
commitPercentageSize(latestPanel.percentageSize)
|
|
397
|
-
} else if (latestPanel.sizeUnit === "pixels" && latestPanel.size !== undefined && parentSize > 0) {
|
|
398
|
-
// ピクセル単位パネルは親サイズから割合を求める
|
|
399
|
-
commitPercentageSize((latestPanel.size / parentSize) * 100)
|
|
400
|
-
}
|
|
401
|
-
}, [commitPercentageSize, commitPixelSize, direction, resetPanelSizes])
|
|
402
|
-
|
|
403
|
-
const hasInitialMeasurementRef = useRef(false)
|
|
404
|
-
useEffect(() => {
|
|
405
|
-
// 登録がリデューサーへ反映された直後に一度だけ DOM 実測を同期する
|
|
406
|
-
// (以降の追従は ResizeObserver / MutationObserver が担う)
|
|
407
|
-
if (!hasInitialMeasurementRef.current && currentPanel) {
|
|
408
|
-
hasInitialMeasurementRef.current = true
|
|
409
|
-
updateSizes()
|
|
410
|
-
}
|
|
411
|
-
}, [currentPanel, updateSizes])
|
|
412
|
-
|
|
413
|
-
useEffect(() => {
|
|
414
|
-
// ResizeObserver で DOM 実測を監視し状態と乖離時にローカルキャッシュを更新
|
|
415
|
-
const panelElement = panelRef.current
|
|
416
|
-
if (!panelElement) return
|
|
417
|
-
|
|
418
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
419
|
-
updateSizes()
|
|
420
|
-
})
|
|
421
|
-
|
|
422
|
-
resizeObserver.observe(panelElement)
|
|
423
|
-
|
|
424
|
-
updateSizes()
|
|
425
|
-
|
|
426
|
-
// 初期計測失敗時の再試行タイマー
|
|
427
|
-
const timeoutIds: Array<ReturnType<typeof setTimeout>> = []
|
|
428
|
-
|
|
429
|
-
// PANEL_RESIZE_RETRY_INTERVALS.forEach((delay) => {
|
|
430
|
-
// const timeoutId = setTimeout(() => {
|
|
431
|
-
// const rect = panelElement.getBoundingClientRect()
|
|
432
|
-
// const currentPixelSize = direction === "horizontal" ? rect.width : rect.height
|
|
433
|
-
|
|
434
|
-
// // 初期計測失敗や割合キャッシュ未設定を検出
|
|
435
|
-
// const shouldRetry =
|
|
436
|
-
// currentPixelSize === 0 || (currentPanel?.sizeUnit === "pixels" && percentageSizeRef.current === 0) || (currentPanel?.sizeUnit === "pixels" && currentPanel?.originalPixelSize !== undefined && percentageSizeRef.current === 0 && currentPixelSize > 0)
|
|
437
|
-
|
|
438
|
-
// if (shouldRetry) {
|
|
439
|
-
// // 計測のやり直しをスケジュール
|
|
440
|
-
// updateSizes()
|
|
441
|
-
// }
|
|
442
|
-
// }, delay)
|
|
443
|
-
// timeoutIds.push(timeoutId)
|
|
444
|
-
// })
|
|
445
|
-
|
|
446
|
-
const mutationObserver = new MutationObserver(() => {
|
|
447
|
-
// 親要素の属性や子要素変化でも遅延同期を実行してレイアウト変動へ追従
|
|
448
|
-
// ただし、測定値の更新による DOM 変更では再実行しない
|
|
449
|
-
const shouldUpdate = (() => {
|
|
450
|
-
if (!panelRef.current) return false
|
|
451
|
-
const rect = panelRef.current.getBoundingClientRect()
|
|
452
|
-
const measuredSize = direction === "horizontal" ? rect.width : rect.height
|
|
453
|
-
// 実測サイズがローカルキャッシュと一致していれば更新不要
|
|
454
|
-
if (measuredSize === pixelSizeRef.current) {
|
|
455
|
-
return false
|
|
456
|
-
}
|
|
457
|
-
return true
|
|
458
|
-
})()
|
|
459
|
-
|
|
460
|
-
if (shouldUpdate) {
|
|
461
|
-
setTimeout(updateSizes, 0)
|
|
462
|
-
}
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
const parentElement = panelElement.parentElement
|
|
466
|
-
if (parentElement) {
|
|
467
|
-
// 親 (グループコンテナ) 自身の属性変化と直下の子要素の増減のみを監視する。
|
|
468
|
-
// subtree 監視はパネル内容の DOM 変異のたびに全パネルで強制レイアウトを誘発するため使わない。
|
|
469
|
-
// パネル自身のサイズ変化は ResizeObserver が捕捉する。
|
|
470
|
-
mutationObserver.observe(parentElement, {
|
|
471
|
-
childList: true,
|
|
472
|
-
attributes: true,
|
|
473
|
-
attributeFilter: ["style", "class"],
|
|
474
|
-
})
|
|
475
|
-
}
|
|
476
|
-
|
|
219
|
+
const observer = new ResizeObserver(measure)
|
|
220
|
+
observer.observe(element)
|
|
221
|
+
measure()
|
|
477
222
|
return () => {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
// ピクセルと親サイズの双方が取得できた場合にのみ割合を更新
|
|
518
|
-
if (currentPixelSize > 0 && parentSize > 0) {
|
|
519
|
-
const calculatedPercentage = (currentPixelSize / parentSize) * 100
|
|
520
|
-
commitPercentageSize(calculatedPercentage)
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}, [commitPercentageSize, commitPixelSize, currentPanel, currentPanelPercentageSize, currentPanelSize, currentPanelSizeUnit, direction, isEffectivelyCollapsed, resetPanelSizes])
|
|
526
|
-
|
|
527
|
-
useEffect(() => {
|
|
528
|
-
// 最新測定値をコンテキスト経由でグループへ報告しオーバーレイ等を更新
|
|
529
|
-
reportPanelMeasurement(panelId.current, {
|
|
530
|
-
pixelSize,
|
|
531
|
-
percentageSize,
|
|
532
|
-
})
|
|
533
|
-
}, [reportPanelMeasurement, pixelSize, percentageSize])
|
|
534
|
-
|
|
535
|
-
/**
|
|
536
|
-
* Compute CSS flex-basis value based on panel size unit and collapse state.
|
|
537
|
-
* パネルサイズユニットと折りたたみ状態に基づいて CSS flex-basis 値を計算。
|
|
538
|
-
*/
|
|
539
|
-
const getFlexBasis = () => {
|
|
540
|
-
if (isEffectivelyCollapsed) {
|
|
541
|
-
return "0px"
|
|
542
|
-
}
|
|
543
|
-
if (currentPanel?.originalPixelSize !== undefined) {
|
|
544
|
-
return `${currentSize}px`
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
if (currentPanel?.percentageSize !== undefined) {
|
|
548
|
-
return `${currentPanel.percentageSize}%`
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
return `${currentSize}px`
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const computedFlexShrink = currentPanel?.sizeUnit === "percentage" && !currentPanel?.collapsed ? 1 : 0
|
|
555
|
-
|
|
556
|
-
// パネル自身の Flex 振る舞いと最小サイズを制御するベーススタイル
|
|
557
|
-
const panelStyle: CSSProperties = {
|
|
558
|
-
flexBasis: getFlexBasis(),
|
|
559
|
-
flexGrow: 0,
|
|
560
|
-
flexShrink: computedFlexShrink,
|
|
561
|
-
overflow: "hidden",
|
|
562
|
-
position: "relative",
|
|
563
|
-
boxSizing: "border-box",
|
|
564
|
-
minHeight: "0px",
|
|
565
|
-
minWidth: "0px",
|
|
566
|
-
...style,
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
if (direction === "horizontal") {
|
|
570
|
-
panelStyle.height = "100%"
|
|
571
|
-
// 横並びではコンテンツに引きずられずに縮むため minWidth を確実に 0 にする
|
|
572
|
-
panelStyle.minWidth = 0
|
|
573
|
-
} else {
|
|
574
|
-
panelStyle.width = "100%"
|
|
575
|
-
// 縦並びでは高さ縮小時にコンテンツの min-height を無視するため 0 を設定
|
|
576
|
-
panelStyle.minHeight = 0
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
if (isEffectivelyCollapsed) {
|
|
580
|
-
// 折りたたみ時にパネルをレイアウトから外しユーザー操作も遮断するためのスタイル
|
|
581
|
-
if (direction === "horizontal") {
|
|
582
|
-
panelStyle.width = "0px"
|
|
583
|
-
panelStyle.minWidth = "0px"
|
|
584
|
-
} else {
|
|
585
|
-
panelStyle.height = "0px"
|
|
586
|
-
panelStyle.minHeight = "0px"
|
|
587
|
-
}
|
|
588
|
-
panelStyle.flexGrow = 0
|
|
589
|
-
panelStyle.flexShrink = 0
|
|
590
|
-
panelStyle.pointerEvents = "none"
|
|
591
|
-
panelStyle.visibility = "hidden"
|
|
592
|
-
if (isExplicitlyCollapsed) {
|
|
593
|
-
panelStyle.display = "none"
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
const containerAxisSize = direction === "horizontal" ? containerSize.width : containerSize.height
|
|
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])
|
|
598
262
|
|
|
599
|
-
// 実際の子要素をパネル内に固定しスクロールも許可するためのラッパースタイル
|
|
600
263
|
const contentWrapperStyle = useMemo<CSSProperties>(
|
|
601
264
|
() => ({
|
|
602
|
-
|
|
603
|
-
|
|
265
|
+
// 内容は通常フローの伸縮子として置く。絶対配置にするとパディングボックスを基準にしてしまい、
|
|
266
|
+
// 利用者がパネルへ与えたパディングが何の効果も持たなくなる
|
|
267
|
+
flex: "1 1 auto",
|
|
268
|
+
position: "relative",
|
|
604
269
|
overflow: contentOverflow,
|
|
605
270
|
display: "flex",
|
|
606
271
|
flexDirection: "column",
|
|
607
|
-
width: "100%",
|
|
608
|
-
height: "100%",
|
|
609
272
|
minWidth: "0px",
|
|
610
273
|
minHeight: "0px",
|
|
611
274
|
}),
|
|
@@ -614,21 +277,20 @@ export const Panel = memo(
|
|
|
614
277
|
|
|
615
278
|
return (
|
|
616
279
|
<div
|
|
280
|
+
{...elementProps}
|
|
617
281
|
ref={panelRef}
|
|
618
282
|
className={twMerge("panel", className)}
|
|
619
283
|
style={panelStyle}
|
|
620
|
-
data-panel-id={panelId
|
|
621
|
-
data-panel-size={
|
|
622
|
-
data-panel-size-unit={
|
|
623
|
-
data-panel-pixel-size={currentPanel?.sizeUnit === "pixels" ? currentPanel?.originalPixelSize : null}
|
|
624
|
-
data-panel-order={order}
|
|
284
|
+
data-panel-id={panelId}
|
|
285
|
+
data-panel-size={size}
|
|
286
|
+
data-panel-size-unit={panel?.sizeUnit ?? "percentage"}
|
|
625
287
|
data-panel-collapsible={isCollapsible}
|
|
626
|
-
data-panel-collapse-start={
|
|
627
|
-
data-panel-collapse-end={
|
|
628
|
-
data-panel-collapsed={
|
|
629
|
-
aria-hidden={
|
|
288
|
+
data-panel-collapse-start={collapseFlags.collapseFromStart}
|
|
289
|
+
data-panel-collapse-end={collapseFlags.collapseFromEnd}
|
|
290
|
+
data-panel-collapsed={isCollapsed}
|
|
291
|
+
aria-hidden={isCollapsed}>
|
|
630
292
|
<div style={contentWrapperStyle}>{children}</div>
|
|
631
|
-
{
|
|
293
|
+
{showDebugInfo && <PanelDebugInfo panel={panel} containerAxisSize={containerSize} direction={direction} />}
|
|
632
294
|
</div>
|
|
633
295
|
)
|
|
634
296
|
},
|