@aiquants/resize-panels 1.7.2 → 1.8.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/LICENSE +21 -0
- package/README.md +21 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/styles/resize-panels.css +1 -1
- package/dist/styles/resize-panels.standalone.css +3 -0
- package/package.json +10 -7
- package/src/GlobalDebugOverlay.tsx +284 -0
- package/src/Panel.tsx +635 -0
- package/src/PanelDebugInfo.tsx +148 -0
- package/src/PanelGroup.tsx +260 -0
- package/src/PanelResizeHandle.tsx +718 -0
- package/src/context.ts +25 -0
- package/src/debugOverlayStore.ts +351 -0
- package/src/hooks.ts +968 -0
- package/src/index.ts +62 -0
- package/src/reducer.ts +1234 -0
- package/src/roundHalfToEven.ts +40 -0
- package/src/styles/components.entry.css +8 -0
- package/src/styles/resize-panels.css +3 -0
- package/src/styles/standalone.entry.css +12 -0
- package/src/types.ts +288 -0
- package/src/utils/simple-logger.ts +186 -0
- package/src/utils.ts +279 -0
- package/dist/resize-panels.css +0 -1
package/src/context.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file PanelGroup context for managing panel state
|
|
3
|
+
* パネル状態を管理するための PanelGroup コンテキスト
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createContext, useContext } from "react"
|
|
7
|
+
import type { PanelGroupContextValue } from "./types"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Provide shared panel group state to descendant components.
|
|
11
|
+
* 子孫コンポーネントへパネルグループの共有状態を提供するコンテキスト。
|
|
12
|
+
*/
|
|
13
|
+
export const PanelGroupContext = createContext<PanelGroupContextValue | null>(null)
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Access panel group behaviors and layout data from the nearest provider.
|
|
17
|
+
* 最も近いプロバイダーからパネルグループの振る舞いとレイアウトデータを取得するフック。
|
|
18
|
+
*/
|
|
19
|
+
export const usePanelGroup = () => {
|
|
20
|
+
const context = useContext(PanelGroupContext)
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error("usePanelGroup must be used within a PanelGroup")
|
|
23
|
+
}
|
|
24
|
+
return context
|
|
25
|
+
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Debug overlay store utilities
|
|
3
|
+
* デバッグオーバーレイのストアユーティリティ
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useSyncExternalStore } from "react"
|
|
7
|
+
import type { ContainerSize, PanelDirection, PanelLayoutData, ResizeHandleLayoutData } from "./types"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Snapshot of the debug overlay state across panel groups.
|
|
11
|
+
* デバッグオーバーレイのスナップショット状態を表す構造体。
|
|
12
|
+
*/
|
|
13
|
+
export type DebugOverlaySnapshot = {
|
|
14
|
+
ownerId: string | null
|
|
15
|
+
groups: DebugGroupState[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Aggregated state for a panel group tracked by the debug overlay.
|
|
20
|
+
* デバッグオーバーレイが追跡するパネルグループの集約状態。
|
|
21
|
+
*/
|
|
22
|
+
export type DebugGroupState = {
|
|
23
|
+
groupId: string
|
|
24
|
+
displayName: string
|
|
25
|
+
direction: PanelDirection
|
|
26
|
+
containerSize: ContainerSize
|
|
27
|
+
panels: PanelLayoutData[]
|
|
28
|
+
handles: ResizeHandleLayoutData[]
|
|
29
|
+
index: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type DebugGroupUpdate = {
|
|
33
|
+
displayName: string
|
|
34
|
+
direction: PanelDirection
|
|
35
|
+
containerSize: ContainerSize
|
|
36
|
+
panels: PanelLayoutData[]
|
|
37
|
+
handles: ResizeHandleLayoutData[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type StoreListener = () => void
|
|
41
|
+
|
|
42
|
+
type DebugGroupEntry = DebugGroupState & {
|
|
43
|
+
serializedPanels: string
|
|
44
|
+
serializedHandles: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const serializePanels = (panels: PanelLayoutData[]) => {
|
|
48
|
+
return JSON.stringify(
|
|
49
|
+
panels.map((panel) => ({
|
|
50
|
+
id: panel.id,
|
|
51
|
+
size: panel.size,
|
|
52
|
+
percentageSize: panel.percentageSize ?? null,
|
|
53
|
+
preferredPercentageSize: panel.preferredPercentageSize ?? null,
|
|
54
|
+
preferredPixelSize: panel.preferredPixelSize ?? null,
|
|
55
|
+
sizeUnit: panel.sizeUnit,
|
|
56
|
+
originalPixelSize: panel.originalPixelSize ?? null,
|
|
57
|
+
minSize: panel.minSize ?? null,
|
|
58
|
+
maxSize: panel.maxSize ?? null,
|
|
59
|
+
autoMinSize: panel.autoMinSize ?? null,
|
|
60
|
+
collapseFromStart: panel.collapseFromStart,
|
|
61
|
+
collapseFromEnd: panel.collapseFromEnd,
|
|
62
|
+
collapsedByDirection: panel.collapsedByDirection ?? null,
|
|
63
|
+
collapsed: panel.collapsed ?? false,
|
|
64
|
+
sizeBeforeCollapse: panel.sizeBeforeCollapse ?? null,
|
|
65
|
+
preferredPercentageSizeBeforeCollapse: panel.preferredPercentageSizeBeforeCollapse ?? null,
|
|
66
|
+
preferredPixelSizeBeforeCollapse: panel.preferredPixelSizeBeforeCollapse ?? null,
|
|
67
|
+
measuredPixelSizeBeforeCollapse: panel.measuredPixelSizeBeforeCollapse ?? null,
|
|
68
|
+
pixelAdjustPriority: panel.pixelAdjustPriority ?? null,
|
|
69
|
+
flexAdjustPriority: panel.flexAdjustPriority ?? null,
|
|
70
|
+
measuredPixelSize: panel.measuredPixelSize ?? null,
|
|
71
|
+
measuredPercentageSize: panel.measuredPercentageSize ?? null,
|
|
72
|
+
})),
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const serializeHandles = (handles: ResizeHandleLayoutData[]) => {
|
|
77
|
+
return JSON.stringify(
|
|
78
|
+
handles.map((handle) => ({
|
|
79
|
+
id: handle.id,
|
|
80
|
+
thickness: handle.thickness,
|
|
81
|
+
visible: handle.visible,
|
|
82
|
+
direction: handle.direction,
|
|
83
|
+
startPanelId: handle.startPanelId ?? null,
|
|
84
|
+
endPanelId: handle.endPanelId ?? null,
|
|
85
|
+
})),
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const createDebugOverlayStore = () => {
|
|
90
|
+
const listeners = new Set<StoreListener>()
|
|
91
|
+
const groups = new Map<string, DebugGroupEntry>()
|
|
92
|
+
const enabledGroups = new Set<string>()
|
|
93
|
+
let ownerId: string | null = null
|
|
94
|
+
let orderCounter = 0
|
|
95
|
+
let snapshotDirty = true
|
|
96
|
+
let cachedGroups: DebugGroupState[] = []
|
|
97
|
+
let cachedSnapshot: DebugOverlaySnapshot = {
|
|
98
|
+
ownerId: null,
|
|
99
|
+
groups: cachedGroups,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// リスナーへ変更通知
|
|
103
|
+
const notify = () => {
|
|
104
|
+
for (const listener of listeners) {
|
|
105
|
+
listener()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// スナップショット再計算フラグを設定して通知
|
|
110
|
+
const emitChange = () => {
|
|
111
|
+
snapshotDirty = true
|
|
112
|
+
notify()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 有効なグループから次のオーバーレイオーナーを決定
|
|
116
|
+
const resolveOwner = () => {
|
|
117
|
+
const previousOwner = ownerId
|
|
118
|
+
if (previousOwner && enabledGroups.has(previousOwner)) {
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
ownerId = null
|
|
122
|
+
const iterator = enabledGroups.values().next()
|
|
123
|
+
if (!iterator.done) {
|
|
124
|
+
ownerId = iterator.value
|
|
125
|
+
}
|
|
126
|
+
return previousOwner !== ownerId
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// リスナー登録とサブスクリプション管理
|
|
130
|
+
const subscribe = (listener: StoreListener) => {
|
|
131
|
+
listeners.add(listener)
|
|
132
|
+
return () => {
|
|
133
|
+
listeners.delete(listener)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// スナップショット取得 (必要に応じて再構築)
|
|
138
|
+
const getSnapshot = (): DebugOverlaySnapshot => {
|
|
139
|
+
if (resolveOwner()) {
|
|
140
|
+
snapshotDirty = true
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (snapshotDirty) {
|
|
144
|
+
const sorted = Array.from(groups.values()).sort((a, b) => a.index - b.index)
|
|
145
|
+
cachedGroups = sorted.map(({ serializedPanels, serializedHandles, ...publicGroup }) => publicGroup)
|
|
146
|
+
cachedSnapshot = {
|
|
147
|
+
ownerId,
|
|
148
|
+
groups: cachedGroups,
|
|
149
|
+
}
|
|
150
|
+
snapshotDirty = false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return cachedSnapshot
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// グループを有効化してオーバーレイ対象へ追加
|
|
157
|
+
const enableGroup = (groupId: string) => {
|
|
158
|
+
const previousSize = enabledGroups.size
|
|
159
|
+
enabledGroups.add(groupId)
|
|
160
|
+
if (enabledGroups.size === previousSize) {
|
|
161
|
+
return
|
|
162
|
+
}
|
|
163
|
+
if (resolveOwner()) {
|
|
164
|
+
emitChange()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// グループを無効化してオーバーレイ対象から除外
|
|
169
|
+
const disableGroup = (groupId: string) => {
|
|
170
|
+
if (!enabledGroups.delete(groupId)) {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
if (resolveOwner()) {
|
|
174
|
+
emitChange()
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// グループ状態を更新 (差分検出して変更時のみ通知)
|
|
179
|
+
const updateGroup = (groupId: string, update: DebugGroupUpdate) => {
|
|
180
|
+
const existing = groups.get(groupId)
|
|
181
|
+
const index = existing?.index ?? orderCounter++
|
|
182
|
+
const serializedPanels = serializePanels(update.panels)
|
|
183
|
+
const serializedHandles = serializeHandles(update.handles)
|
|
184
|
+
|
|
185
|
+
// 各プロパティの変更検出
|
|
186
|
+
const displayChanged = existing?.displayName !== update.displayName
|
|
187
|
+
const directionChanged = existing?.direction !== update.direction
|
|
188
|
+
const containerChanged = existing ? existing.containerSize.width !== update.containerSize.width || existing.containerSize.height !== update.containerSize.height : true
|
|
189
|
+
|
|
190
|
+
// パネル配列の変更を詳細検証
|
|
191
|
+
let panelsChanged = existing?.serializedPanels !== serializedPanels
|
|
192
|
+
if (!panelsChanged && existing) {
|
|
193
|
+
if (existing.panels.length !== update.panels.length) {
|
|
194
|
+
panelsChanged = true
|
|
195
|
+
} else {
|
|
196
|
+
for (let index = 0; index < update.panels.length; index += 1) {
|
|
197
|
+
const prevPanel = existing.panels[index]
|
|
198
|
+
const nextPanel = update.panels[index]
|
|
199
|
+
if (!(prevPanel && nextPanel)) {
|
|
200
|
+
panelsChanged = true
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
if (
|
|
204
|
+
prevPanel.id !== nextPanel.id ||
|
|
205
|
+
prevPanel.size !== nextPanel.size ||
|
|
206
|
+
prevPanel.percentageSize !== nextPanel.percentageSize ||
|
|
207
|
+
prevPanel.collapsed !== nextPanel.collapsed ||
|
|
208
|
+
prevPanel.measuredPixelSize !== nextPanel.measuredPixelSize ||
|
|
209
|
+
prevPanel.measuredPercentageSize !== nextPanel.measuredPercentageSize
|
|
210
|
+
) {
|
|
211
|
+
panelsChanged = true
|
|
212
|
+
break
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ハンドル配列の変更を詳細検証
|
|
219
|
+
let handlesChanged = existing?.serializedHandles !== serializedHandles
|
|
220
|
+
if (!handlesChanged && existing) {
|
|
221
|
+
if (existing.handles.length !== update.handles.length) {
|
|
222
|
+
handlesChanged = true
|
|
223
|
+
} else {
|
|
224
|
+
for (let index = 0; index < update.handles.length; index += 1) {
|
|
225
|
+
const prevHandle = existing.handles[index]
|
|
226
|
+
const nextHandle = update.handles[index]
|
|
227
|
+
if (!(prevHandle && nextHandle)) {
|
|
228
|
+
handlesChanged = true
|
|
229
|
+
break
|
|
230
|
+
}
|
|
231
|
+
if (
|
|
232
|
+
prevHandle.id !== nextHandle.id ||
|
|
233
|
+
prevHandle.thickness !== nextHandle.thickness ||
|
|
234
|
+
prevHandle.visible !== nextHandle.visible ||
|
|
235
|
+
prevHandle.direction !== nextHandle.direction ||
|
|
236
|
+
prevHandle.startPanelId !== nextHandle.startPanelId ||
|
|
237
|
+
prevHandle.endPanelId !== nextHandle.endPanelId
|
|
238
|
+
) {
|
|
239
|
+
handlesChanged = true
|
|
240
|
+
break
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// 空のグループは初回のみスキップ
|
|
247
|
+
if (!existing && update.panels.length === 0 && update.handles.length === 0) {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 変更なければスキップ
|
|
252
|
+
if (existing && !displayChanged && !directionChanged && !containerChanged && !panelsChanged && !handlesChanged) {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 状態をクローンして保存
|
|
257
|
+
const clonedPanels = update.panels.map((panel) => ({ ...panel }))
|
|
258
|
+
const clonedHandles = update.handles.map((handle) => ({ ...handle }))
|
|
259
|
+
const clonedContainer = { ...update.containerSize }
|
|
260
|
+
|
|
261
|
+
groups.set(groupId, {
|
|
262
|
+
groupId,
|
|
263
|
+
displayName: update.displayName,
|
|
264
|
+
direction: update.direction,
|
|
265
|
+
containerSize: clonedContainer,
|
|
266
|
+
panels: clonedPanels,
|
|
267
|
+
handles: clonedHandles,
|
|
268
|
+
index,
|
|
269
|
+
serializedPanels,
|
|
270
|
+
serializedHandles,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
emitChange()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// グループを削除
|
|
277
|
+
const removeGroup = (groupId: string) => {
|
|
278
|
+
const removed = groups.delete(groupId)
|
|
279
|
+
const disabledNow = enabledGroups.delete(groupId)
|
|
280
|
+
const ownerChanged = resolveOwner()
|
|
281
|
+
|
|
282
|
+
if (removed || disabledNow || ownerChanged) {
|
|
283
|
+
emitChange()
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
subscribe,
|
|
289
|
+
getSnapshot,
|
|
290
|
+
getOwnerId: () => {
|
|
291
|
+
if (resolveOwner()) {
|
|
292
|
+
snapshotDirty = true
|
|
293
|
+
}
|
|
294
|
+
return ownerId
|
|
295
|
+
},
|
|
296
|
+
enableGroup,
|
|
297
|
+
disableGroup,
|
|
298
|
+
updateGroup,
|
|
299
|
+
removeGroup,
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const debugOverlayStore = createDebugOverlayStore()
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Subscribe to the identifier of the panel group that renders the overlay.
|
|
307
|
+
* オーバーレイを描画するパネルグループ識別子を購読するフック。
|
|
308
|
+
*/
|
|
309
|
+
export const useDebugOverlayOwnerId = () => {
|
|
310
|
+
return useSyncExternalStore(debugOverlayStore.subscribe, debugOverlayStore.getOwnerId, debugOverlayStore.getOwnerId)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Subscribe to the aggregate debug overlay snapshot.
|
|
315
|
+
* 集約されたデバッグオーバーレイのスナップショットを購読するフック。
|
|
316
|
+
*/
|
|
317
|
+
export const useDebugOverlaySnapshot = () => {
|
|
318
|
+
return useSyncExternalStore(debugOverlayStore.subscribe, debugOverlayStore.getSnapshot, debugOverlayStore.getSnapshot)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Enable a panel group for inclusion in the debug overlay.
|
|
323
|
+
* デバッグオーバーレイへの取り込み対象としてパネルグループを有効化するメソッド。
|
|
324
|
+
*/
|
|
325
|
+
export const enableDebugOverlayGroup = (groupId: string) => {
|
|
326
|
+
debugOverlayStore.enableGroup(groupId)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Disable a panel group from the debug overlay without erasing cached data.
|
|
331
|
+
* キャッシュを保持したままパネルグループをデバッグオーバーレイ対象から外すメソッド。
|
|
332
|
+
*/
|
|
333
|
+
export const disableDebugOverlayGroup = (groupId: string) => {
|
|
334
|
+
debugOverlayStore.disableGroup(groupId)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Persist the latest panel group measurements into the debug overlay store.
|
|
339
|
+
* 最新のパネルグループ測定値をデバッグオーバーレイストアに保存するメソッド。
|
|
340
|
+
*/
|
|
341
|
+
export const updateDebugOverlayGroup = (groupId: string, update: DebugGroupUpdate) => {
|
|
342
|
+
debugOverlayStore.updateGroup(groupId, update)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Remove a panel group from the debug overlay store entirely.
|
|
347
|
+
* パネルグループをデバッグオーバーレイストアから完全に削除するメソッド。
|
|
348
|
+
*/
|
|
349
|
+
export const removeDebugOverlayGroup = (groupId: string) => {
|
|
350
|
+
debugOverlayStore.removeGroup(groupId)
|
|
351
|
+
}
|